olas-operate-middleware 0.13.6__tar.gz → 0.13.7__tar.gz

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.
Files changed (100) hide show
  1. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/PKG-INFO +1 -1
  2. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/funding_manager.py +138 -56
  3. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/protocol.py +47 -24
  4. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/utils/__init__.py +108 -0
  5. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/pyproject.toml +3 -1
  6. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/LICENSE +0 -0
  7. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/README.md +0 -0
  8. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/__init__.py +0 -0
  9. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/account/__init__.py +0 -0
  10. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/account/user.py +0 -0
  11. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/bridge/bridge_manager.py +0 -0
  12. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/bridge/providers/lifi_provider.py +0 -0
  13. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/bridge/providers/native_bridge_provider.py +0 -0
  14. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/bridge/providers/provider.py +0 -0
  15. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/bridge/providers/relay_provider.py +0 -0
  16. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/cli.py +0 -0
  17. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/constants.py +0 -0
  18. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/README.md +0 -0
  19. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/__init__.py +0 -0
  20. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/__init__.py +0 -0
  21. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/dual_staking_token/__init__.py +0 -0
  22. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/dual_staking_token/build/DualStakingToken.json +0 -0
  23. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/dual_staking_token/contract.py +0 -0
  24. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/dual_staking_token/contract.yaml +0 -0
  25. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/foreign_omnibridge/__init__.py +0 -0
  26. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +0 -0
  27. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/foreign_omnibridge/contract.py +0 -0
  28. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/foreign_omnibridge/contract.yaml +0 -0
  29. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/home_omnibridge/__init__.py +0 -0
  30. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +0 -0
  31. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/home_omnibridge/contract.py +0 -0
  32. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/home_omnibridge/contract.yaml +0 -0
  33. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/l1_standard_bridge/__init__.py +0 -0
  34. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +0 -0
  35. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/l1_standard_bridge/contract.py +0 -0
  36. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/l1_standard_bridge/contract.yaml +0 -0
  37. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/l2_standard_bridge/__init__.py +0 -0
  38. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +0 -0
  39. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/l2_standard_bridge/contract.py +0 -0
  40. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/l2_standard_bridge/contract.yaml +0 -0
  41. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/mech_activity/__init__.py +0 -0
  42. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/mech_activity/build/MechActivity.json +0 -0
  43. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/mech_activity/contract.py +0 -0
  44. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/mech_activity/contract.yaml +0 -0
  45. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/optimism_mintable_erc20/__init__.py +0 -0
  46. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +0 -0
  47. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/optimism_mintable_erc20/contract.py +0 -0
  48. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/optimism_mintable_erc20/contract.yaml +0 -0
  49. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/recovery_module/__init__.py +0 -0
  50. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/recovery_module/build/RecoveryModule.json +0 -0
  51. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/recovery_module/contract.py +0 -0
  52. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/recovery_module/contract.yaml +0 -0
  53. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/requester_activity_checker/__init__.py +0 -0
  54. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +0 -0
  55. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/requester_activity_checker/contract.py +0 -0
  56. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/requester_activity_checker/contract.yaml +0 -0
  57. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/staking_token/__init__.py +0 -0
  58. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/staking_token/build/StakingToken.json +0 -0
  59. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/staking_token/contract.py +0 -0
  60. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/staking_token/contract.yaml +0 -0
  61. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/uniswap_v2_erc20/__init__.py +0 -0
  62. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/uniswap_v2_erc20/build/IUniswapV2ERC20.json +0 -0
  63. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/uniswap_v2_erc20/contract.py +0 -0
  64. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/uniswap_v2_erc20/contract.yaml +0 -0
  65. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +0 -0
  66. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +0 -0
  67. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/keys.py +0 -0
  68. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/ledger/__init__.py +0 -0
  69. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/ledger/profiles.py +0 -0
  70. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/migration.py +0 -0
  71. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/operate_http/__init__.py +0 -0
  72. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/operate_http/exceptions.py +0 -0
  73. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/operate_types.py +0 -0
  74. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/pearl.py +0 -0
  75. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/quickstart/analyse_logs.py +0 -0
  76. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/quickstart/claim_staking_rewards.py +0 -0
  77. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/quickstart/reset_configs.py +0 -0
  78. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/quickstart/reset_password.py +0 -0
  79. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/quickstart/reset_staking.py +0 -0
  80. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/quickstart/run_service.py +0 -0
  81. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/quickstart/stop_service.py +0 -0
  82. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/quickstart/terminate_on_chain_service.py +0 -0
  83. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/quickstart/utils.py +0 -0
  84. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/resource.py +0 -0
  85. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/__init__.py +0 -0
  86. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/agent_runner.py +0 -0
  87. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/deployment_runner.py +0 -0
  88. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/health_checker.py +0 -0
  89. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/manage.py +0 -0
  90. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/service.py +0 -0
  91. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/utils/__init__.py +0 -0
  92. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/utils/mech.py +0 -0
  93. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/services/utils/tendermint.py +0 -0
  94. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/settings.py +0 -0
  95. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/utils/gnosis.py +0 -0
  96. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/utils/single_instance.py +0 -0
  97. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/utils/ssl.py +0 -0
  98. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/wallet/__init__.py +0 -0
  99. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/wallet/master.py +0 -0
  100. {olas_operate_middleware-0.13.6 → olas_operate_middleware-0.13.7}/operate/wallet/wallet_recovery_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: olas-operate-middleware
