olas-operate-middleware 0.10.20__py3-none-any.whl → 0.11.1__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.20.dist-info → olas_operate_middleware-0.11.1.dist-info}/METADATA +3 -1
- {olas_operate_middleware-0.10.20.dist-info → olas_operate_middleware-0.11.1.dist-info}/RECORD +25 -22
- operate/bridge/bridge_manager.py +10 -12
- operate/bridge/providers/native_bridge_provider.py +1 -1
- operate/bridge/providers/provider.py +21 -34
- operate/cli.py +438 -108
- operate/constants.py +20 -0
- operate/ledger/__init__.py +55 -5
- operate/ledger/profiles.py +81 -11
- operate/operate_types.py +205 -2
- operate/quickstart/run_service.py +3 -1
- operate/services/agent_runner.py +5 -3
- operate/services/deployment_runner.py +3 -0
- operate/services/funding_manager.py +904 -0
- operate/services/manage.py +165 -306
- operate/services/protocol.py +392 -140
- operate/services/service.py +81 -5
- operate/settings.py +70 -0
- operate/utils/gnosis.py +79 -24
- operate/utils/single_instance.py +226 -0
- operate/wallet/master.py +214 -177
- operate/wallet/wallet_recovery_manager.py +5 -5
- {olas_operate_middleware-0.10.20.dist-info → olas_operate_middleware-0.11.1.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.10.20.dist-info → olas_operate_middleware-0.11.1.dist-info}/entry_points.txt +0 -0
- {olas_operate_middleware-0.10.20.dist-info → olas_operate_middleware-0.11.1.dist-info}/licenses/LICENSE +0 -0
operate/services/protocol.py
CHANGED
|
@@ -28,6 +28,7 @@ import os
|
|
|
28
28
|
import tempfile
|
|
29
29
|
import typing as t
|
|
30
30
|
from enum import Enum
|
|
31
|
+
from functools import cache
|
|
31
32
|
from pathlib import Path
|
|
32
33
|
from typing import Optional, Union, cast
|
|
33
34
|
|
|
@@ -59,6 +60,7 @@ from hexbytes import HexBytes
|
|
|
59
60
|
from web3.contract import Contract
|
|
60
61
|
|
|
61
62
|
from operate.constants import (
|
|
63
|
+
NO_STAKING_PROGRAM_ID,
|
|
62
64
|
ON_CHAIN_INTERACT_RETRIES,
|
|
63
65
|
ON_CHAIN_INTERACT_SLEEP,
|
|
64
66
|
ON_CHAIN_INTERACT_TIMEOUT,
|
|
@@ -68,8 +70,15 @@ from operate.data import DATA_DIR
|
|
|
68
70
|
from operate.data.contracts.dual_staking_token.contract import DualStakingTokenContract
|
|
69
71
|
from operate.data.contracts.recovery_module.contract import RecoveryModule
|
|
70
72
|
from operate.data.contracts.staking_token.contract import StakingTokenContract
|
|
73
|
+
from operate.ledger import (
|
|
74
|
+
get_default_ledger_api,
|
|
75
|
+
update_tx_with_gas_estimate,
|
|
76
|
+
update_tx_with_gas_pricing,
|
|
77
|
+
)
|
|
78
|
+
from operate.ledger.profiles import CONTRACTS, STAKING
|
|
71
79
|
from operate.operate_types import Chain as OperateChain
|
|
72
80
|
from operate.operate_types import ContractAddresses
|
|
81
|
+
from operate.services.service import NON_EXISTENT_TOKEN
|
|
73
82
|
from operate.utils.gnosis import (
|
|
74
83
|
MultiSendOperation,
|
|
75
84
|
SafeOperation,
|
|
@@ -162,6 +171,8 @@ class GnosisSafeTransaction:
|
|
|
162
171
|
operation=SafeOperation.DELEGATE_CALL.value,
|
|
163
172
|
nonce=self.ledger_api.api.eth.get_transaction_count(owner),
|
|
164
173
|
)
|
|
174
|
+
update_tx_with_gas_pricing(tx, self.ledger_api)
|
|
175
|
+
update_tx_with_gas_estimate(tx, self.ledger_api)
|
|
165
176
|
return t.cast(t.Dict, tx)
|
|
166
177
|
|
|
167
178
|
def settle(self) -> t.Dict:
|
|
@@ -170,6 +181,9 @@ class GnosisSafeTransaction:
|
|
|
170
181
|
ledger_api=self.ledger_api,
|
|
171
182
|
crypto=self.crypto,
|
|
172
183
|
chain_type=self.chain_type,
|
|
184
|
+
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
185
|
+
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
186
|
+
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
173
187
|
)
|
|
174
188
|
setattr(tx_settler, "build", self.build) # noqa: B010
|
|
175
189
|
return tx_settler.transact(
|
|
@@ -180,31 +194,93 @@ class GnosisSafeTransaction:
|
|
|
180
194
|
)
|
|
181
195
|
|
|
182
196
|
|
|
183
|
-
class StakingManager
|
|
197
|
+
class StakingManager:
|
|
184
198
|
"""Helper class for staking a service."""
|
|
185
199
|
|
|
200
|
+
staking_ctr = t.cast(
|
|
201
|
+
StakingTokenContract,
|
|
202
|
+
StakingTokenContract.from_dir(
|
|
203
|
+
directory=str(DATA_DIR / "contracts" / "staking_token")
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
dual_staking_ctr = t.cast(
|
|
208
|
+
DualStakingTokenContract,
|
|
209
|
+
DualStakingTokenContract.from_dir(
|
|
210
|
+
directory=str(DATA_DIR / "contracts" / "dual_staking_token")
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
|
|
186
214
|
def __init__(
|
|
187
215
|
self,
|
|
188
|
-
|
|
189
|
-
chain_type: ChainType = ChainType.CUSTOM,
|
|
190
|
-
password: Optional[str] = None,
|
|
216
|
+
chain: OperateChain,
|
|
191
217
|
) -> None:
|
|
192
218
|
"""Initialize object."""
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
self.chain = chain
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def ledger_api(self) -> LedgerApi:
|
|
223
|
+
"""Get ledger api."""
|
|
224
|
+
return get_default_ledger_api(OperateChain(self.chain.value))
|
|
225
|
+
|
|
226
|
+
@staticmethod
|
|
227
|
+
@cache
|
|
228
|
+
def _get_staking_params(chain: OperateChain, staking_contract: str) -> t.Dict:
|
|
229
|
+
"""Get staking params"""
|
|
230
|
+
ledger_api = get_default_ledger_api(chain=chain)
|
|
231
|
+
instance = StakingManager.staking_ctr.get_instance(
|
|
232
|
+
ledger_api=ledger_api,
|
|
233
|
+
contract_address=staking_contract,
|
|
199
234
|
)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
)
|
|
235
|
+
agent_ids = instance.functions.getAgentIds().call()
|
|
236
|
+
service_registry = instance.functions.serviceRegistry().call()
|
|
237
|
+
staking_token = instance.functions.stakingToken().call()
|
|
238
|
+
service_registry_token_utility = (
|
|
239
|
+
instance.functions.serviceRegistryTokenUtility().call()
|
|
240
|
+
)
|
|
241
|
+
min_staking_deposit = instance.functions.minStakingDeposit().call()
|
|
242
|
+
activity_checker = instance.functions.activityChecker().call()
|
|
243
|
+
|
|
244
|
+
output = {
|
|
245
|
+
"staking_contract": staking_contract,
|
|
246
|
+
"agent_ids": agent_ids,
|
|
247
|
+
"service_registry": service_registry,
|
|
248
|
+
"staking_token": staking_token,
|
|
249
|
+
"service_registry_token_utility": service_registry_token_utility,
|
|
250
|
+
"min_staking_deposit": min_staking_deposit,
|
|
251
|
+
"activity_checker": activity_checker,
|
|
252
|
+
"additional_staking_tokens": {},
|
|
253
|
+
}
|
|
254
|
+
try:
|
|
255
|
+
instance = StakingManager.dual_staking_ctr.get_instance(
|
|
256
|
+
ledger_api=ledger_api,
|
|
257
|
+
contract_address=staking_contract,
|
|
258
|
+
)
|
|
259
|
+
output["additional_staking_tokens"][
|
|
260
|
+
instance.functions.secondToken().call()
|
|
261
|
+
] = instance.functions.secondTokenAmount().call()
|
|
262
|
+
except Exception: # pylint: disable=broad-except # nosec
|
|
263
|
+
# Contract is not a dual staking contract
|
|
264
|
+
|
|
265
|
+
# TODO The exception caught here should be ContractLogicError.
|
|
266
|
+
# This exception is typically raised when the contract reverts with
|
|
267
|
+
# a reason string. However, in some cases, the error message
|
|
268
|
+
# does not contain a reason string, which means web3.py raises
|
|
269
|
+
# a generic ValueError instead. It should be properly analyzed
|
|
270
|
+
# what exceptions might be raised by web3.py in this case. To
|
|
271
|
+
# avoid any issues we are simply catching all exceptions.
|
|
272
|
+
pass
|
|
273
|
+
|
|
274
|
+
return output
|
|
275
|
+
|
|
276
|
+
def get_staking_params(self, staking_contract: str) -> t.Dict:
|
|
277
|
+
"""Get staking params"""
|
|
278
|
+
return StakingManager._get_staking_params(
|
|
279
|
+
chain=self.chain,
|
|
280
|
+
staking_contract=staking_contract,
|
|
205
281
|
)
|
|
206
282
|
|
|
207
|
-
def
|
|
283
|
+
def staking_state(self, service_id: int, staking_contract: str) -> StakingState:
|
|
208
284
|
"""Is the service staked?"""
|
|
209
285
|
return StakingState(
|
|
210
286
|
self.staking_ctr.get_instance(
|
|
@@ -246,11 +322,12 @@ class StakingManager(OnChainHelper):
|
|
|
246
322
|
|
|
247
323
|
def service_info(self, staking_contract: str, service_id: int) -> dict:
|
|
248
324
|
"""Get the service onchain info"""
|
|
249
|
-
|
|
250
|
-
self.ledger_api,
|
|
251
|
-
staking_contract,
|
|
252
|
-
|
|
253
|
-
).
|
|
325
|
+
instance = self.staking_ctr.get_instance(
|
|
326
|
+
ledger_api=self.ledger_api,
|
|
327
|
+
contract_address=staking_contract,
|
|
328
|
+
)
|
|
329
|
+
service_info = instance.functions.getServiceInfo(service_id).call()
|
|
330
|
+
return service_info
|
|
254
331
|
|
|
255
332
|
def agent_ids(self, staking_contract: str) -> t.List[int]:
|
|
256
333
|
"""Get a list of agent IDs for the given staking contract."""
|
|
@@ -306,7 +383,7 @@ class StakingManager(OnChainHelper):
|
|
|
306
383
|
staking_contract: str,
|
|
307
384
|
) -> None:
|
|
308
385
|
"""Check if service can be staked."""
|
|
309
|
-
status = self.
|
|
386
|
+
status = self.staking_state(service_id, staking_contract)
|
|
310
387
|
if status == StakingState.STAKED:
|
|
311
388
|
raise ValueError("Service already staked")
|
|
312
389
|
|
|
@@ -316,21 +393,28 @@ class StakingManager(OnChainHelper):
|
|
|
316
393
|
if not self.slots_available(staking_contract):
|
|
317
394
|
raise ValueError("No sataking slots available.")
|
|
318
395
|
|
|
396
|
+
# TODO To be deprecated, only used in on-chain manager
|
|
319
397
|
def stake(
|
|
320
398
|
self,
|
|
321
399
|
service_id: int,
|
|
322
400
|
service_registry: str,
|
|
323
401
|
staking_contract: str,
|
|
402
|
+
key: Path,
|
|
403
|
+
password: str,
|
|
324
404
|
) -> None:
|
|
325
405
|
"""Stake the service"""
|
|
406
|
+
och = OnChainHelper(
|
|
407
|
+
key=key, chain_type=ChainType(self.chain.value), password=password
|
|
408
|
+
)
|
|
409
|
+
|
|
326
410
|
self.check_staking_compatibility(
|
|
327
411
|
service_id=service_id, staking_contract=staking_contract
|
|
328
412
|
)
|
|
329
413
|
|
|
330
414
|
tx_settler = TxSettler(
|
|
331
|
-
ledger_api=
|
|
332
|
-
crypto=
|
|
333
|
-
chain_type=
|
|
415
|
+
ledger_api=och.ledger_api,
|
|
416
|
+
crypto=och.crypto,
|
|
417
|
+
chain_type=och.chain_type,
|
|
334
418
|
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
335
419
|
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
336
420
|
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
@@ -346,10 +430,10 @@ class StakingManager(OnChainHelper):
|
|
|
346
430
|
*args: t.Any, **kargs: t.Any
|
|
347
431
|
) -> t.Dict:
|
|
348
432
|
return registry_contracts.erc20.get_approve_tx(
|
|
349
|
-
ledger_api=
|
|
433
|
+
ledger_api=och.ledger_api,
|
|
350
434
|
contract_address=service_registry,
|
|
351
435
|
spender=staking_contract,
|
|
352
|
-
sender=
|
|
436
|
+
sender=och.crypto.address,
|
|
353
437
|
amount=service_id, # TODO: This is a workaround and it should be fixed
|
|
354
438
|
)
|
|
355
439
|
|
|
@@ -364,15 +448,15 @@ class StakingManager(OnChainHelper):
|
|
|
364
448
|
def _build_staking_tx( # pylint: disable=unused-argument
|
|
365
449
|
*args: t.Any, **kargs: t.Any
|
|
366
450
|
) -> t.Dict:
|
|
367
|
-
return
|
|
451
|
+
return och.ledger_api.build_transaction(
|
|
368
452
|
contract_instance=self.staking_ctr.get_instance(
|
|
369
|
-
ledger_api=
|
|
453
|
+
ledger_api=och.ledger_api,
|
|
370
454
|
contract_address=staking_contract,
|
|
371
455
|
),
|
|
372
456
|
method_name="stake",
|
|
373
457
|
method_args={"serviceId": service_id},
|
|
374
458
|
tx_args={
|
|
375
|
-
"sender_address":
|
|
459
|
+
"sender_address": och.crypto.address,
|
|
376
460
|
},
|
|
377
461
|
raise_on_try=True,
|
|
378
462
|
)
|
|
@@ -391,7 +475,7 @@ class StakingManager(OnChainHelper):
|
|
|
391
475
|
staking_contract: str,
|
|
392
476
|
) -> None:
|
|
393
477
|
"""Check unstaking availability"""
|
|
394
|
-
if self.
|
|
478
|
+
if self.staking_state(
|
|
395
479
|
service_id=service_id, staking_contract=staking_contract
|
|
396
480
|
) not in {StakingState.STAKED, StakingState.EVICTED}:
|
|
397
481
|
raise ValueError("Service not staked.")
|
|
@@ -415,13 +499,23 @@ class StakingManager(OnChainHelper):
|
|
|
415
499
|
if staked_duration < minimum_staking_duration and available_rewards > 0:
|
|
416
500
|
raise ValueError("Service cannot be unstaked yet.")
|
|
417
501
|
|
|
418
|
-
|
|
502
|
+
# TODO To be deprecated, only used in on-chain manager
|
|
503
|
+
def unstake(
|
|
504
|
+
self,
|
|
505
|
+
service_id: int,
|
|
506
|
+
staking_contract: str,
|
|
507
|
+
key: Path,
|
|
508
|
+
password: str,
|
|
509
|
+
) -> None:
|
|
419
510
|
"""Unstake the service"""
|
|
511
|
+
och = OnChainHelper(
|
|
512
|
+
key=key, chain_type=ChainType(self.chain.value), password=password
|
|
513
|
+
)
|
|
420
514
|
|
|
421
515
|
tx_settler = TxSettler(
|
|
422
|
-
ledger_api=
|
|
423
|
-
crypto=
|
|
424
|
-
chain_type=
|
|
516
|
+
ledger_api=och.ledger_api,
|
|
517
|
+
crypto=och.crypto,
|
|
518
|
+
chain_type=och.chain_type,
|
|
425
519
|
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
426
520
|
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
427
521
|
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
@@ -430,15 +524,15 @@ class StakingManager(OnChainHelper):
|
|
|
430
524
|
def _build_unstaking_tx( # pylint: disable=unused-argument
|
|
431
525
|
*args: t.Any, **kargs: t.Any
|
|
432
526
|
) -> t.Dict:
|
|
433
|
-
return
|
|
527
|
+
return och.ledger_api.build_transaction(
|
|
434
528
|
contract_instance=self.staking_ctr.get_instance(
|
|
435
|
-
ledger_api=
|
|
529
|
+
ledger_api=och.ledger_api,
|
|
436
530
|
contract_address=staking_contract,
|
|
437
531
|
),
|
|
438
532
|
method_name="unstake",
|
|
439
533
|
method_args={"serviceId": service_id},
|
|
440
534
|
tx_args={
|
|
441
|
-
"sender_address":
|
|
535
|
+
"sender_address": och.crypto.address,
|
|
442
536
|
},
|
|
443
537
|
raise_on_try=True,
|
|
444
538
|
)
|
|
@@ -523,6 +617,69 @@ class StakingManager(OnChainHelper):
|
|
|
523
617
|
args=[service_id],
|
|
524
618
|
)
|
|
525
619
|
|
|
620
|
+
def get_staking_contract(
|
|
621
|
+
self, staking_program_id: t.Optional[str]
|
|
622
|
+
) -> t.Optional[str]:
|
|
623
|
+
"""Get staking contract based on the config and the staking program."""
|
|
624
|
+
if staking_program_id == NO_STAKING_PROGRAM_ID or staking_program_id is None:
|
|
625
|
+
return None
|
|
626
|
+
|
|
627
|
+
return STAKING[self.chain].get(
|
|
628
|
+
staking_program_id,
|
|
629
|
+
staking_program_id,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
def get_current_staking_program(self, service_id: int) -> t.Optional[str]:
|
|
633
|
+
"""Get the current staking program of a service"""
|
|
634
|
+
ledger_api = self.ledger_api
|
|
635
|
+
|
|
636
|
+
if service_id == NON_EXISTENT_TOKEN:
|
|
637
|
+
return None
|
|
638
|
+
|
|
639
|
+
service_registry = registry_contracts.service_registry.get_instance(
|
|
640
|
+
ledger_api=ledger_api,
|
|
641
|
+
contract_address=CONTRACTS[self.chain]["service_registry"],
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
service_owner = service_registry.functions.ownerOf(service_id).call()
|
|
645
|
+
|
|
646
|
+
try:
|
|
647
|
+
state = self.staking_state(
|
|
648
|
+
service_id=service_id, staking_contract=service_owner
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
except Exception: # pylint: disable=broad-except
|
|
652
|
+
# Service owner is not a staking contract
|
|
653
|
+
|
|
654
|
+
# TODO The exception caught here should be ContractLogicError.
|
|
655
|
+
# This exception is typically raised when the contract reverts with
|
|
656
|
+
# a reason string. However, in some cases, the error message
|
|
657
|
+
# does not contain a reason string, which means web3.py raises
|
|
658
|
+
# a generic ValueError instead. It should be properly analyzed
|
|
659
|
+
# what exceptions might be raised by web3.py in this case. To
|
|
660
|
+
# avoid any issues we are simply catching all exceptions.
|
|
661
|
+
return None
|
|
662
|
+
|
|
663
|
+
if state == StakingState.UNSTAKED:
|
|
664
|
+
return None
|
|
665
|
+
|
|
666
|
+
for staking_program_id, val in STAKING[self.chain].items():
|
|
667
|
+
if val == service_owner:
|
|
668
|
+
return staking_program_id
|
|
669
|
+
|
|
670
|
+
# Fallback, if not possible to determine staking_program_id it means it's an "inner" staking contract
|
|
671
|
+
# (e.g., in the case of DualStakingToken). Loop trough all the known contracts.
|
|
672
|
+
for staking_program_id, staking_program_address in STAKING[self.chain].items():
|
|
673
|
+
state = self.staking_state(
|
|
674
|
+
service_id=service_id, staking_contract=staking_program_address
|
|
675
|
+
)
|
|
676
|
+
if state in (StakingState.STAKED, StakingState.EVICTED):
|
|
677
|
+
return staking_program_id
|
|
678
|
+
|
|
679
|
+
# it's staked, but we don't know which staking program
|
|
680
|
+
# so the staking_program_id should be an arbitrary staking contract
|
|
681
|
+
return service_owner
|
|
682
|
+
|
|
526
683
|
|
|
527
684
|
# TODO Backport this to Open Autonomy MintHelper class
|
|
528
685
|
# MintHelper should support passing custom 'description', 'name' and 'attributes'.
|
|
@@ -572,8 +729,6 @@ class MintManager(MintHelper):
|
|
|
572
729
|
class _ChainUtil:
|
|
573
730
|
"""On chain service management."""
|
|
574
731
|
|
|
575
|
-
_cache = {}
|
|
576
|
-
|
|
577
732
|
def __init__(
|
|
578
733
|
self,
|
|
579
734
|
rpc: str,
|
|
@@ -814,9 +969,7 @@ class _ChainUtil:
|
|
|
814
969
|
"""Check if there are available slots on the staking contract"""
|
|
815
970
|
self._patch()
|
|
816
971
|
return StakingManager(
|
|
817
|
-
|
|
818
|
-
password=self.wallet.password,
|
|
819
|
-
chain_type=self.chain_type,
|
|
972
|
+
chain=OperateChain(self.chain_type.value),
|
|
820
973
|
).slots_available(
|
|
821
974
|
staking_contract=staking_contract,
|
|
822
975
|
)
|
|
@@ -825,9 +978,7 @@ class _ChainUtil:
|
|
|
825
978
|
"""Check if there are available staking rewards on the staking contract"""
|
|
826
979
|
self._patch()
|
|
827
980
|
available_rewards = StakingManager(
|
|
828
|
-
|
|
829
|
-
password=self.wallet.password,
|
|
830
|
-
chain_type=self.chain_type,
|
|
981
|
+
chain=OperateChain(self.chain_type.value),
|
|
831
982
|
).available_rewards(
|
|
832
983
|
staking_contract=staking_contract,
|
|
833
984
|
)
|
|
@@ -837,9 +988,7 @@ class _ChainUtil:
|
|
|
837
988
|
"""Check if there are claimable staking rewards on the staking contract"""
|
|
838
989
|
self._patch()
|
|
839
990
|
claimable_rewards = StakingManager(
|
|
840
|
-
|
|
841
|
-
password=self.wallet.password,
|
|
842
|
-
chain_type=self.chain_type,
|
|
991
|
+
chain=OperateChain(self.chain_type.value),
|
|
843
992
|
).claimable_rewards(
|
|
844
993
|
staking_contract=staking_contract,
|
|
845
994
|
service_id=service_id,
|
|
@@ -850,10 +999,8 @@ class _ChainUtil:
|
|
|
850
999
|
"""Stake the service"""
|
|
851
1000
|
self._patch()
|
|
852
1001
|
return StakingManager(
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
chain_type=self.chain_type,
|
|
856
|
-
).status(
|
|
1002
|
+
chain=OperateChain(self.chain_type.value),
|
|
1003
|
+
).staking_state(
|
|
857
1004
|
service_id=service_id,
|
|
858
1005
|
staking_contract=staking_contract,
|
|
859
1006
|
)
|
|
@@ -862,72 +1009,15 @@ class _ChainUtil:
|
|
|
862
1009
|
self, staking_contract: str, fallback_params: t.Optional[t.Dict] = None
|
|
863
1010
|
) -> t.Dict:
|
|
864
1011
|
"""Get agent IDs for the staking contract"""
|
|
865
|
-
|
|
866
1012
|
if staking_contract is None and fallback_params is not None:
|
|
867
1013
|
return fallback_params
|
|
868
|
-
|
|
869
|
-
cache = _ChainUtil._cache
|
|
870
|
-
if staking_contract in cache.setdefault("get_staking_params", {}):
|
|
871
|
-
return cache["get_staking_params"][staking_contract]
|
|
872
|
-
|
|
873
1014
|
self._patch()
|
|
874
1015
|
staking_manager = StakingManager(
|
|
875
|
-
|
|
876
|
-
password=self.wallet.password,
|
|
877
|
-
chain_type=self.chain_type,
|
|
878
|
-
)
|
|
879
|
-
agent_ids = staking_manager.agent_ids(
|
|
880
|
-
staking_contract=staking_contract,
|
|
1016
|
+
chain=OperateChain(self.chain_type.value),
|
|
881
1017
|
)
|
|
882
|
-
|
|
1018
|
+
return staking_manager.get_staking_params(
|
|
883
1019
|
staking_contract=staking_contract,
|
|
884
1020
|
)
|
|
885
|
-
staking_token = staking_manager.staking_token(
|
|
886
|
-
staking_contract=staking_contract,
|
|
887
|
-
)
|
|
888
|
-
service_registry_token_utility = staking_manager.service_registry_token_utility(
|
|
889
|
-
staking_contract=staking_contract,
|
|
890
|
-
)
|
|
891
|
-
min_staking_deposit = staking_manager.min_staking_deposit(
|
|
892
|
-
staking_contract=staking_contract,
|
|
893
|
-
)
|
|
894
|
-
activity_checker = staking_manager.activity_checker(
|
|
895
|
-
staking_contract=staking_contract,
|
|
896
|
-
)
|
|
897
|
-
|
|
898
|
-
output = {
|
|
899
|
-
"staking_contract": staking_contract,
|
|
900
|
-
"agent_ids": agent_ids,
|
|
901
|
-
"service_registry": service_registry,
|
|
902
|
-
"staking_token": staking_token,
|
|
903
|
-
"service_registry_token_utility": service_registry_token_utility,
|
|
904
|
-
"min_staking_deposit": min_staking_deposit,
|
|
905
|
-
"activity_checker": activity_checker,
|
|
906
|
-
"additional_staking_tokens": {},
|
|
907
|
-
}
|
|
908
|
-
try:
|
|
909
|
-
instance = staking_manager.dual_staking_ctr.get_instance(
|
|
910
|
-
ledger_api=self.ledger_api,
|
|
911
|
-
contract_address=staking_contract,
|
|
912
|
-
)
|
|
913
|
-
output["additional_staking_tokens"][
|
|
914
|
-
instance.functions.secondToken().call()
|
|
915
|
-
] = instance.functions.secondTokenAmount().call()
|
|
916
|
-
except Exception: # pylint: disable=broad-except # nosec
|
|
917
|
-
# Contract is not a dual staking contract
|
|
918
|
-
|
|
919
|
-
# TODO The exception caught here should be ContractLogicError.
|
|
920
|
-
# This exception is typically raised when the contract reverts with
|
|
921
|
-
# a reason string. However, in some cases, the error message
|
|
922
|
-
# does not contain a reason string, which means web3.py raises
|
|
923
|
-
# a generic ValueError instead. It should be properly analyzed
|
|
924
|
-
# what exceptions might be raised by web3.py in this case. To
|
|
925
|
-
# avoid any issues we are simply catching all exceptions.
|
|
926
|
-
pass
|
|
927
|
-
|
|
928
|
-
cache["get_staking_params"][staking_contract] = output
|
|
929
|
-
|
|
930
|
-
return output
|
|
931
1021
|
|
|
932
1022
|
|
|
933
1023
|
class OnChainManager(_ChainUtil):
|
|
@@ -1115,35 +1205,33 @@ class OnChainManager(_ChainUtil):
|
|
|
1115
1205
|
"""Stake service."""
|
|
1116
1206
|
self._patch()
|
|
1117
1207
|
StakingManager(
|
|
1118
|
-
|
|
1119
|
-
password=self.wallet.password,
|
|
1120
|
-
chain_type=self.chain_type,
|
|
1208
|
+
chain=OperateChain(self.chain_type.value),
|
|
1121
1209
|
).stake(
|
|
1122
1210
|
service_id=service_id,
|
|
1123
1211
|
service_registry=service_registry,
|
|
1124
1212
|
staking_contract=staking_contract,
|
|
1213
|
+
key=self.wallet.key_path,
|
|
1214
|
+
password=self.wallet.password,
|
|
1125
1215
|
)
|
|
1126
1216
|
|
|
1127
1217
|
def unstake(self, service_id: int, staking_contract: str) -> None:
|
|
1128
1218
|
"""Unstake service."""
|
|
1129
1219
|
self._patch()
|
|
1130
1220
|
StakingManager(
|
|
1131
|
-
|
|
1132
|
-
password=self.wallet.password,
|
|
1133
|
-
chain_type=self.chain_type,
|
|
1221
|
+
chain=OperateChain(self.chain_type.value),
|
|
1134
1222
|
).unstake(
|
|
1135
1223
|
service_id=service_id,
|
|
1136
1224
|
staking_contract=staking_contract,
|
|
1225
|
+
key=self.wallet.key_path,
|
|
1226
|
+
password=self.wallet.password,
|
|
1137
1227
|
)
|
|
1138
1228
|
|
|
1139
1229
|
def staking_status(self, service_id: int, staking_contract: str) -> StakingState:
|
|
1140
1230
|
"""Stake the service"""
|
|
1141
1231
|
self._patch()
|
|
1142
1232
|
return StakingManager(
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
chain_type=self.chain_type,
|
|
1146
|
-
).status(
|
|
1233
|
+
chain=OperateChain(self.chain_type.value),
|
|
1234
|
+
).staking_state(
|
|
1147
1235
|
service_id=service_id,
|
|
1148
1236
|
staking_contract=staking_contract,
|
|
1149
1237
|
)
|
|
@@ -1397,6 +1485,182 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1397
1485
|
return [deploy_message]
|
|
1398
1486
|
return [approve_hash_message, deploy_message]
|
|
1399
1487
|
|
|
1488
|
+
def get_safe_b_native_transfer_messages( # pylint: disable=too-many-locals
|
|
1489
|
+
self,
|
|
1490
|
+
safe_b_address: str,
|
|
1491
|
+
to: str,
|
|
1492
|
+
amount: int,
|
|
1493
|
+
) -> t.Tuple[t.Dict, t.Dict]:
|
|
1494
|
+
"""
|
|
1495
|
+
Build the two messages (Safe calls) to withdraw native ETH from Safe B via this Safe (owner of Safe B).
|
|
1496
|
+
|
|
1497
|
+
Builds the messages to be settled by this Safe:
|
|
1498
|
+
1) approveHash(inner_tx_hash)
|
|
1499
|
+
2) execTransaction(...) to transfer ETH
|
|
1500
|
+
"""
|
|
1501
|
+
safe_b_instance = registry_contracts.gnosis_safe.get_instance(
|
|
1502
|
+
ledger_api=self.ledger_api,
|
|
1503
|
+
contract_address=safe_b_address,
|
|
1504
|
+
)
|
|
1505
|
+
|
|
1506
|
+
txs = []
|
|
1507
|
+
txs.append(
|
|
1508
|
+
{
|
|
1509
|
+
"to": to,
|
|
1510
|
+
"data": b"",
|
|
1511
|
+
"operation": MultiSendOperation.CALL,
|
|
1512
|
+
"value": amount,
|
|
1513
|
+
}
|
|
1514
|
+
)
|
|
1515
|
+
|
|
1516
|
+
multisend_address = ContractConfigs.get(MULTISEND_CONTRACT.name).contracts[
|
|
1517
|
+
self.chain_type
|
|
1518
|
+
]
|
|
1519
|
+
multisend_tx = registry_contracts.multisend.get_multisend_tx(
|
|
1520
|
+
ledger_api=self.ledger_api,
|
|
1521
|
+
contract_address=multisend_address,
|
|
1522
|
+
txs=txs,
|
|
1523
|
+
)
|
|
1524
|
+
|
|
1525
|
+
# Compute inner Safe transaction hash
|
|
1526
|
+
safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash(
|
|
1527
|
+
ledger_api=self.ledger_api,
|
|
1528
|
+
contract_address=safe_b_address,
|
|
1529
|
+
to_address=multisend_address,
|
|
1530
|
+
value=multisend_tx["value"],
|
|
1531
|
+
data=multisend_tx["data"],
|
|
1532
|
+
operation=SafeOperation.CALL.value,
|
|
1533
|
+
).get("tx_hash")
|
|
1534
|
+
|
|
1535
|
+
# Build approveHash message
|
|
1536
|
+
approve_hash_data = safe_b_instance.encodeABI(
|
|
1537
|
+
fn_name="approveHash",
|
|
1538
|
+
args=[safe_tx_hash],
|
|
1539
|
+
)
|
|
1540
|
+
approve_hash_message = {
|
|
1541
|
+
"to": safe_b_address,
|
|
1542
|
+
"data": approve_hash_data[2:],
|
|
1543
|
+
"operation": MultiSendOperation.CALL,
|
|
1544
|
+
"value": 0,
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
# Build execTransaction message
|
|
1548
|
+
exec_data = safe_b_instance.encodeABI(
|
|
1549
|
+
fn_name="execTransaction",
|
|
1550
|
+
args=[
|
|
1551
|
+
multisend_address,
|
|
1552
|
+
multisend_tx["value"],
|
|
1553
|
+
multisend_tx["data"],
|
|
1554
|
+
SafeOperation.DELEGATE_CALL.value,
|
|
1555
|
+
0, # safeTxGas
|
|
1556
|
+
0, # baseGas
|
|
1557
|
+
0, # gasPrice
|
|
1558
|
+
ZERO_ADDRESS, # gasToken
|
|
1559
|
+
ZERO_ADDRESS, # refundReceiver
|
|
1560
|
+
get_packed_signature_for_approved_hash(owners=(self.safe,)),
|
|
1561
|
+
],
|
|
1562
|
+
)
|
|
1563
|
+
exec_message = {
|
|
1564
|
+
"to": safe_b_address,
|
|
1565
|
+
"data": exec_data[2:],
|
|
1566
|
+
"operation": MultiSendOperation.CALL,
|
|
1567
|
+
"value": 0,
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
return approve_hash_message, exec_message
|
|
1571
|
+
|
|
1572
|
+
def get_safe_b_erc20_transfer_messages( # pylint: disable=too-many-locals
|
|
1573
|
+
self,
|
|
1574
|
+
safe_b_address: str,
|
|
1575
|
+
token: str,
|
|
1576
|
+
to: str,
|
|
1577
|
+
amount: int,
|
|
1578
|
+
) -> t.Tuple[t.Dict, t.Dict]:
|
|
1579
|
+
"""
|
|
1580
|
+
Build the two messages (Safe calls) to withdraw ERC20 from Safe B via this Safe (owner of Safe B).
|
|
1581
|
+
|
|
1582
|
+
Builds the messages to be settled by this Safe:
|
|
1583
|
+
1) approveHash(inner_tx_hash)
|
|
1584
|
+
2) execTransaction(...) to transfer ERC20 tokens
|
|
1585
|
+
"""
|
|
1586
|
+
safe_b_instance = registry_contracts.gnosis_safe.get_instance(
|
|
1587
|
+
ledger_api=self.ledger_api,
|
|
1588
|
+
contract_address=safe_b_address,
|
|
1589
|
+
)
|
|
1590
|
+
erc20_instance = registry_contracts.erc20.get_instance(
|
|
1591
|
+
ledger_api=self.ledger_api,
|
|
1592
|
+
contract_address=token,
|
|
1593
|
+
)
|
|
1594
|
+
|
|
1595
|
+
txs = []
|
|
1596
|
+
txs.append(
|
|
1597
|
+
{
|
|
1598
|
+
"to": token,
|
|
1599
|
+
"data": erc20_instance.encodeABI(
|
|
1600
|
+
fn_name="transfer",
|
|
1601
|
+
args=[to, amount],
|
|
1602
|
+
),
|
|
1603
|
+
"operation": MultiSendOperation.CALL,
|
|
1604
|
+
"value": 0,
|
|
1605
|
+
}
|
|
1606
|
+
)
|
|
1607
|
+
|
|
1608
|
+
multisend_address = ContractConfigs.get(MULTISEND_CONTRACT.name).contracts[
|
|
1609
|
+
self.chain_type
|
|
1610
|
+
]
|
|
1611
|
+
multisend_tx = registry_contracts.multisend.get_multisend_tx(
|
|
1612
|
+
ledger_api=self.ledger_api,
|
|
1613
|
+
contract_address=multisend_address,
|
|
1614
|
+
txs=txs,
|
|
1615
|
+
)
|
|
1616
|
+
|
|
1617
|
+
# Compute inner Safe transaction hash
|
|
1618
|
+
safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash(
|
|
1619
|
+
ledger_api=self.ledger_api,
|
|
1620
|
+
contract_address=safe_b_address,
|
|
1621
|
+
to_address=multisend_address,
|
|
1622
|
+
value=multisend_tx["value"],
|
|
1623
|
+
data=multisend_tx["data"],
|
|
1624
|
+
operation=SafeOperation.CALL.value,
|
|
1625
|
+
).get("tx_hash")
|
|
1626
|
+
|
|
1627
|
+
# Build approveHash message
|
|
1628
|
+
approve_hash_data = safe_b_instance.encodeABI(
|
|
1629
|
+
fn_name="approveHash",
|
|
1630
|
+
args=[safe_tx_hash],
|
|
1631
|
+
)
|
|
1632
|
+
approve_hash_message = {
|
|
1633
|
+
"to": safe_b_address,
|
|
1634
|
+
"data": approve_hash_data[2:],
|
|
1635
|
+
"operation": MultiSendOperation.CALL,
|
|
1636
|
+
"value": 0,
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
# Build execTransaction message
|
|
1640
|
+
exec_data = safe_b_instance.encodeABI(
|
|
1641
|
+
fn_name="execTransaction",
|
|
1642
|
+
args=[
|
|
1643
|
+
multisend_address,
|
|
1644
|
+
multisend_tx["value"],
|
|
1645
|
+
multisend_tx["data"],
|
|
1646
|
+
SafeOperation.DELEGATE_CALL.value,
|
|
1647
|
+
0, # safeTxGas
|
|
1648
|
+
0, # baseGas
|
|
1649
|
+
0, # gasPrice
|
|
1650
|
+
ZERO_ADDRESS, # gasToken
|
|
1651
|
+
ZERO_ADDRESS, # refundReceiver
|
|
1652
|
+
get_packed_signature_for_approved_hash(owners=(self.safe,)),
|
|
1653
|
+
],
|
|
1654
|
+
)
|
|
1655
|
+
exec_message = {
|
|
1656
|
+
"to": safe_b_address,
|
|
1657
|
+
"data": exec_data[2:],
|
|
1658
|
+
"operation": MultiSendOperation.CALL,
|
|
1659
|
+
"value": 0,
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
return approve_hash_message, exec_message
|
|
1663
|
+
|
|
1400
1664
|
def get_terminate_data(self, service_id: int) -> t.Dict:
|
|
1401
1665
|
"""Get terminate tx data."""
|
|
1402
1666
|
instance = registry_contracts.service_manager.get_instance(
|
|
@@ -1440,9 +1704,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1440
1704
|
"""Get staking approval data"""
|
|
1441
1705
|
self._patch()
|
|
1442
1706
|
txd = StakingManager(
|
|
1443
|
-
|
|
1444
|
-
password=self.wallet.password,
|
|
1445
|
-
chain_type=self.chain_type,
|
|
1707
|
+
chain=OperateChain(self.chain_type.value),
|
|
1446
1708
|
).get_stake_approval_tx_data(
|
|
1447
1709
|
service_id=service_id,
|
|
1448
1710
|
service_registry=service_registry,
|
|
@@ -1464,9 +1726,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1464
1726
|
"""Get staking tx data"""
|
|
1465
1727
|
self._patch()
|
|
1466
1728
|
txd = StakingManager(
|
|
1467
|
-
|
|
1468
|
-
password=self.wallet.password,
|
|
1469
|
-
chain_type=self.chain_type,
|
|
1729
|
+
chain=OperateChain(self.chain_type.value),
|
|
1470
1730
|
).get_stake_tx_data(
|
|
1471
1731
|
service_id=service_id,
|
|
1472
1732
|
staking_contract=staking_contract,
|
|
@@ -1487,9 +1747,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1487
1747
|
"""Get unstaking tx data"""
|
|
1488
1748
|
self._patch()
|
|
1489
1749
|
staking_manager = StakingManager(
|
|
1490
|
-
|
|
1491
|
-
password=self.wallet.password,
|
|
1492
|
-
chain_type=self.chain_type,
|
|
1750
|
+
chain=OperateChain(self.chain_type.value),
|
|
1493
1751
|
)
|
|
1494
1752
|
txd = (
|
|
1495
1753
|
staking_manager.get_forced_unstake_tx_data(
|
|
@@ -1517,9 +1775,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1517
1775
|
"""Get claiming tx data"""
|
|
1518
1776
|
self._patch()
|
|
1519
1777
|
staking_manager = StakingManager(
|
|
1520
|
-
|
|
1521
|
-
password=self.wallet.password,
|
|
1522
|
-
chain_type=self.chain_type,
|
|
1778
|
+
chain=OperateChain(self.chain_type.value),
|
|
1523
1779
|
)
|
|
1524
1780
|
txd = staking_manager.get_claim_tx_data(
|
|
1525
1781
|
service_id=service_id,
|
|
@@ -1536,9 +1792,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1536
1792
|
"""Stake service."""
|
|
1537
1793
|
self._patch()
|
|
1538
1794
|
return StakingManager(
|
|
1539
|
-
|
|
1540
|
-
password=self.wallet.password,
|
|
1541
|
-
chain_type=self.chain_type,
|
|
1795
|
+
chain=OperateChain(self.chain_type.value),
|
|
1542
1796
|
).slots_available(
|
|
1543
1797
|
staking_contract=staking_contract,
|
|
1544
1798
|
)
|
|
@@ -1548,9 +1802,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1548
1802
|
self._patch()
|
|
1549
1803
|
try:
|
|
1550
1804
|
StakingManager(
|
|
1551
|
-
|
|
1552
|
-
password=self.wallet.password,
|
|
1553
|
-
chain_type=self.chain_type,
|
|
1805
|
+
chain=OperateChain(self.chain_type.value),
|
|
1554
1806
|
).check_if_unstaking_possible(
|
|
1555
1807
|
service_id=service_id,
|
|
1556
1808
|
staking_contract=staking_contract,
|