olas-operate-middleware 0.10.5__py3-none-any.whl → 0.10.7__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.5.dist-info → olas_operate_middleware-0.10.7.dist-info}/METADATA +2 -2
- {olas_operate_middleware-0.10.5.dist-info → olas_operate_middleware-0.10.7.dist-info}/RECORD +14 -10
- 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/ledger/profiles.py +73 -59
- operate/operate_types.py +2 -0
- operate/services/manage.py +181 -2
- operate/services/protocol.py +146 -67
- operate/services/service.py +12 -0
- {olas_operate_middleware-0.10.5.dist-info → olas_operate_middleware-0.10.7.dist-info}/LICENSE +0 -0
- {olas_operate_middleware-0.10.5.dist-info → olas_operate_middleware-0.10.7.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.10.5.dist-info → olas_operate_middleware-0.10.7.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ------------------------------------------------------------------------------
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2025 Valory AG
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
# ------------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
"""This module contains the class to connect to the `RecoveryModule` contract."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
from typing import Any, Dict
|
|
24
|
+
|
|
25
|
+
from aea.configurations.base import PublicId
|
|
26
|
+
from aea.contracts.base import Contract
|
|
27
|
+
from aea.crypto.base import LedgerApi
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
PUBLIC_ID = PublicId.from_str("valory/recovery_module:0.1.0")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RecoveryModule(Contract):
|
|
34
|
+
"""The RecoveryModule contract"""
|
|
35
|
+
|
|
36
|
+
contract_id = PUBLIC_ID
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def get_recover_access_transaction(
|
|
40
|
+
cls,
|
|
41
|
+
ledger_api: LedgerApi,
|
|
42
|
+
contract_address: str,
|
|
43
|
+
owner: str,
|
|
44
|
+
service_id: int,
|
|
45
|
+
raise_on_try: bool = False,
|
|
46
|
+
) -> Dict[str, Any]:
|
|
47
|
+
"""Get the recover access transaction."""
|
|
48
|
+
|
|
49
|
+
tx_params = ledger_api.build_transaction(
|
|
50
|
+
contract_instance=cls.get_instance(
|
|
51
|
+
ledger_api=ledger_api, contract_address=contract_address
|
|
52
|
+
),
|
|
53
|
+
method_name="recoverAccess",
|
|
54
|
+
method_args={
|
|
55
|
+
"serviceId": service_id,
|
|
56
|
+
},
|
|
57
|
+
tx_args={"sender_address": owner},
|
|
58
|
+
raise_on_try=raise_on_try,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return tx_params
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: recovery_module
|
|
2
|
+
author: valory
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
type: contract
|
|
5
|
+
description: Recovery module
|
|
6
|
+
license: Apache-2.0
|
|
7
|
+
aea_version: '>=1.0.0, <2.0.0'
|
|
8
|
+
fingerprint:
|
|
9
|
+
__init__.py: bafybeicjlkmxs5ikpgdtgndifstpmmpaixwcbgouvmt6gowuhf5dy3dpgu
|
|
10
|
+
build/RecoveryModule.json: bafybeifsyjdbprcp4kxpijlpqfovxcf7cv2ujapt3d5zn34wby26ldv4ky
|
|
11
|
+
contract.py: bafybeifihpwb3etbrnn2hqdizsp5zolis3ur23slt3q5zctcykm5nlvv7q
|
|
12
|
+
fingerprint_ignore_patterns: []
|
|
13
|
+
contracts: []
|
|
14
|
+
class_name: RecoveryModule
|
|
15
|
+
contract_interface_paths:
|
|
16
|
+
ethereum: build/RecoveryModule.json
|
|
17
|
+
dependencies:
|
|
18
|
+
open-aea-ledger-ethereum:
|
|
19
|
+
version: ==1.60.0
|
|
20
|
+
open-aea-test-autonomy:
|
|
21
|
+
version: ==0.18.3
|
|
22
|
+
web3:
|
|
23
|
+
version: <7,>=6.0.0
|
operate/ledger/profiles.py
CHANGED
|
@@ -21,64 +21,46 @@
|
|
|
21
21
|
|
|
22
22
|
import typing as t
|
|
23
23
|
|
|
24
|
+
from autonomy.chain.constants import CHAIN_PROFILES, DEFAULT_MULTISEND
|
|
25
|
+
|
|
24
26
|
from operate.constants import NO_STAKING_PROGRAM_ID, ZERO_ADDRESS
|
|
25
27
|
from operate.operate_types import Chain, ContractAddresses
|
|
26
28
|
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"gnosis_safe_proxy_factory": "0x5953f21495BD9aF1D78e87bb42AcCAA55C1e896C",
|
|
45
|
-
"gnosis_safe_same_address_multisig": "0xb09CcF0Dbf0C178806Aaee28956c74bd66d21f73",
|
|
46
|
-
"multisend": "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D",
|
|
47
|
-
}
|
|
48
|
-
),
|
|
49
|
-
Chain.ETHEREUM: ContractAddresses(
|
|
50
|
-
{
|
|
51
|
-
"service_manager": "0x2EA682121f815FBcF86EA3F3CaFdd5d67F2dB143",
|
|
52
|
-
"service_registry": "0x48b6af7B12C71f09e2fC8aF4855De4Ff54e775cA",
|
|
53
|
-
"service_registry_token_utility": "0x3Fb926116D454b95c669B6Bf2E7c3bad8d19affA",
|
|
54
|
-
"gnosis_safe_proxy_factory": "0x46C0D07F55d4F9B5Eed2Fc9680B5953e5fd7b461",
|
|
55
|
-
"gnosis_safe_same_address_multisig": "0xfa517d01DaA100cB1932FA4345F68874f7E7eF46",
|
|
56
|
-
"multisend": "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D",
|
|
57
|
-
}
|
|
58
|
-
),
|
|
59
|
-
Chain.BASE: ContractAddresses(
|
|
60
|
-
{
|
|
61
|
-
"service_manager": "0x63e66d7ad413C01A7b49C7FF4e3Bb765C4E4bd1b",
|
|
62
|
-
"service_registry": "0x3C1fF68f5aa342D296d4DEe4Bb1cACCA912D95fE",
|
|
63
|
-
"service_registry_token_utility": "0x34C895f302D0b5cf52ec0Edd3945321EB0f83dd5",
|
|
64
|
-
"gnosis_safe_proxy_factory": "0x22bE6fDcd3e29851B29b512F714C328A00A96B83",
|
|
65
|
-
"gnosis_safe_same_address_multisig": "0xFbBEc0C8b13B38a9aC0499694A69a10204c5E2aB",
|
|
66
|
-
"multisend": "0x40A2aCCbd92BCA938b02010E17A5b8929b49130D",
|
|
67
|
-
}
|
|
68
|
-
),
|
|
69
|
-
Chain.MODE: ContractAddresses(
|
|
30
|
+
# TODO: Refactor, remove the usage of CONTRACTS and use CHAIN_PROFILES from Open Autonomy instead.
|
|
31
|
+
CHAINS = [
|
|
32
|
+
Chain.ARBITRUM_ONE,
|
|
33
|
+
Chain.BASE,
|
|
34
|
+
Chain.CELO,
|
|
35
|
+
Chain.ETHEREUM,
|
|
36
|
+
Chain.GNOSIS,
|
|
37
|
+
Chain.MODE,
|
|
38
|
+
Chain.OPTIMISM,
|
|
39
|
+
Chain.POLYGON,
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
CONTRACTS: t.Dict[Chain, ContractAddresses] = {}
|
|
43
|
+
for _chain in CHAINS:
|
|
44
|
+
profile = CHAIN_PROFILES[_chain.value]
|
|
45
|
+
CONTRACTS[_chain] = ContractAddresses(
|
|
70
46
|
{
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"gnosis_safe_proxy_factory": "
|
|
75
|
-
"gnosis_safe_same_address_multisig":
|
|
76
|
-
|
|
47
|
+
"service_registry": profile["service_registry"],
|
|
48
|
+
"service_registry_token_utility": profile["service_registry_token_utility"],
|
|
49
|
+
"service_manager": profile["service_manager_token"],
|
|
50
|
+
"gnosis_safe_proxy_factory": profile["gnosis_safe_proxy_factory"],
|
|
51
|
+
"gnosis_safe_same_address_multisig": profile[
|
|
52
|
+
"gnosis_safe_same_address_multisig"
|
|
53
|
+
],
|
|
54
|
+
"safe_multisig_with_recovery_module": profile[
|
|
55
|
+
"safe_multisig_with_recovery_module"
|
|
56
|
+
],
|
|
57
|
+
"recovery_module": profile["recovery_module"],
|
|
58
|
+
"multisend": DEFAULT_MULTISEND,
|
|
77
59
|
}
|
|
78
|
-
)
|
|
79
|
-
}
|
|
60
|
+
)
|
|
80
61
|
|
|
81
62
|
STAKING: t.Dict[Chain, t.Dict[str, str]] = {
|
|
63
|
+
Chain.ARBITRUM_ONE: {},
|
|
82
64
|
Chain.GNOSIS: {
|
|
83
65
|
"pearl_alpha": "0xEE9F19b5DF06c7E8Bfc7B28745dcf944C504198A",
|
|
84
66
|
"pearl_beta": "0xeF44Fb0842DDeF59D37f85D61A1eF492bbA6135d",
|
|
@@ -150,6 +132,7 @@ STAKING: t.Dict[Chain, t.Dict[str, str]] = {
|
|
|
150
132
|
"modius_alpha_3": "0x9034D0413D122015710f1744A19eFb1d7c2CEB13",
|
|
151
133
|
"modius_alpha_4": "0x8BcAdb2c291C159F9385964e5eD95a9887302862",
|
|
152
134
|
},
|
|
135
|
+
Chain.POLYGON: {},
|
|
153
136
|
}
|
|
154
137
|
|
|
155
138
|
|
|
@@ -167,37 +150,50 @@ DEFAULT_PRIORITY_MECH = { # maps mech marketplace address to its default priori
|
|
|
167
150
|
|
|
168
151
|
# ERC20 token addresses
|
|
169
152
|
OLAS: t.Dict[Chain, str] = {
|
|
170
|
-
Chain.
|
|
171
|
-
Chain.OPTIMISM: "0xFC2E6e6BCbd49ccf3A5f029c79984372DcBFE527",
|
|
153
|
+
Chain.ARBITRUM_ONE: "0x064F8B858C2A603e1b106a2039f5446D32dc81c1",
|
|
172
154
|
Chain.BASE: "0x54330d28ca3357F294334BDC454a032e7f353416",
|
|
155
|
+
Chain.CELO: "0xaCFfAe8e57Ec6E394Eb1b41939A8CF7892DbDc51",
|
|
173
156
|
Chain.ETHEREUM: "0x0001A500A6B18995B03f44bb040A5fFc28E45CB0",
|
|
157
|
+
Chain.GNOSIS: "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f",
|
|
174
158
|
Chain.MODE: "0xcfD1D50ce23C46D3Cf6407487B2F8934e96DC8f9",
|
|
159
|
+
Chain.OPTIMISM: "0xFC2E6e6BCbd49ccf3A5f029c79984372DcBFE527",
|
|
160
|
+
Chain.POLYGON: "0xFEF5d947472e72Efbb2E388c730B7428406F2F95",
|
|
175
161
|
}
|
|
176
162
|
|
|
177
163
|
USDC: t.Dict[Chain, str] = {
|
|
178
|
-
Chain.
|
|
179
|
-
Chain.OPTIMISM: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
164
|
+
Chain.ARBITRUM_ONE: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
180
165
|
Chain.BASE: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
166
|
+
Chain.CELO: "0xcebA9300f2b948710d2653dD7B07f33A8B32118C",
|
|
181
167
|
Chain.ETHEREUM: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
168
|
+
Chain.GNOSIS: "0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83",
|
|
182
169
|
Chain.MODE: "0xd988097fb8612cc24eeC14542bC03424c656005f",
|
|
170
|
+
Chain.OPTIMISM: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
171
|
+
Chain.POLYGON: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
183
172
|
}
|
|
184
173
|
|
|
185
174
|
WRAPPED_NATIVE_ASSET = {
|
|
186
|
-
Chain.
|
|
187
|
-
Chain.OPTIMISM: "0x4200000000000000000000000000000000000006",
|
|
175
|
+
Chain.ARBITRUM_ONE: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
188
176
|
Chain.BASE: "0x4200000000000000000000000000000000000006",
|
|
177
|
+
Chain.CELO: "0x471EcE3750Da237f93B8E339c536989b8978a438", # Dual token
|
|
189
178
|
Chain.ETHEREUM: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
179
|
+
Chain.GNOSIS: "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d",
|
|
190
180
|
Chain.MODE: "0x4200000000000000000000000000000000000006",
|
|
181
|
+
Chain.OPTIMISM: "0x4200000000000000000000000000000000000006",
|
|
191
182
|
Chain.POLYGON: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
|
|
192
|
-
Chain.ARBITRUM_ONE: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
193
183
|
}
|
|
194
184
|
|
|
195
185
|
ERC20_TOKENS = [OLAS, USDC, WRAPPED_NATIVE_ASSET]
|
|
196
186
|
|
|
197
187
|
DEFAULT_NEW_SAFE_FUNDS: t.Dict[Chain, t.Dict[str, int]] = {
|
|
188
|
+
Chain.ARBITRUM_ONE: {
|
|
189
|
+
ZERO_ADDRESS: int(1e15 / 4),
|
|
190
|
+
},
|
|
198
191
|
Chain.BASE: {
|
|
199
192
|
ZERO_ADDRESS: int(1e15 / 4),
|
|
200
193
|
},
|
|
194
|
+
Chain.CELO: {
|
|
195
|
+
ZERO_ADDRESS: int(1e18),
|
|
196
|
+
},
|
|
201
197
|
Chain.ETHEREUM: {
|
|
202
198
|
ZERO_ADDRESS: int(1e15 / 4),
|
|
203
199
|
},
|
|
@@ -210,21 +206,35 @@ DEFAULT_NEW_SAFE_FUNDS: t.Dict[Chain, t.Dict[str, int]] = {
|
|
|
210
206
|
Chain.OPTIMISM: {
|
|
211
207
|
ZERO_ADDRESS: int(1e15 / 4),
|
|
212
208
|
},
|
|
209
|
+
Chain.POLYGON: {
|
|
210
|
+
ZERO_ADDRESS: int(1e18),
|
|
211
|
+
},
|
|
213
212
|
}
|
|
214
213
|
|
|
215
214
|
DEFAULT_MASTER_EOA_FUNDS = {
|
|
215
|
+
Chain.ARBITRUM_ONE: {ZERO_ADDRESS: 5_000_000_000_000_000},
|
|
216
216
|
Chain.BASE: {ZERO_ADDRESS: 5_000_000_000_000_000},
|
|
217
|
+
Chain.CELO: {ZERO_ADDRESS: 1_500_000_000_000_000_000},
|
|
217
218
|
Chain.ETHEREUM: {ZERO_ADDRESS: 20_000_000_000_000_000},
|
|
218
219
|
Chain.GNOSIS: {ZERO_ADDRESS: 1_500_000_000_000_000_000},
|
|
219
220
|
Chain.MODE: {ZERO_ADDRESS: 500_000_000_000_000},
|
|
220
221
|
Chain.OPTIMISM: {ZERO_ADDRESS: 5_000_000_000_000_000},
|
|
222
|
+
Chain.POLYGON: {ZERO_ADDRESS: 1_500_000_000_000_000_000},
|
|
221
223
|
}
|
|
222
224
|
|
|
223
225
|
EXPLORER_URL = {
|
|
226
|
+
Chain.ARBITRUM_ONE: {
|
|
227
|
+
"tx": "https://arbiscan.io/tx/{tx_hash}",
|
|
228
|
+
"address": "https://arbiscan.io/address/{address}",
|
|
229
|
+
},
|
|
224
230
|
Chain.BASE: {
|
|
225
231
|
"tx": "https://basescan.org/tx/{tx_hash}",
|
|
226
232
|
"address": "https://basescan.org/address/{address}",
|
|
227
233
|
},
|
|
234
|
+
Chain.CELO: {
|
|
235
|
+
"tx": "https://celoscan.io/tx/{tx_hash}",
|
|
236
|
+
"address": "https://celoscan.io/address/{address}",
|
|
237
|
+
},
|
|
228
238
|
Chain.ETHEREUM: {
|
|
229
239
|
"tx": "https://etherscan.io/tx/{tx_hash}",
|
|
230
240
|
"address": "https://etherscan.io/address/{address}",
|
|
@@ -234,13 +244,17 @@ EXPLORER_URL = {
|
|
|
234
244
|
"address": "https://gnosisscan.io/address/{address}",
|
|
235
245
|
},
|
|
236
246
|
Chain.MODE: {
|
|
237
|
-
"tx": "https://
|
|
238
|
-
"address": "https://
|
|
247
|
+
"tx": "https://explorer.mode.network/tx/{tx_hash}",
|
|
248
|
+
"address": "https://explorer.mode.network/address/{address}",
|
|
239
249
|
},
|
|
240
250
|
Chain.OPTIMISM: {
|
|
241
251
|
"tx": "https://optimistic.etherscan.io/tx/{tx_hash}",
|
|
242
252
|
"address": "https://optimistic.etherscan.io/address/{address}",
|
|
243
253
|
},
|
|
254
|
+
Chain.POLYGON: {
|
|
255
|
+
"tx": "https://polygonscan.com/tx/{tx_hash}",
|
|
256
|
+
"address": "https://polygonscan.com/address/{address}",
|
|
257
|
+
},
|
|
244
258
|
}
|
|
245
259
|
|
|
246
260
|
|
operate/operate_types.py
CHANGED
operate/services/manage.py
CHANGED
|
@@ -23,6 +23,7 @@ import asyncio
|
|
|
23
23
|
import json
|
|
24
24
|
import logging
|
|
25
25
|
import os
|
|
26
|
+
import tempfile
|
|
26
27
|
import traceback
|
|
27
28
|
import typing as t
|
|
28
29
|
from collections import Counter, defaultdict
|
|
@@ -33,10 +34,11 @@ from pathlib import Path
|
|
|
33
34
|
|
|
34
35
|
import requests
|
|
35
36
|
from aea.helpers.base import IPFSHash
|
|
36
|
-
from aea_ledger_ethereum import LedgerApi
|
|
37
|
+
from aea_ledger_ethereum import EthereumCrypto, LedgerApi
|
|
37
38
|
from autonomy.chain.base import registry_contracts
|
|
38
39
|
from autonomy.chain.config import CHAIN_PROFILES, ChainType
|
|
39
40
|
from autonomy.chain.metadata import IPFS_URI_PREFIX
|
|
41
|
+
from web3 import Web3
|
|
40
42
|
|
|
41
43
|
from operate.constants import IPFS_ADDRESS, ZERO_ADDRESS
|
|
42
44
|
from operate.data import DATA_DIR
|
|
@@ -624,6 +626,8 @@ class ServiceManager:
|
|
|
624
626
|
# TODO fix this
|
|
625
627
|
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
626
628
|
|
|
629
|
+
self._enable_recovery_module(service_config_id=service_config_id, chain=chain)
|
|
630
|
+
|
|
627
631
|
current_agent_id = None
|
|
628
632
|
on_chain_state = OnChainState.NON_EXISTENT
|
|
629
633
|
if chain_data.token > -1:
|
|
@@ -829,6 +833,11 @@ class ServiceManager:
|
|
|
829
833
|
self._get_on_chain_state(service=service, chain=chain)
|
|
830
834
|
== OnChainState.PRE_REGISTRATION
|
|
831
835
|
):
|
|
836
|
+
self.logger.info("Execute recovery module operations")
|
|
837
|
+
self._execute_recovery_module_flow_from_safe(
|
|
838
|
+
service_config_id=service_config_id, chain=chain
|
|
839
|
+
)
|
|
840
|
+
|
|
832
841
|
self.logger.info("Updating service")
|
|
833
842
|
receipt = (
|
|
834
843
|
sftxb.new_tx()
|
|
@@ -1047,6 +1056,7 @@ class ServiceManager:
|
|
|
1047
1056
|
)
|
|
1048
1057
|
).settle()
|
|
1049
1058
|
|
|
1059
|
+
# Deploy service
|
|
1050
1060
|
if (
|
|
1051
1061
|
self._get_on_chain_state(service=service, chain=chain)
|
|
1052
1062
|
== OnChainState.FINISHED_REGISTRATION
|
|
@@ -1055,15 +1065,27 @@ class ServiceManager:
|
|
|
1055
1065
|
|
|
1056
1066
|
reuse_multisig = True
|
|
1057
1067
|
info = sftxb.info(token_id=chain_data.token)
|
|
1058
|
-
|
|
1068
|
+
service_safe_address = info["multisig"]
|
|
1069
|
+
if service_safe_address == ZERO_ADDRESS:
|
|
1059
1070
|
reuse_multisig = False
|
|
1060
1071
|
|
|
1061
1072
|
self.logger.info(f"{reuse_multisig=}")
|
|
1062
1073
|
|
|
1074
|
+
is_recovery_module_enabled = (
|
|
1075
|
+
registry_contracts.gnosis_safe.is_module_enabled(
|
|
1076
|
+
ledger_api=sftxb.ledger_api,
|
|
1077
|
+
contract_address=service_safe_address,
|
|
1078
|
+
module_address=CONTRACTS[Chain(chain)]["recovery_module"],
|
|
1079
|
+
).get("enabled")
|
|
1080
|
+
)
|
|
1081
|
+
|
|
1082
|
+
self.logger.info(f"{is_recovery_module_enabled=}")
|
|
1083
|
+
|
|
1063
1084
|
messages = sftxb.get_deploy_data_from_safe(
|
|
1064
1085
|
service_id=chain_data.token,
|
|
1065
1086
|
reuse_multisig=reuse_multisig,
|
|
1066
1087
|
master_safe=safe,
|
|
1088
|
+
use_recovery_module=is_recovery_module_enabled,
|
|
1067
1089
|
)
|
|
1068
1090
|
tx = sftxb.new_tx()
|
|
1069
1091
|
for message in messages:
|
|
@@ -1198,6 +1220,9 @@ class ServiceManager:
|
|
|
1198
1220
|
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1199
1221
|
safe = wallet.safes[Chain(chain)] # type: ignore
|
|
1200
1222
|
|
|
1223
|
+
if withdrawal_address:
|
|
1224
|
+
withdrawal_address = Web3.to_checksum_address(withdrawal_address)
|
|
1225
|
+
|
|
1201
1226
|
# TODO fixme
|
|
1202
1227
|
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
1203
1228
|
|
|
@@ -1289,6 +1314,9 @@ class ServiceManager:
|
|
|
1289
1314
|
},
|
|
1290
1315
|
)
|
|
1291
1316
|
|
|
1317
|
+
self._enable_recovery_module(
|
|
1318
|
+
service_config_id=service_config_id, chain=chain
|
|
1319
|
+
)
|
|
1292
1320
|
self.logger.info("Swapping Safe owners")
|
|
1293
1321
|
sftxb.swap( # noqa: E800
|
|
1294
1322
|
service_id=chain_data.token, # noqa: E800
|
|
@@ -1318,6 +1346,156 @@ class ServiceManager:
|
|
|
1318
1346
|
)
|
|
1319
1347
|
self.logger.info(f"{service.name} signer drained")
|
|
1320
1348
|
|
|
1349
|
+
def _execute_recovery_module_flow_from_safe( # pylint: disable=too-many-locals
|
|
1350
|
+
self,
|
|
1351
|
+
service_config_id: str,
|
|
1352
|
+
chain: str,
|
|
1353
|
+
) -> None:
|
|
1354
|
+
"""Execute recovery module operations from Safe"""
|
|
1355
|
+
self.logger.info(f"_execute_recovery_module_operations_from_safe {chain=}")
|
|
1356
|
+
service = self.load(service_config_id=service_config_id)
|
|
1357
|
+
chain_config = service.chain_configs[chain]
|
|
1358
|
+
chain_data = chain_config.chain_data
|
|
1359
|
+
ledger_config = chain_config.ledger_config
|
|
1360
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1361
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
1362
|
+
safe = wallet.safes[Chain(chain)]
|
|
1363
|
+
|
|
1364
|
+
if chain_data.token == NON_EXISTENT_TOKEN:
|
|
1365
|
+
self.logger.info("Service is not minted.")
|
|
1366
|
+
return
|
|
1367
|
+
|
|
1368
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
1369
|
+
service_safe_address = info["multisig"]
|
|
1370
|
+
on_chain_state = OnChainState(info["service_state"])
|
|
1371
|
+
|
|
1372
|
+
if service_safe_address == ZERO_ADDRESS:
|
|
1373
|
+
self.logger.info("Service Safe is not deployed.")
|
|
1374
|
+
return
|
|
1375
|
+
|
|
1376
|
+
recovery_module_address = CONTRACTS[Chain(chain)]["recovery_module"]
|
|
1377
|
+
is_recovery_module_enabled = registry_contracts.gnosis_safe.is_module_enabled(
|
|
1378
|
+
ledger_api=sftxb.ledger_api,
|
|
1379
|
+
contract_address=service_safe_address,
|
|
1380
|
+
module_address=recovery_module_address,
|
|
1381
|
+
).get("enabled")
|
|
1382
|
+
|
|
1383
|
+
service_safe_owners = sftxb.get_service_safe_owners(service_id=chain_data.token)
|
|
1384
|
+
master_safe_is_service_safe_owner = service_safe_owners == [safe]
|
|
1385
|
+
|
|
1386
|
+
self.logger.info(f"{is_recovery_module_enabled=}")
|
|
1387
|
+
self.logger.info(f"{master_safe_is_service_safe_owner=}")
|
|
1388
|
+
|
|
1389
|
+
if not is_recovery_module_enabled and not master_safe_is_service_safe_owner:
|
|
1390
|
+
self.logger.info(
|
|
1391
|
+
"Recovery module is not enabled and Master Safe is not service Safe owner. Skipping recovery operations."
|
|
1392
|
+
)
|
|
1393
|
+
return
|
|
1394
|
+
|
|
1395
|
+
if not is_recovery_module_enabled:
|
|
1396
|
+
self._enable_recovery_module(
|
|
1397
|
+
service_config_id=service_config_id, chain=chain
|
|
1398
|
+
)
|
|
1399
|
+
|
|
1400
|
+
if (
|
|
1401
|
+
not master_safe_is_service_safe_owner
|
|
1402
|
+
and on_chain_state == OnChainState.PRE_REGISTRATION
|
|
1403
|
+
):
|
|
1404
|
+
self.logger.info("Recovering service Safe access through recovery module.")
|
|
1405
|
+
sftxb.new_tx().add(
|
|
1406
|
+
sftxb.get_recover_access_data(
|
|
1407
|
+
service_id=chain_data.token,
|
|
1408
|
+
)
|
|
1409
|
+
).settle()
|
|
1410
|
+
self.logger.info("Recovering service Safe done.")
|
|
1411
|
+
|
|
1412
|
+
def _enable_recovery_module( # pylint: disable=too-many-locals
|
|
1413
|
+
self,
|
|
1414
|
+
service_config_id: str,
|
|
1415
|
+
chain: str,
|
|
1416
|
+
) -> None:
|
|
1417
|
+
"""Enable recovery module"""
|
|
1418
|
+
self.logger.info(f"_enable_recovery_module {chain=}")
|
|
1419
|
+
service = self.load(service_config_id=service_config_id)
|
|
1420
|
+
chain_config = service.chain_configs[chain]
|
|
1421
|
+
chain_data = chain_config.chain_data
|
|
1422
|
+
ledger_config = chain_config.ledger_config
|
|
1423
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1424
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
1425
|
+
safe = wallet.safes[Chain(chain)]
|
|
1426
|
+
|
|
1427
|
+
if chain_data.token == NON_EXISTENT_TOKEN:
|
|
1428
|
+
self.logger.info("Service is not minted.")
|
|
1429
|
+
return
|
|
1430
|
+
|
|
1431
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
1432
|
+
service_safe_address = info["multisig"]
|
|
1433
|
+
|
|
1434
|
+
if service_safe_address == ZERO_ADDRESS:
|
|
1435
|
+
self.logger.info("Service Safe is not deployed.")
|
|
1436
|
+
return
|
|
1437
|
+
|
|
1438
|
+
recovery_module_address = CONTRACTS[Chain(chain)]["recovery_module"]
|
|
1439
|
+
is_recovery_module_enabled = registry_contracts.gnosis_safe.is_module_enabled(
|
|
1440
|
+
ledger_api=sftxb.ledger_api,
|
|
1441
|
+
contract_address=service_safe_address,
|
|
1442
|
+
module_address=recovery_module_address,
|
|
1443
|
+
).get("enabled")
|
|
1444
|
+
|
|
1445
|
+
if is_recovery_module_enabled:
|
|
1446
|
+
self.logger.info("Recovery module is already enabled in service Safe.")
|
|
1447
|
+
return
|
|
1448
|
+
|
|
1449
|
+
self.logger.info("Recovery module is not enabled.")
|
|
1450
|
+
|
|
1451
|
+
# NOTE Recovery from agent only works for single-agent services
|
|
1452
|
+
agent_address = service.agent_addresses[0]
|
|
1453
|
+
service_safe_owners = sftxb.get_service_safe_owners(service_id=chain_data.token)
|
|
1454
|
+
agent_is_service_safe_owner = service_safe_owners == [agent_address]
|
|
1455
|
+
master_safe_is_service_safe_owner = service_safe_owners == [safe]
|
|
1456
|
+
|
|
1457
|
+
if agent_is_service_safe_owner:
|
|
1458
|
+
self.logger.info("(Agent) Enabling recovery module in service Safe.")
|
|
1459
|
+
try:
|
|
1460
|
+
with tempfile.NamedTemporaryFile(mode="w+", delete=True) as tmp_file:
|
|
1461
|
+
private_key = self.keys_manager.get(key=agent_address).private_key
|
|
1462
|
+
tmp_file.write(private_key)
|
|
1463
|
+
tmp_file.flush()
|
|
1464
|
+
crypto = EthereumCrypto(private_key_path=tmp_file.name)
|
|
1465
|
+
EthSafeTxBuilder._new_tx( # pylint: disable=protected-access
|
|
1466
|
+
ledger_api=sftxb.ledger_api,
|
|
1467
|
+
crypto=crypto,
|
|
1468
|
+
chain_type=ChainType(chain),
|
|
1469
|
+
safe=service_safe_address,
|
|
1470
|
+
).add(
|
|
1471
|
+
sftxb.get_enable_module_data(
|
|
1472
|
+
module_address=recovery_module_address,
|
|
1473
|
+
safe_address=service_safe_address,
|
|
1474
|
+
)
|
|
1475
|
+
).settle()
|
|
1476
|
+
tmp_file.seek(0)
|
|
1477
|
+
tmp_file.write("\0" * len(private_key))
|
|
1478
|
+
tmp_file.flush()
|
|
1479
|
+
|
|
1480
|
+
self.logger.info(
|
|
1481
|
+
"(Agent) Recovery module enabled successfully in service Safe."
|
|
1482
|
+
)
|
|
1483
|
+
except Exception as e: # pylint: disable=broad-except
|
|
1484
|
+
self.logger.error(
|
|
1485
|
+
f"Failed to enable recovery module in service Safe. Exception {e}: {traceback.format_exc()}"
|
|
1486
|
+
)
|
|
1487
|
+
elif master_safe_is_service_safe_owner:
|
|
1488
|
+
# TODO Enable recovery module when Safe owner = master Safe.
|
|
1489
|
+
# This should be similar to the above code, but
|
|
1490
|
+
# requires implement a transaction where the owner is another Safe.
|
|
1491
|
+
self.logger.info(
|
|
1492
|
+
"(Service owner) Enabling recovery module in service Safe. [Not implemented]"
|
|
1493
|
+
)
|
|
1494
|
+
else:
|
|
1495
|
+
self.logger.error(
|
|
1496
|
+
f"Cannot enable recovery module. Safe {service_safe_address} has inconsistent owners."
|
|
1497
|
+
)
|
|
1498
|
+
|
|
1321
1499
|
def _get_current_staking_program(
|
|
1322
1500
|
self, service: Service, chain: str
|
|
1323
1501
|
) -> t.Optional[str]:
|
|
@@ -2009,6 +2187,7 @@ class ServiceManager:
|
|
|
2009
2187
|
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
2010
2188
|
ledger_api = wallet.ledger_api(chain=ledger_config.chain, rpc=ledger_config.rpc)
|
|
2011
2189
|
ethereum_crypto = KeysManager().get_crypto_instance(service.agent_addresses[0])
|
|
2190
|
+
withdrawal_address = Web3.to_checksum_address(withdrawal_address)
|
|
2012
2191
|
|
|
2013
2192
|
# drain ERC20 tokens from service safe
|
|
2014
2193
|
for token_name, token_address in (
|