3
- Version: 0.13.6
3
+ Version: 0.13.7
4
4
  Summary:
5
5
  License-File: LICENSE
6
6
  Author: David Vilela
@@ -60,6 +60,7 @@ from operate.ledger.profiles import (
60
60
  from operate.operate_types import Chain, ChainAmounts, LedgerType, OnChainState
61
61
  from operate.services.protocol import EthSafeTxBuilder, StakingManager, StakingState
62
62
  from operate.services.service import NON_EXISTENT_TOKEN, Service
63
+ from operate.utils import concurrent_execute
63
64
  from operate.utils.gnosis import drain_eoa, get_asset_balance, get_owners
64
65
  from operate.utils.gnosis import transfer as transfer_from_safe
65
66
  from operate.utils.gnosis import transfer_erc20_from_safe
@@ -344,13 +345,27 @@ class FundingManager:
344
345
 
345
346
  # os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc # TODO do we need this?
346
347
 
347
- # Determine bonded native amount
348
- service_registry_address = CHAIN_PROFILES[chain]["service_registry"]
348
+ # Fetch on-chain data
349
349
  service_registry = registry_contracts.service_registry.get_instance(
350
350
  ledger_api=ledger_api,
351
- contract_address=service_registry_address,
351
+ contract_address=CHAIN_PROFILES[chain]["service_registry"],
352
+ )
353
+ (
354
+ service_info,
355
+ operator_balance,
356
+ current_staking_program,
357
+ ) = concurrent_execute(
358
+ (service_registry.functions.getService(service_id).call, ()),
359
+ (
360
+ service_registry.functions.getOperatorBalance(
361
+ master_safe, service_id
362
+ ).call,
363
+ (),
364
+ ),
365
+ (staking_manager.get_current_staking_program, (service_id,)),
352
366
  )
353
- service_info = service_registry.functions.getService(service_id).call()
367
+
368
+ # Determine bonded native amount
354
369
  security_deposit = service_info[0]
355
370
  service_state = service_info[6]
356
371
  agent_ids = service_info[7]
@@ -362,15 +377,9 @@ class FundingManager:
362
377
  ):
363
378
  bonded_assets[ZERO_ADDRESS] += security_deposit
364
379
 
365
- operator_balance = service_registry.functions.getOperatorBalance(
366
- master_safe, service_id
367
- ).call()
368
380
  bonded_assets[ZERO_ADDRESS] += operator_balance
369
381
 
370
382
  # Determine bonded token amount for staking programs
