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
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# ------------------------------------------------------------------------------
|
|
4
|
+
#
|
|
5
|
+
# Copyright 2024 Valory AG
|
|
6
|
+
#
|
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
# you may not use this file except in compliance with the License.
|
|
9
|
+
# You may obtain a copy of the License at
|
|
10
|
+
#
|
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
#
|
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
# See the License for the specific language governing permissions and
|
|
17
|
+
# limitations under the License.
|
|
18
|
+
#
|
|
19
|
+
# ------------------------------------------------------------------------------
|
|
20
|
+
"""Native bridge provider."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
import time
|
|
25
|
+
import typing as t
|
|
26
|
+
from abc import ABC, abstractmethod
|
|
27
|
+
|
|
28
|
+
from aea.common import JSONLike
|
|
29
|
+
from aea.crypto.base import LedgerApi
|
|
30
|
+
from autonomy.chain.base import registry_contracts
|
|
31
|
+
from eth_typing import BlockIdentifier
|
|
32
|
+
from web3 import Web3
|
|
33
|
+
|
|
34
|
+
from operate.bridge.providers.provider import (
|
|
35
|
+
MESSAGE_EXECUTION_FAILED,
|
|
36
|
+
MESSAGE_EXECUTION_FAILED_ETA,
|
|
37
|
+
MESSAGE_EXECUTION_FAILED_REVERTED,
|
|
38
|
+
MESSAGE_QUOTE_ZERO,
|
|
39
|
+
Provider,
|
|
40
|
+
ProviderRequest,
|
|
41
|
+
ProviderRequestStatus,
|
|
42
|
+
QuoteData,
|
|
43
|
+
)
|
|
44
|
+
from operate.constants import ZERO_ADDRESS
|
|
45
|
+
from operate.data import DATA_DIR
|
|
46
|
+
from operate.data.contracts.foreign_omnibridge.contract import ForeignOmnibridge
|
|
47
|
+
from operate.data.contracts.home_omnibridge.contract import HomeOmnibridge
|
|
48
|
+
from operate.data.contracts.l1_standard_bridge.contract import (
|
|
49
|
+
DEFAULT_BRIDGE_MIN_GAS_LIMIT,
|
|
50
|
+
L1StandardBridge,
|
|
51
|
+
)
|
|
52
|
+
from operate.data.contracts.l2_standard_bridge.contract import L2StandardBridge
|
|
53
|
+
from operate.data.contracts.optimism_mintable_erc20.contract import (
|
|
54
|
+
OptimismMintableERC20,
|
|
55
|
+
)
|
|
56
|
+
from operate.ledger import (
|
|
57
|
+
get_default_ledger_api,
|
|
58
|
+
update_tx_with_gas_estimate,
|
|
59
|
+
update_tx_with_gas_pricing,
|
|
60
|
+
)
|
|
61
|
+
from operate.ledger.profiles import ERC20_TOKENS, EXPLORER_URL
|
|
62
|
+
from operate.operate_types import Chain
|
|
63
|
+
from operate.wallet.master import MasterWalletManager
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
BLOCK_CHUNK_SIZE = 5000
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class BridgeContractAdaptor(ABC):
|
|
70
|
+
"""Adaptor class for bridge contract packages."""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
from_chain: str,
|
|
75
|
+
from_bridge: str,
|
|
76
|
+
to_chain: str,
|
|
77
|
+
to_bridge: str,
|
|
78
|
+
bridge_eta: int,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Initialize the bridge contract adaptor."""
|
|
81
|
+
super().__init__()
|
|
82
|
+
if from_chain == to_chain:
|
|
83
|
+
raise ValueError("from_chain and to_chain cannot be the same.")
|
|
84
|
+
self.from_chain = from_chain
|
|
85
|
+
self.from_bridge = from_bridge
|
|
86
|
+
self.to_chain = to_chain
|
|
87
|
+
self.to_bridge = to_bridge
|
|
88
|
+
self.bridge_eta = bridge_eta
|
|
89
|
+
|
|
90
|
+
def can_handle_request(self, params: t.Dict) -> bool:
|
|
91
|
+
"""Returns 'true' if the contract adaptor can handle a request for 'params'."""
|
|
92
|
+
from_chain = params["from"]["chain"]
|
|
93
|
+
from_token = Web3.to_checksum_address(params["from"]["token"])
|
|
94
|
+
to_chain = params["to"]["chain"]
|
|
95
|
+
to_token = Web3.to_checksum_address(params["to"]["token"])
|
|
96
|
+
|
|
97
|
+
if from_chain != self.from_chain:
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
if to_chain != self.to_chain:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
if from_token == ZERO_ADDRESS and to_token == ZERO_ADDRESS:
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
for token_map in ERC20_TOKENS.values():
|
|
107
|
+
if (
|
|
108
|
+
Chain(from_chain) in token_map
|
|
109
|
+
and Chain(to_chain) in token_map
|
|
110
|
+
and token_map[Chain(from_chain)].lower() == from_token.lower()
|
|
111
|
+
and token_map[Chain(to_chain)].lower() == to_token.lower()
|
|
112
|
+
):
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def build_bridge_tx(
|
|
119
|
+
self, from_ledger_api: LedgerApi, provider_request: ProviderRequest
|
|
120
|
+
) -> JSONLike:
|
|
121
|
+
"""Build bridge transaction."""
|
|
122
|
+
raise NotImplementedError()
|
|
123
|
+
|
|
124
|
+
@abstractmethod
|
|
125
|
+
def find_bridge_finalized_tx(
|
|
126
|
+
self,
|
|
127
|
+
from_ledger_api: LedgerApi,
|
|
128
|
+
to_ledger_api: LedgerApi,
|
|
129
|
+
provider_request: ProviderRequest,
|
|
130
|
+
from_block: BlockIdentifier,
|
|
131
|
+
to_block: BlockIdentifier,
|
|
132
|
+
) -> t.Optional[str]:
|
|
133
|
+
"""Return the transaction hash of the event indicating bridge completion."""
|
|
134
|
+
raise NotImplementedError()
|
|
135
|
+
|
|
136
|
+
@abstractmethod
|
|
137
|
+
def get_explorer_link(
|
|
138
|
+
self, from_ledger_api: LedgerApi, provider_request: ProviderRequest
|
|
139
|
+
) -> t.Optional[str]:
|
|
140
|
+
"""Get the explorer link for a transaction."""
|
|
141
|
+
raise NotImplementedError()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class OptimismContractAdaptor(BridgeContractAdaptor):
|
|
145
|
+
"""Adaptor class for Optimism contract packages."""
|
|
146
|
+
|
|
147
|
+
_l1_standard_bridge_contract = t.cast(
|
|
148
|
+
L1StandardBridge,
|
|
149
|
+
L1StandardBridge.from_dir(
|
|
150
|
+
directory=str(DATA_DIR / "contracts" / "l1_standard_bridge"),
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
_l2_standard_bridge_contract = t.cast(
|
|
154
|
+
L2StandardBridge,
|
|
155
|
+
L2StandardBridge.from_dir(
|
|
156
|
+
directory=str(DATA_DIR / "contracts" / "l2_standard_bridge"),
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
_optimism_mintable_erc20_contract = t.cast(
|
|
161
|
+
OptimismMintableERC20,
|
|
162
|
+
OptimismMintableERC20.from_dir(
|
|
163
|
+
directory=str(DATA_DIR / "contracts" / "optimism_mintable_erc20"),
|
|
164
|
+
),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def can_handle_request(self, params: t.Dict) -> bool:
|
|
168
|
+
"""Returns 'true' if the contract adaptor can handle a request for 'params'."""
|
|
169
|
+
|
|
170
|
+
if not super().can_handle_request(params):
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
from_chain = params["from"]["chain"]
|
|
174
|
+
from_token = Web3.to_checksum_address(params["from"]["token"])
|
|
175
|
+
from_ledger_api = get_default_ledger_api(Chain(from_chain))
|
|
176
|
+
to_chain = params["to"]["chain"]
|
|
177
|
+
to_token = Web3.to_checksum_address(params["to"]["token"])
|
|
178
|
+
to_ledger_api = get_default_ledger_api(Chain(to_chain))
|
|
179
|
+
|
|
180
|
+
if from_token == ZERO_ADDRESS and to_token == ZERO_ADDRESS:
|
|
181
|
+
if not self._l1_standard_bridge_contract.supports_bridge_eth_to(
|
|
182
|
+
ledger_api=from_ledger_api, contract_address=self.from_bridge
|
|
183
|
+
):
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
if to_token != ZERO_ADDRESS:
|
|
187
|
+
try:
|
|
188
|
+
l1_token = self._optimism_mintable_erc20_contract.l1_token(
|
|
189
|
+
ledger_api=to_ledger_api,
|
|
190
|
+
contract_address=to_token,
|
|
191
|
+
)["data"]
|
|
192
|
+
|
|
193
|
+
if l1_token != from_token:
|
|
194
|
+
return False
|
|
195
|
+
except Exception: # pylint: disable=broad-except
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
def build_bridge_tx(
|
|
201
|
+
self, from_ledger_api: LedgerApi, provider_request: ProviderRequest
|
|
202
|
+
) -> JSONLike:
|
|
203
|
+
"""Build bridge transaction."""
|
|
204
|
+
from_address = provider_request.params["from"]["address"]
|
|
205
|
+
from_token = provider_request.params["from"]["token"]
|
|
206
|
+
to_address = provider_request.params["to"]["address"]
|
|
207
|
+
to_token = provider_request.params["to"]["token"]
|
|
208
|
+
to_amount = provider_request.params["to"]["amount"]
|
|
209
|
+
from_bridge = self.from_bridge
|
|
210
|
+
extra_data = Web3.keccak(text=provider_request.id)
|
|
211
|
+
|
|
212
|
+
if from_token == ZERO_ADDRESS:
|
|
213
|
+
return self._l1_standard_bridge_contract.build_bridge_eth_to_tx(
|
|
214
|
+
ledger_api=from_ledger_api,
|
|
215
|
+
contract_address=from_bridge,
|
|
216
|
+
sender=from_address,
|
|
217
|
+
to=to_address,
|
|
218
|
+
amount=int(to_amount),
|
|
219
|
+
min_gas_limit=DEFAULT_BRIDGE_MIN_GAS_LIMIT,
|
|
220
|
+
extra_data=extra_data,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return self._l1_standard_bridge_contract.build_bridge_erc20_to_tx(
|
|
224
|
+
ledger_api=from_ledger_api,
|
|
225
|
+
contract_address=from_bridge,
|
|
226
|
+
sender=from_address,
|
|
227
|
+
local_token=from_token,
|
|
228
|
+
remote_token=to_token,
|
|
229
|
+
to=to_address,
|
|
230
|
+
amount=int(to_amount),
|
|
231
|
+
min_gas_limit=DEFAULT_BRIDGE_MIN_GAS_LIMIT,
|
|
232
|
+
extra_data=extra_data,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def find_bridge_finalized_tx(
|
|
236
|
+
self,
|
|
237
|
+
from_ledger_api: LedgerApi,
|
|
238
|
+
to_ledger_api: LedgerApi,
|
|
239
|
+
provider_request: ProviderRequest,
|
|
240
|
+
from_block: BlockIdentifier,
|
|
241
|
+
to_block: BlockIdentifier,
|
|
242
|
+
) -> t.Optional[str]:
|
|
243
|
+
"""Return the transaction hash of the event indicating bridge completion."""
|
|
244
|
+
from_address = provider_request.params["from"]["address"]
|
|
245
|
+
from_token = provider_request.params["from"]["token"]
|
|
246
|
+
to_address = provider_request.params["to"]["address"]
|
|
247
|
+
to_token = provider_request.params["to"]["token"]
|
|
248
|
+
to_amount = provider_request.params["to"]["amount"]
|
|
249
|
+
to_bridge = self.to_bridge
|
|
250
|
+
extra_data = Web3.keccak(text=provider_request.id)
|
|
251
|
+
|
|
252
|
+
if from_token == ZERO_ADDRESS:
|
|
253
|
+
return self._l2_standard_bridge_contract.find_eth_bridge_finalized_tx(
|
|
254
|
+
ledger_api=to_ledger_api,
|
|
255
|
+
contract_address=to_bridge,
|
|
256
|
+
from_=from_address,
|
|
257
|
+
to=to_address,
|
|
258
|
+
amount=to_amount,
|
|
259
|
+
extra_data=extra_data,
|
|
260
|
+
from_block=from_block,
|
|
261
|
+
to_block=to_block,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return self._l2_standard_bridge_contract.find_erc20_bridge_finalized_tx(
|
|
265
|
+
ledger_api=to_ledger_api,
|
|
266
|
+
contract_address=to_bridge,
|
|
267
|
+
local_token=to_token,
|
|
268
|
+
remote_token=from_token,
|
|
269
|
+
from_=from_address,
|
|
270
|
+
to=to_address,
|
|
271
|
+
amount=to_amount,
|
|
272
|
+
extra_data=extra_data,
|
|
273
|
+
from_block=from_block,
|
|
274
|
+
to_block=to_block,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def get_explorer_link(
|
|
278
|
+
self, from_ledger_api: LedgerApi, provider_request: ProviderRequest
|
|
279
|
+
) -> t.Optional[str]:
|
|
280
|
+
"""Get the explorer link for a transaction."""
|
|
281
|
+
if not provider_request.execution_data:
|
|
282
|
+
return None
|
|
283
|
+
|
|
284
|
+
tx_hash = provider_request.execution_data.from_tx_hash
|
|
285
|
+
if not tx_hash:
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
chain = Chain(provider_request.params["from"]["chain"])
|
|
289
|
+
url = EXPLORER_URL[chain]["tx"]
|
|
290
|
+
return url.format(tx_hash=tx_hash)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class OmnibridgeContractAdaptor(BridgeContractAdaptor):
|
|
294
|
+
"""Adaptor class for Omnibridge contract packages."""
|
|
295
|
+
|
|
296
|
+
_foreign_omnibridge = t.cast(
|
|
297
|
+
ForeignOmnibridge,
|
|
298
|
+
ForeignOmnibridge.from_dir(
|
|
299
|
+
directory=str(DATA_DIR / "contracts" / "foreign_omnibridge"),
|
|
300
|
+
),
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
_home_omnibridge = t.cast(
|
|
304
|
+
HomeOmnibridge,
|
|
305
|
+
HomeOmnibridge.from_dir(
|
|
306
|
+
directory=str(DATA_DIR / "contracts" / "home_omnibridge"),
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
def can_handle_request(self, params: t.Dict) -> bool:
|
|
311
|
+
"""Returns 'true' if the contract adaptor can handle a request for 'params'."""
|
|
312
|
+
from_token = Web3.to_checksum_address(params["from"]["token"])
|
|
313
|
+
if from_token == ZERO_ADDRESS:
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
return super().can_handle_request(params)
|
|
317
|
+
|
|
318
|
+
def build_bridge_tx(
|
|
319
|
+
self, from_ledger_api: LedgerApi, provider_request: ProviderRequest
|
|
320
|
+
) -> JSONLike:
|
|
321
|
+
"""Build bridge transaction."""
|
|
322
|
+
from_address = provider_request.params["from"]["address"]
|
|
323
|
+
from_token = provider_request.params["from"]["token"]
|
|
324
|
+
to_address = provider_request.params["to"]["address"]
|
|
325
|
+
to_amount = provider_request.params["to"]["amount"]
|
|
326
|
+
from_bridge = self.from_bridge
|
|
327
|
+
|
|
328
|
+
if from_token == ZERO_ADDRESS:
|
|
329
|
+
raise NotImplementedError(
|
|
330
|
+
f"{self.__class__.__name__} does not support bridge native tokens."
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
return self._foreign_omnibridge.build_relay_tokens_tx(
|
|
334
|
+
ledger_api=from_ledger_api,
|
|
335
|
+
contract_address=from_bridge,
|
|
336
|
+
sender=from_address,
|
|
337
|
+
token=from_token,
|
|
338
|
+
receiver=to_address,
|
|
339
|
+
amount=to_amount,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def find_bridge_finalized_tx(
|
|
343
|
+
self,
|
|
344
|
+
from_ledger_api: LedgerApi,
|
|
345
|
+
to_ledger_api: LedgerApi,
|
|
346
|
+
provider_request: ProviderRequest,
|
|
347
|
+
from_block: BlockIdentifier,
|
|
348
|
+
to_block: BlockIdentifier,
|
|
349
|
+
) -> t.Optional[str]:
|
|
350
|
+
"""Return the transaction hash of the event indicating bridge completion."""
|
|
351
|
+
from_token = provider_request.params["from"]["token"]
|
|
352
|
+
to_address = provider_request.params["to"]["address"]
|
|
353
|
+
to_token = provider_request.params["to"]["token"]
|
|
354
|
+
to_amount = provider_request.params["to"]["amount"]
|
|
355
|
+
to_bridge = self.to_bridge
|
|
356
|
+
|
|
357
|
+
if from_token == ZERO_ADDRESS:
|
|
358
|
+
raise NotImplementedError(
|
|
359
|
+
f"{self.__class__.__name__} does not support bridge native tokens."
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
message_id = self.get_message_id(
|
|
363
|
+
from_ledger_api=from_ledger_api,
|
|
364
|
+
provider_request=provider_request,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
if not message_id:
|
|
368
|
+
raise RuntimeError(
|
|
369
|
+
f"Cannot find 'messageId' for request {provider_request.id}."
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
return self._home_omnibridge.find_tokens_bridged_tx(
|
|
373
|
+
ledger_api=to_ledger_api,
|
|
374
|
+
contract_address=to_bridge,
|
|
375
|
+
token=to_token,
|
|
376
|
+
recipient=to_address,
|
|
377
|
+
value=to_amount,
|
|
378
|
+
message_id=message_id,
|
|
379
|
+
from_block=from_block,
|
|
380
|
+
to_block=to_block,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def get_message_id(
|
|
384
|
+
self, from_ledger_api: LedgerApi, provider_request: ProviderRequest
|
|
385
|
+
) -> t.Optional[str]:
|
|
386
|
+
"""Get the bridge message id."""
|
|
387
|
+
if not provider_request.execution_data:
|
|
388
|
+
return None
|
|
389
|
+
|
|
390
|
+
if not provider_request.execution_data.from_tx_hash:
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
if (
|
|
394
|
+
provider_request.execution_data.provider_data
|
|
395
|
+
and "message_id" in provider_request.execution_data.provider_data
|
|
396
|
+
):
|
|
397
|
+
return provider_request.execution_data.provider_data.get("message_id", None)
|
|
398
|
+
|
|
399
|
+
from_address = provider_request.params["from"]["address"]
|
|
400
|
+
from_token = provider_request.params["from"]["token"]
|
|
401
|
+
from_tx_hash = provider_request.execution_data.from_tx_hash
|
|
402
|
+
to_amount = provider_request.params["to"]["amount"]
|
|
403
|
+
from_bridge = self.from_bridge
|
|
404
|
+
|
|
405
|
+
message_id = self._foreign_omnibridge.get_tokens_bridging_initiated_message_id(
|
|
406
|
+
ledger_api=from_ledger_api,
|
|
407
|
+
contract_address=from_bridge,
|
|
408
|
+
tx_hash=from_tx_hash,
|
|
409
|
+
token=from_token,
|
|
410
|
+
sender=from_address,
|
|
411
|
+
value=to_amount,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if not provider_request.execution_data.provider_data:
|
|
415
|
+
provider_request.execution_data.provider_data = {}
|
|
416
|
+
|
|
417
|
+
provider_request.execution_data.provider_data["message_id"] = message_id
|
|
418
|
+
return message_id
|
|
419
|
+
|
|
420
|
+
def get_explorer_link(
|
|
421
|
+
self, from_ledger_api: LedgerApi, provider_request: ProviderRequest
|
|
422
|
+
) -> t.Optional[str]:
|
|
423
|
+
"""Get the explorer link for a transaction."""
|
|
424
|
+
message_id = self.get_message_id(from_ledger_api, provider_request)
|
|
425
|
+
if not message_id:
|
|
426
|
+
return None
|
|
427
|
+
return (
|
|
428
|
+
f"https://bridge.gnosischain.com/bridge-explorer/transaction/{message_id}"
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class NativeBridgeProvider(Provider):
|
|
433
|
+
"""Native bridge provider"""
|
|
434
|
+
|
|
435
|
+
def __init__(
|
|
436
|
+
self,
|
|
437
|
+
bridge_contract_adaptor: BridgeContractAdaptor,
|
|
438
|
+
provider_id: str,
|
|
439
|
+
wallet_manager: MasterWalletManager,
|
|
440
|
+
logger: logging.Logger,
|
|
441
|
+
) -> None:
|
|
442
|
+
"""Initialize the provider."""
|
|
443
|
+
self.bridge_contract_adaptor = bridge_contract_adaptor
|
|
444
|
+
super().__init__(
|
|
445
|
+
wallet_manager=wallet_manager, provider_id=provider_id, logger=logger
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
def can_handle_request(self, params: t.Dict) -> bool:
|
|
449
|
+
"""Returns 'true' if the provider can handle a request for 'params'."""
|
|
450
|
+
if not super().can_handle_request(params):
|
|
451
|
+
return False
|
|
452
|
+
|
|
453
|
+
if not self.bridge_contract_adaptor.can_handle_request(params):
|
|
454
|
+
return False
|
|
455
|
+
|
|
456
|
+
return True
|
|
457
|
+
|
|
458
|
+
def description(self) -> str:
|
|
459
|
+
"""Get a human-readable description of the provider."""
|
|
460
|
+
return f"Native bridge provider ({self.bridge_contract_adaptor.__class__.__name__})."
|
|
461
|
+
|
|
462
|
+
def quote(self, provider_request: ProviderRequest) -> None:
|
|
463
|
+
"""Update the request with the quote."""
|
|
464
|
+
self._validate(provider_request)
|
|
465
|
+
|
|
466
|
+
if provider_request.status not in (
|
|
467
|
+
ProviderRequestStatus.CREATED,
|
|
468
|
+
ProviderRequestStatus.QUOTE_DONE,
|
|
469
|
+
ProviderRequestStatus.QUOTE_FAILED,
|
|
470
|
+
):
|
|
471
|
+
raise RuntimeError(
|
|
472
|
+
f"Cannot quote request {provider_request.id} with status {provider_request.status}."
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
if provider_request.execution_data:
|
|
476
|
+
raise RuntimeError(
|
|
477
|
+
f"Cannot quote request {provider_request.id}: execution already present."
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
to_amount = provider_request.params["to"]["amount"]
|
|
481
|
+
bridge_eta = self.bridge_contract_adaptor.bridge_eta
|
|
482
|
+
|
|
483
|
+
message = None
|
|
484
|
+
if to_amount == 0:
|
|
485
|
+
self.logger.info(f"[NATIVE BRIDGE PROVIDER] {MESSAGE_QUOTE_ZERO}")
|
|
486
|
+
bridge_eta = 0
|
|
487
|
+
message = MESSAGE_QUOTE_ZERO
|
|
488
|
+
|
|
489
|
+
quote_data = QuoteData(
|
|
490
|
+
eta=bridge_eta,
|
|
491
|
+
elapsed_time=0,
|
|
492
|
+
message=message,
|
|
493
|
+
provider_data=None,
|
|
494
|
+
timestamp=int(time.time()),
|
|
495
|
+
)
|
|
496
|
+
provider_request.quote_data = quote_data
|
|
497
|
+
provider_request.status = ProviderRequestStatus.QUOTE_DONE
|
|
498
|
+
|
|
499
|
+
def _get_approve_tx(self, provider_request: ProviderRequest) -> t.Optional[t.Dict]:
|
|
500
|
+
"""Get the approve transaction."""
|
|
501
|
+
self.logger.info(
|
|
502
|
+
f"[NATIVE BRIDGE PROVIDER] Get appprove transaction for request {provider_request.id}."
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
if provider_request.params["to"]["amount"] == 0:
|
|
506
|
+
return None
|
|
507
|
+
|
|
508
|
+
quote_data = provider_request.quote_data
|
|
509
|
+
if not quote_data:
|
|
510
|
+
return None
|
|
511
|
+
|
|
512
|
+
from_address = provider_request.params["from"]["address"]
|
|
513
|
+
from_token = provider_request.params["from"]["token"]
|
|
514
|
+
to_amount = provider_request.params["to"]["amount"]
|
|
515
|
+
from_bridge = self.bridge_contract_adaptor.from_bridge
|
|
516
|
+
from_ledger_api = self._from_ledger_api(provider_request)
|
|
517
|
+
|
|
518
|
+
if from_token == ZERO_ADDRESS:
|
|
519
|
+
return None
|
|
520
|
+
|
|
521
|
+
approve_tx = registry_contracts.erc20.get_approve_tx(
|
|
522
|
+
ledger_api=from_ledger_api,
|
|
523
|
+
contract_address=from_token,
|
|
524
|
+
spender=from_bridge,
|
|
525
|
+
sender=from_address,
|
|
526
|
+
amount=to_amount,
|
|
527
|
+
)
|
|
528
|
+
approve_tx["gas"] = 200_000 # TODO backport to ERC20 contract as default
|
|
529
|
+
update_tx_with_gas_pricing(approve_tx, from_ledger_api)
|
|
530
|
+
update_tx_with_gas_estimate(approve_tx, from_ledger_api)
|
|
531
|
+
return approve_tx
|
|
532
|
+
|
|
533
|
+
def _get_bridge_tx(self, provider_request: ProviderRequest) -> t.Optional[t.Dict]:
|
|
534
|
+
"""Get the bridge transaction."""
|
|
535
|
+
self.logger.info(
|
|
536
|
+
f"[NATIVE BRIDGE PROVIDER] Get bridge transaction for request {provider_request.id}."
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
if provider_request.params["to"]["amount"] == 0:
|
|
540
|
+
return None
|
|
541
|
+
|
|
542
|
+
quote_data = provider_request.quote_data
|
|
543
|
+
if not quote_data:
|
|
544
|
+
return None
|
|
545
|
+
|
|
546
|
+
from_ledger_api = self._from_ledger_api(provider_request)
|
|
547
|
+
bridge_tx = self.bridge_contract_adaptor.build_bridge_tx(
|
|
548
|
+
from_ledger_api=from_ledger_api, provider_request=provider_request
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
update_tx_with_gas_pricing(bridge_tx, from_ledger_api)
|
|
552
|
+
update_tx_with_gas_estimate(bridge_tx, from_ledger_api)
|
|
553
|
+
return bridge_tx
|
|
554
|
+
|
|
555
|
+
def _get_txs(
|
|
556
|
+
self, provider_request: ProviderRequest, *args: t.Any, **kwargs: t.Any
|
|
557
|
+
) -> t.List[t.Tuple[str, t.Dict]]:
|
|
558
|
+
"""Get the sorted list of transactions to execute the quote."""
|
|
559
|
+
txs = []
|
|
560
|
+
approve_tx = self._get_approve_tx(provider_request)
|
|
561
|
+
if approve_tx:
|
|
562
|
+
txs.append(("approve_tx", approve_tx))
|
|
563
|
+
bridge_tx = self._get_bridge_tx(provider_request)
|
|
564
|
+
if bridge_tx:
|
|
565
|
+
txs.append(("bridge_tx", bridge_tx))
|
|
566
|
+
return txs
|
|
567
|
+
|
|
568
|
+
def _update_execution_status(self, provider_request: ProviderRequest) -> None:
|
|
569
|
+
"""Update the execution status."""
|
|
570
|
+
|
|
571
|
+
if provider_request.status != ProviderRequestStatus.EXECUTION_PENDING:
|
|
572
|
+
return
|
|
573
|
+
|
|
574
|
+
self.logger.info(
|
|
575
|
+
f"[NATIVE BRIDGE PROVIDER] Updating execution status for request {provider_request.id}."
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
execution_data = provider_request.execution_data
|
|
579
|
+
if not execution_data:
|
|
580
|
+
raise RuntimeError(
|
|
581
|
+
f"Cannot update {provider_request.id}: execution data not present."
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
from_tx_hash = execution_data.from_tx_hash
|
|
585
|
+
if not from_tx_hash:
|
|
586
|
+
execution_data.message = (
|
|
587
|
+
f"{MESSAGE_EXECUTION_FAILED} missing transaction hash."
|
|
588
|
+
)
|
|
589
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_FAILED
|
|
590
|
+
return
|
|
591
|
+
|
|
592
|
+
bridge_eta = self.bridge_contract_adaptor.bridge_eta
|
|
593
|
+
|
|
594
|
+
try:
|
|
595
|
+
from_ledger_api = self._from_ledger_api(provider_request)
|
|
596
|
+
from_w3 = from_ledger_api.api
|
|
597
|
+
|
|
598
|
+
receipt = from_w3.eth.get_transaction_receipt(from_tx_hash)
|
|
599
|
+
if receipt.status == 0:
|
|
600
|
+
execution_data.message = MESSAGE_EXECUTION_FAILED_REVERTED
|
|
601
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_FAILED
|
|
602
|
+
return
|
|
603
|
+
|
|
604
|
+
# Get the timestamp of the bridge_tx on the 'from' chain
|
|
605
|
+
bridge_tx_receipt = from_w3.eth.get_transaction_receipt(from_tx_hash)
|
|
606
|
+
bridge_tx_block = from_w3.eth.get_block(bridge_tx_receipt.blockNumber)
|
|
607
|
+
bridge_tx_ts = bridge_tx_block.timestamp
|
|
608
|
+
|
|
609
|
+
# Find the event on the 'to' chain
|
|
610
|
+
to_ledger_api = self._to_ledger_api(provider_request)
|
|
611
|
+
to_w3 = to_ledger_api.api
|
|
612
|
+
starting_block = self._find_block_before_timestamp(to_w3, bridge_tx_ts)
|
|
613
|
+
starting_block_ts = to_w3.eth.get_block(starting_block).timestamp
|
|
614
|
+
latest_block = to_w3.eth.block_number
|
|
615
|
+
|
|
616
|
+
for from_block in range(starting_block, latest_block + 1, BLOCK_CHUNK_SIZE):
|
|
617
|
+
to_block = min(from_block + BLOCK_CHUNK_SIZE - 1, latest_block)
|
|
618
|
+
|
|
619
|
+
to_tx_hash = self.bridge_contract_adaptor.find_bridge_finalized_tx(
|
|
620
|
+
from_ledger_api=from_ledger_api,
|
|
621
|
+
to_ledger_api=to_ledger_api,
|
|
622
|
+
provider_request=provider_request,
|
|
623
|
+
from_block=from_block,
|
|
624
|
+
to_block=to_block,
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
if to_tx_hash:
|
|
628
|
+
self.logger.info(
|
|
629
|
+
f"[NATIVE BRIDGE PROVIDER] Execution done for request {provider_request.id}."
|
|
630
|
+
)
|
|
631
|
+
execution_data.message = None
|
|
632
|
+
execution_data.to_tx_hash = to_tx_hash
|
|
633
|
+
execution_data.elapsed_time = Provider._tx_timestamp(
|
|
634
|
+
to_tx_hash, to_ledger_api
|
|
635
|
+
) - Provider._tx_timestamp(from_tx_hash, from_ledger_api)
|
|
636
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_DONE
|
|
637
|
+
return
|
|
638
|
+
|
|
639
|
+
last_block_ts = to_w3.eth.get_block(to_block).timestamp
|
|
640
|
+
if last_block_ts > starting_block_ts + bridge_eta * 2:
|
|
641
|
+
self.logger.info(
|
|
642
|
+
f"[NATIVE BRIDGE PROVIDER] Execution failed for request {provider_request.id}: bridge exceeds 2*ETA."
|
|
643
|
+
)
|
|
644
|
+
execution_data.message = MESSAGE_EXECUTION_FAILED_ETA
|
|
645
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_FAILED
|
|
646
|
+
return
|
|
647
|
+
|
|
648
|
+
except Exception as e:
|
|
649
|
+
self.logger.error(f"Error updating execution status: {e}")
|
|
650
|
+
import traceback
|
|
651
|
+
|
|
652
|
+
traceback.print_exc()
|
|
653
|
+
execution_data.message = f"{MESSAGE_EXECUTION_FAILED} {str(e)}"
|
|
654
|
+
provider_request.status = ProviderRequestStatus.EXECUTION_FAILED
|
|
655
|
+
|
|
656
|
+
@staticmethod
|
|
657
|
+
def _find_block_before_timestamp(w3: Web3, timestamp: int) -> int:
|
|
658
|
+
"""Returns the largest block number of the block before `timestamp`."""
|
|
659
|
+
latest = w3.eth.block_number
|
|
660
|
+
low, high = 0, latest
|
|
661
|
+
best = 0
|
|
662
|
+
while low <= high:
|
|
663
|
+
mid = (low + high) // 2
|
|
664
|
+
block = w3.eth.get_block(mid)
|
|
665
|
+
if block["timestamp"] < timestamp:
|
|
666
|
+
best = mid
|
|
667
|
+
low = mid + 1
|
|
668
|
+
else:
|
|
669
|
+
high = mid - 1
|
|
670
|
+
return best
|
|
671
|
+
|
|
672
|
+
def _get_explorer_link(self, provider_request: ProviderRequest) -> t.Optional[str]:
|
|
673
|
+
"""Get the explorer link for a transaction."""
|
|
674
|
+
from_ledger_api = self._from_ledger_api(provider_request)
|
|
675
|
+
return self.bridge_contract_adaptor.get_explorer_link(
|
|
676
|
+
from_ledger_api=from_ledger_api, provider_request=provider_request
|
|
677
|
+
)
|