371
- current_staking_program = staking_manager.get_current_staking_program(
372
- service_id=service_id,
373
- )
374
383
  target_staking_program = user_params.staking_program_id
375
384
  staking_contract = staking_manager.get_staking_contract(
376
385
  staking_program_id=current_staking_program or target_staking_program,
@@ -381,9 +390,7 @@ class FundingManager:
381
390
 
382
391
  staking_manager = StakingManager(Chain(chain))
383
392
  staking_params = staking_manager.get_staking_params(
384
- staking_contract=staking_manager.get_staking_contract(
385
- staking_program_id=user_params.staking_program_id,
386
- ),
393
+ staking_contract=staking_contract,
387
394
  )
388
395
 
389
396
  service_registry_token_utility_address = staking_params[
@@ -396,24 +403,62 @@ class FundingManager:
396
403
  )
397
404
  )
398
405
 
406
+ (
407
+ *agent_instances_and_bonds,
408
+ token_bond,
409
+ security_deposits,
410
+ staking_state,
411
+ ) = concurrent_execute(
412
+ *(
413
+ [
414
+ (
415
+ service_registry.functions.getInstancesForAgentId(
416
+ service_id, agent_id
417
+ ).call,
418
+ (),
419
+ )
420
+ for agent_id in agent_ids
421
+ ]
422
+ + [
423
+ (
424
+ service_registry_token_utility.functions.getAgentBond(
425
+ service_id, agent_id
426
+ ).call,
427
+ (),
428
+ )
429
+ for agent_id in agent_ids
430
+ ]
431
+ + [
432
+ (
433
+ service_registry_token_utility.functions.getOperatorBalance(
434
+ master_safe, service_id
435
+ ).call,
436
+ (),
437
+ ),
438
+ (
439
+ service_registry_token_utility.functions.mapServiceIdTokenDeposit(
440
+ service_id
441
+ ).call,
442
+ (),
443
+ ),
444
+ (
445
+ staking_manager.staking_state,
446
+ (service_id, staking_params["staking_contract"]),
447
+ ),
448
+ ]
449
+ ),
450
+ )
451
+
399
452
  agent_bonds = 0
400
- for agent_id in agent_ids:
401
- num_agent_instances = service_registry.functions.getInstancesForAgentId(
402
- service_id, agent_id
403
- ).call()[0]
404
- agent_bond = service_registry_token_utility.functions.getAgentBond(
405
- service_id, agent_id
406
- ).call()
453
+ for agent_instances, agent_bond in zip(
454
+ agent_instances_and_bonds[: len(agent_ids)],
455
+ agent_instances_and_bonds[len(agent_ids) :],
456
+ ):
457
+ num_agent_instances = agent_instances[0]
407
458
  agent_bonds += num_agent_instances * agent_bond
408
459
 
409
460
  if service_state == OnChainState.TERMINATED_BONDED:
410
461
  num_agent_instances = service_info[5]
411
- token_bond = (
412
- service_registry_token_utility.functions.getOperatorBalance(
413
- master_safe,
414
- service_id,
415
- ).call()
416
- )
417
462
  agent_bonds += num_agent_instances * token_bond
418
463
 
419
464
  security_deposit = 0
@@ -422,20 +467,11 @@ class FundingManager:
422
467
  <= service_state
423
468
  < OnChainState.TERMINATED_BONDED
424
469
  ):
425
- security_deposit = (
426
- service_registry_token_utility.functions.mapServiceIdTokenDeposit(
427
- service_id
428
- ).call()[1]
429
- )
470
+ security_deposit = security_deposits[1]
430
471
 
431
472
  bonded_assets[staking_params["staking_token"]] += agent_bonds
432
473
  bonded_assets[staking_params["staking_token"]] += security_deposit
433
474
 
434
- staking_state = staking_manager.staking_state(
435
- service_id=service_id,
436
- staking_contract=staking_params["staking_contract"],
437
- )
438
-
439
475
  if staking_state in (StakingState.STAKED, StakingState.EVICTED):
440
476
  for token, amount in staking_params[
441
477
  "additional_staking_tokens"
@@ -564,40 +600,64 @@ class FundingManager:
564
600
 
565
601
  def _get_master_safe_balances(self, thresholds: ChainAmounts) -> ChainAmounts:
566
602
  output = ChainAmounts()
603
+ batch_calls_args = {}
567
604
  for chain_str, addresses in thresholds.items():
568
605
  chain = Chain(chain_str)
569
606
  master_safe = self._resolve_master_safe(chain)
570
- master_safe_dict = output.setdefault(chain_str, {}).setdefault(
571
- master_safe, {}
572
- )
607
+ output.setdefault(chain_str, {}).setdefault(master_safe, {})
573
608
  for _, assets in addresses.items():
574
609
  for asset, _ in assets.items():
575
- master_safe_dict[asset] = get_asset_balance(
576
- ledger_api=get_default_ledger_api(chain),
577
- asset_address=asset,
578
- address=master_safe,
579
- raise_on_invalid_address=False,
610
+ batch_calls_args[
611
+ (
612
+ get_default_ledger_api(chain),
613
+ asset,
614
+ master_safe,
615
+ False,
616
+ )
617
+ ] = (
618
+ chain_str,
619
+ master_safe,
620
+ asset,
580
621
  )
581
622
 
623
+ batch_calls_results = concurrent_execute(
624
+ *[(get_asset_balance, args) for args in batch_calls_args.keys()]
625
+ )
626
+ for args, balance in zip(batch_calls_args.keys(), batch_calls_results):
627
+ chain_str, master_safe, asset = batch_calls_args[args]
628
+ output[chain_str][master_safe][asset] = balance
629
+
582
630
  return output
583
631
 
584
632
  def _get_master_eoa_balances(self, thresholds: ChainAmounts) -> ChainAmounts:
585
633
  output = ChainAmounts()
634
+ batch_calls_args = {}
586
635
  for chain_str, addresses in thresholds.items():
587
636
  chain = Chain(chain_str)
588
637
  master_eoa = self._resolve_master_eoa(chain)
589
- master_eoa_dict = output.setdefault(chain_str, {}).setdefault(
590
- master_eoa, {}
591
- )
638
+ output.setdefault(chain_str, {}).setdefault(master_eoa, {})
592
639
  for _, assets in addresses.items():
593
640
  for asset, _ in assets.items():
594
- master_eoa_dict[asset] = get_asset_balance(
595
- ledger_api=get_default_ledger_api(chain),
596
- asset_address=asset,
597
- address=master_eoa,
598
- raise_on_invalid_address=False,
641
+ batch_calls_args[
642
+ (
643
+ get_default_ledger_api(chain),
644
+ asset,
645
+ master_eoa,
646
+ False,
647
+ )
648
+ ] = (
649
+ chain_str,
650
+ master_eoa,
651
+ asset,
599
652
  )
600
653
 
654
+ batch_calls_results = concurrent_execute(
655
+ *[(get_asset_balance, args) for args in batch_calls_args.keys()]
656
+ )
657
+ for args, balance in zip(batch_calls_args.keys(), batch_calls_results):
658
+ chain_str, master_eoa, asset = batch_calls_args[args]
659
+ output[chain_str][master_eoa][asset] = balance
660
+
601
661
  return output
602
662
 
603
663
  def fund_master_eoa(self) -> None:
@@ -623,12 +683,30 @@ class FundingManager:
623
683
  }
624
684
  )
625
685
  master_eoa_balances = self._get_master_eoa_balances(master_eoa_topups)
686
+ master_safe_balance = self._get_master_safe_balances(master_eoa_topups)
626
687
  master_eoa_shortfalls = self._compute_shortfalls(
627
688
  balances=master_eoa_balances,
628
689
  thresholds=master_eoa_topups * DEFAULT_EOA_THRESHOLD,
629
690
  topups=master_eoa_topups,
630
691
  )
631
- self.fund_chain_amounts(master_eoa_shortfalls)
692
+ possible_to_fund_shortfalls = ChainAmounts(
693
+ {
694
+ chain_str: {
695
+ address: {
696
+ asset: min(
697
+ amount,
698
+ master_safe_balance.get(chain_str, {})
699
+ .get(self._resolve_master_safe(Chain(chain_str)), {})
700
+ .get(asset, 0),
701
+ )
702
+ for asset, amount in assets.items()
703
+ }
704
+ for address, assets in addresses.items()
705
+ }
706
+ for chain_str, addresses in master_eoa_shortfalls.items()
707
+ }
708
+ )
709
+ self.fund_chain_amounts(possible_to_fund_shortfalls)
632
710
 
633
711
  def funding_requirements(self, service: Service) -> t.Dict:
634
712
  """Funding requirements"""
@@ -639,9 +717,13 @@ class FundingManager:
639
717
  total_requirements: ChainAmounts
640
718
  chains = [Chain(chain_str) for chain_str in service.chain_configs.keys()]
641
719
 
642
- # Protocol shortfall
643
- protocol_thresholds = self._compute_protocol_asset_requirements(service)
644
- protocol_balances = self._compute_protocol_bonded_assets(service)
720
+ (
721
+ protocol_thresholds,
722
+ protocol_balances,
723
+ ) = concurrent_execute(
724
+ (self._compute_protocol_asset_requirements, (service,)),
725
+ (self._compute_protocol_bonded_assets, (service,)),
726
+ )
645
727
  protocol_topups = protocol_thresholds
646
728
  protocol_shortfalls = self._compute_shortfalls(
647
729
  balances=protocol_balances,
@@ -80,6 +80,7 @@ from operate.ledger.profiles import CONTRACTS, STAKING
80
80
  from operate.operate_types import Chain as OperateChain
81
81
  from operate.operate_types import ContractAddresses
82
82
  from operate.services.service import NON_EXISTENT_TOKEN
83
+ from operate.utils import concurrent_execute
83
84
  from operate.utils.gnosis import (
84
85
  MultiSendOperation,
85
86
  SafeOperation,
@@ -226,37 +227,20 @@ class StakingManager:
226
227
  def _get_staking_params(chain: OperateChain, staking_contract: str) -> t.Dict:
227
228
  """Get staking params"""
228
229
  ledger_api = get_default_ledger_api(chain=chain)
229
- instance = StakingManager.staking_ctr.get_instance(
230
- ledger_api=ledger_api,
231
- contract_address=staking_contract,
230
+
231
+ second_token_func = (
232
+ lambda: None # pylint: disable=unnecessary-lambda-assignment # noqa: E731
232
233
  )
233
- agent_ids = instance.functions.getAgentIds().call()
234
- service_registry = instance.functions.serviceRegistry().call()
235
- staking_token = instance.functions.stakingToken().call()
236
- service_registry_token_utility = (
237
- instance.functions.serviceRegistryTokenUtility().call()
234
+ second_token_amount_func = (
235
+ lambda: None # pylint: disable=unnecessary-lambda-assignment # noqa: E731
238
236
  )
239
- min_staking_deposit = instance.functions.minStakingDeposit().call()
240
- activity_checker = instance.functions.activityChecker().call()
241
-
242
- output = {
243
- "staking_contract": staking_contract,
244
- "agent_ids": agent_ids,
245
- "service_registry": service_registry,
246
- "staking_token": staking_token,
247
- "service_registry_token_utility": service_registry_token_utility,
248
- "min_staking_deposit": min_staking_deposit,
249
- "activity_checker": activity_checker,
250
- "additional_staking_tokens": {},
251
- }
252
237
  try:
253
238
  instance = StakingManager.dual_staking_ctr.get_instance(
254
239
  ledger_api=ledger_api,
255
240
  contract_address=staking_contract,
256
241
  )
257
- output["additional_staking_tokens"][
258
- instance.functions.secondToken().call()
259
- ] = instance.functions.secondTokenAmount().call()
242
+ second_token_func = instance.functions.secondToken().call
243
+ second_token_amount_func = instance.functions.secondTokenAmount().call
260
244
  except Exception: # pylint: disable=broad-except # nosec
261
245
  # Contract is not a dual staking contract
262
246
 
@@ -269,6 +253,45 @@ class StakingManager:
269
253
  # avoid any issues we are simply catching all exceptions.
270
254
  pass
271
255
 
256
+ instance = StakingManager.staking_ctr.get_instance(
257
+ ledger_api=ledger_api,
258
+ contract_address=staking_contract,
259
+ )
260
+ (
261
+ agent_ids,
262
+ service_registry,
263
+ staking_token,
264
+ service_registry_token_utility,
265
+ min_staking_deposit,
266
+ activity_checker,
267
+ second_token,
268
+ second_token_amount,
269
+ ) = concurrent_execute(
270
+ (instance.functions.getAgentIds().call, ()),
271
+ (instance.functions.serviceRegistry().call, ()),
272
+ (instance.functions.stakingToken().call, ()),
273
+ (instance.functions.serviceRegistryTokenUtility().call, ()),
274
+ (instance.functions.minStakingDeposit().call, ()),
275
+ (instance.functions.activityChecker().call, ()),
276
+ (second_token_func, ()),
277
+ (second_token_amount_func, ()),
278
+ ignore_exceptions=True,
279
+ )
280
+
281
+ output = {
282
+ "staking_contract": staking_contract,
283
+ "agent_ids": agent_ids,
284
+ "service_registry": service_registry,
285
+ "staking_token": staking_token,
286
+ "service_registry_token_utility": service_registry_token_utility,
287
+ "min_staking_deposit": min_staking_deposit,
288
+ "activity_checker": activity_checker,
289
+ "additional_staking_tokens": (
290
+ {second_token: second_token_amount}
291
+ if second_token and second_token_amount
292
+ else {}
293
+ ),
294
+ }
272
295
  return output
273
296
 
274
297
  def get_staking_params(self, staking_contract: str) -> t.Dict:
@@ -19,14 +19,25 @@
19
19
 
20
20
  """Helper utilities."""
21
21
 
22
+ import asyncio
23
+ import inspect
24
+ import logging
22
25
  import os
23
26
  import platform
24
27
  import shutil
25
28
  import time
26
29
  import typing as t
30
+ from concurrent.futures import ThreadPoolExecutor
31
+ from concurrent.futures import TimeoutError as FuturesTimeoutError
32
+ from contextlib import contextmanager
27
33
  from pathlib import Path
28
34
  from threading import Lock
29
35
 
36
+ from operate.constants import DEFAULT_TIMEOUT
37
+
38
+
39
+ logger = logging.getLogger(__name__)
40
+
30
41
 
31
42
  class SingletonMeta(type):
32
43
  """A metaclass for creating thread-safe singleton classes."""
@@ -153,3 +164,100 @@ def unrecoverable_delete(file_path: Path, passes: int = 3) -> None:
153
164
  print(f"Permission denied to securely delete file '{file_path}'.")
154
165
  except Exception as e: # pylint: disable=broad-except
155
166
  print(f"Error during secure deletion of '{file_path}': {e}")
167
+
168
+
169
+ @contextmanager
170
+ def timing_context(label: str = "Block") -> t.Generator[None, None, None]:
171
+ """Context manager for timing a code block."""
172
+ start = time.perf_counter()
173
+ try:
174
+ yield
175
+ finally:
176
+ end = time.perf_counter()
177
+ logger.debug(f"[{label}] Elapsed time: {end - start:.4f} seconds")
178
+
179
+
180
+ def concurrent_execute(
181
+ *func_calls: t.Tuple[t.Callable, t.Tuple],
182
+ ignore_exceptions: bool = False,
183
+ ) -> t.List[t.Any]:
184
+ """Execute callables concurrently.
185
+
186
+ This is a synchronous convenience wrapper around `parallel_execute_async`.
187
+ If called from within an active asyncio event loop, use
188
+ `await parallel_execute_async(...)` instead.
189
+ """
190
+
191
+ async def _runner() -> t.List[t.Any]:
192
+ return await concurrent_execute_async(
193
+ *func_calls,
194
+ ignore_exceptions=ignore_exceptions,
195
+ )
196
+
197
+ try:
198
+ asyncio.get_running_loop()
199
+ except RuntimeError:
200
+ # No running loop in this thread.
201
+ return asyncio.run(_runner())
202
+
203
+ # Running inside an event loop thread.
204
+ # We cannot call `asyncio.run` here, so offload to a background thread.
205
+ # NOTE: this blocks the current thread until completion.
206
+ with ThreadPoolExecutor(max_workers=1) as executor:
207
+ future = executor.submit(asyncio.run, _runner())
208
+ return future.result()
209
+
210
+
211
+ async def concurrent_execute_async(
212
+ *func_calls: t.Tuple[t.Callable, t.Tuple],
213
+ ignore_exceptions: bool = False,
214
+ ) -> t.List[t.Any]:
215
+ """Execute callables concurrently using asyncio.
216
+
217
+ - Async callables are awaited directly.
218
+ - Sync callables are executed via `asyncio.to_thread`.
219
+
220
+ Results are returned in the same order as `funcs`/`args_list`.
221
+ """
222
+
223
+ async def _invoke(func: t.Callable, args: t.Tuple) -> t.Any:
224
+ with timing_context(f"Executing {func.__name__}"):
225
+ if inspect.iscoroutinefunction(func):
226
+ return await t.cast(t.Awaitable[t.Any], func(*args))
227
+ return await asyncio.to_thread(func, *args)
228
+
229
+ results: t.List[t.Any] = [None] * len(func_calls)
230
+
231
+ async def _invoke_indexed(
232
+ idx: int, func: t.Callable, args: t.Tuple
233
+ ) -> t.Tuple[int, t.Any]:
234
+ try:
235
+ return idx, await _invoke(func, args)
236
+ except Exception as e: # pylint: disable=broad-except
237
+ return idx, e
238
+
239
+ tasks: t.List[asyncio.Task] = [
240
+ asyncio.create_task(_invoke_indexed(idx, func, args))
241
+ for idx, (func, args) in enumerate(func_calls)
242
+ ]
243
+
244
+ try:
245
+ for task in asyncio.as_completed(tasks, timeout=DEFAULT_TIMEOUT):
246
+ idx, outcome = await task
247
+ if isinstance(outcome, BaseException):
248
+ if ignore_exceptions:
249
+ results[idx] = None
250
+ else:
251
+ raise outcome
252
+ else:
253
+ results[idx] = outcome
254
+ except asyncio.TimeoutError as e:
255
+ raise FuturesTimeoutError() from e
256
+ finally:
257
+ # Ensure we don't leak pending tasks.
258
+ for task in tasks:
259
+ if not task.done():
260
+ task.cancel()
261
+ await asyncio.gather(*tasks, return_exceptions=True)
262
+
263
+ return results
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "olas-operate-middleware"
3
- version = "0.13.6"
3
+ version = "0.13.7"
4
4
  description = ""
5
5
  authors = ["David Vilela <dvilelaf@gmail.com>", "Viraj Patel <vptl185@gmail.com>"]
6
6
  readme = "README.md"
@@ -40,6 +40,8 @@ cryptography = "^46.0.3"
40
40
  [tool.poetry.group.development.dependencies]
41
41
  tomte = {version = "0.4.0", extras = ["cli"]}
42
42
  build = "1.2.2.post1"
43
+ yappi = "^1.6.10"
44
+ viztracer = "^1.1.1"
43
45
 
44
46
  [build-system]
45
47
  requires = ["poetry-core"]