iwa 0.0.58__py3-none-any.whl → 0.0.60__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.
- iwa/core/chain/interface.py +118 -53
- iwa/core/chain/rate_limiter.py +35 -12
- iwa/core/chainlist.py +15 -10
- iwa/core/cli.py +3 -0
- iwa/core/contracts/cache.py +1 -1
- iwa/core/contracts/contract.py +1 -0
- iwa/core/contracts/decoder.py +10 -4
- iwa/core/http.py +31 -0
- iwa/core/ipfs.py +11 -19
- iwa/core/keys.py +10 -4
- iwa/core/models.py +1 -3
- iwa/core/pricing.py +3 -21
- iwa/core/rpc_monitor.py +1 -0
- iwa/core/services/balance.py +0 -1
- iwa/core/services/safe.py +8 -2
- iwa/core/services/safe_executor.py +52 -18
- iwa/core/services/transaction.py +32 -12
- iwa/core/services/transfer/erc20.py +0 -1
- iwa/core/services/transfer/native.py +1 -1
- iwa/core/tests/test_gnosis_fee.py +6 -2
- iwa/core/tests/test_ipfs.py +1 -1
- iwa/core/tests/test_regression_fixes.py +3 -6
- iwa/core/utils.py +2 -0
- iwa/core/wallet.py +3 -1
- iwa/plugins/olas/constants.py +15 -5
- iwa/plugins/olas/contracts/activity_checker.py +3 -3
- iwa/plugins/olas/contracts/staking.py +0 -1
- iwa/plugins/olas/events.py +15 -13
- iwa/plugins/olas/importer.py +26 -20
- iwa/plugins/olas/plugin.py +16 -14
- iwa/plugins/olas/service_manager/drain.py +1 -3
- iwa/plugins/olas/service_manager/lifecycle.py +9 -9
- iwa/plugins/olas/service_manager/staking.py +11 -6
- iwa/plugins/olas/tests/test_olas_archiving.py +25 -15
- iwa/plugins/olas/tests/test_olas_integration.py +49 -29
- iwa/plugins/olas/tests/test_service_manager.py +8 -10
- iwa/plugins/olas/tests/test_service_manager_errors.py +5 -4
- iwa/plugins/olas/tests/test_service_manager_flows.py +6 -5
- iwa/plugins/olas/tests/test_service_staking.py +64 -38
- iwa/tools/drain_accounts.py +2 -1
- iwa/tools/reset_env.py +2 -1
- iwa/tools/test_chainlist.py +5 -1
- iwa/tui/screens/wallets.py +1 -3
- iwa/web/routers/olas/services.py +10 -5
- {iwa-0.0.58.dist-info → iwa-0.0.60.dist-info}/METADATA +1 -1
- {iwa-0.0.58.dist-info → iwa-0.0.60.dist-info}/RECORD +60 -59
- tests/test_balance_service.py +0 -2
- tests/test_chain.py +1 -2
- tests/test_chain_interface.py +3 -3
- tests/test_rate_limiter.py +7 -5
- tests/test_rate_limiter_retry.py +34 -33
- tests/test_rpc_efficiency.py +4 -1
- tests/test_rpc_rate_limit.py +4 -3
- tests/test_rpc_rotation.py +4 -4
- tests/test_safe_executor.py +76 -50
- tests/test_safe_integration.py +11 -6
- {iwa-0.0.58.dist-info → iwa-0.0.60.dist-info}/WHEEL +0 -0
- {iwa-0.0.58.dist-info → iwa-0.0.60.dist-info}/entry_points.txt +0 -0
- {iwa-0.0.58.dist-info → iwa-0.0.60.dist-info}/licenses/LICENSE +0 -0
- {iwa-0.0.58.dist-info → iwa-0.0.60.dist-info}/top_level.txt +0 -0
iwa/plugins/olas/importer.py
CHANGED
|
@@ -181,9 +181,7 @@ class OlasServiceImporter:
|
|
|
181
181
|
if service.service_id:
|
|
182
182
|
key = f"{service.chain_name}:{service.service_id}"
|
|
183
183
|
if key in seen_keys:
|
|
184
|
-
logger.debug(
|
|
185
|
-
f"Skipping duplicate service {key} from {service.source_folder}"
|
|
186
|
-
)
|
|
184
|
+
logger.debug(f"Skipping duplicate service {key} from {service.source_folder}")
|
|
187
185
|
duplicates += 1
|
|
188
186
|
continue
|
|
189
187
|
seen_keys.add(key)
|
|
@@ -482,9 +480,7 @@ class OlasServiceImporter:
|
|
|
482
480
|
staking_program_id, chain_name
|
|
483
481
|
)
|
|
484
482
|
|
|
485
|
-
def _resolve_staking_contract(
|
|
486
|
-
self, staking_program_id: str, chain_name: str
|
|
487
|
-
) -> Optional[str]:
|
|
483
|
+
def _resolve_staking_contract(self, staking_program_id: str, chain_name: str) -> Optional[str]:
|
|
488
484
|
"""Resolve a staking program ID to a contract address."""
|
|
489
485
|
address = STAKING_PROGRAM_MAP.get(staking_program_id)
|
|
490
486
|
if address:
|
|
@@ -540,8 +536,10 @@ class OlasServiceImporter:
|
|
|
540
536
|
|
|
541
537
|
# Check for "safes" entry which indicates the owner is a Safe
|
|
542
538
|
# Structure: "safes": { "gnosis": "0x..." }
|
|
543
|
-
if
|
|
544
|
-
|
|
539
|
+
if (
|
|
540
|
+
"safes" in data and FLAGS_OWNER_SAFE in data["safes"]
|
|
541
|
+
): # Need to detect chain dynamically or iterate
|
|
542
|
+
pass
|
|
545
543
|
|
|
546
544
|
# Logic update:
|
|
547
545
|
# 1. Capture EOA address always (it's the signer)
|
|
@@ -557,9 +555,13 @@ class OlasServiceImporter:
|
|
|
557
555
|
if safe_owner_address:
|
|
558
556
|
# CASE: Owner is Safe
|
|
559
557
|
service.service_owner_multisig_address = safe_owner_address
|
|
560
|
-
service.service_owner_eoa_address =
|
|
558
|
+
service.service_owner_eoa_address = (
|
|
559
|
+
eoa_address # The EOA is the signer/controller
|
|
560
|
+
)
|
|
561
561
|
|
|
562
|
-
logger.debug(
|
|
562
|
+
logger.debug(
|
|
563
|
+
f"Extracted Safe owner address: {safe_owner_address} (Signer: {eoa_address})"
|
|
564
|
+
)
|
|
563
565
|
elif eoa_address:
|
|
564
566
|
# CASE: Owner is EOA
|
|
565
567
|
service.service_owner_eoa_address = eoa_address
|
|
@@ -767,8 +769,8 @@ class OlasServiceImporter:
|
|
|
767
769
|
safe_result = self._import_safe(
|
|
768
770
|
address=service.safe_address,
|
|
769
771
|
signers=self._get_agent_signers(service),
|
|
770
|
-
tag_suffix="multisig",
|
|
771
|
-
service_name=service.service_name
|
|
772
|
+
tag_suffix="multisig", # e.g. trader_zeta_safe
|
|
773
|
+
service_name=service.service_name,
|
|
772
774
|
)
|
|
773
775
|
if safe_result[0]:
|
|
774
776
|
result.imported_safes.append(service.safe_address)
|
|
@@ -778,19 +780,22 @@ class OlasServiceImporter:
|
|
|
778
780
|
result.errors.append(f"Safe {service.safe_address}: {safe_result[1]}")
|
|
779
781
|
|
|
780
782
|
# 2. Import Owner Safe (if it exists and is different)
|
|
781
|
-
if
|
|
782
|
-
|
|
783
|
+
if (
|
|
784
|
+
service.service_owner_multisig_address
|
|
785
|
+
and service.service_owner_multisig_address != service.safe_address
|
|
786
|
+
):
|
|
787
|
+
# Signer for Owner Safe is the EOA owner key
|
|
783
788
|
owner_signers = self._get_owner_signers(service)
|
|
784
789
|
|
|
785
790
|
safe_result = self._import_safe(
|
|
786
791
|
address=service.service_owner_multisig_address,
|
|
787
792
|
signers=owner_signers,
|
|
788
|
-
tag_suffix="owner_multisig",
|
|
789
|
-
service_name=service.service_name
|
|
793
|
+
tag_suffix="owner_multisig", # e.g. trader_zeta_owner_safe
|
|
794
|
+
service_name=service.service_name,
|
|
790
795
|
)
|
|
791
796
|
if safe_result[0]:
|
|
792
|
-
|
|
793
|
-
|
|
797
|
+
result.imported_safes.append(service.service_owner_multisig_address)
|
|
798
|
+
logger.info(f"Imported Owner Safe {service.service_owner_multisig_address}")
|
|
794
799
|
|
|
795
800
|
def _get_agent_signers(self, service: DiscoveredService) -> List[str]:
|
|
796
801
|
"""Get list of signers for the agent safe."""
|
|
@@ -926,7 +931,7 @@ class OlasServiceImporter:
|
|
|
926
931
|
address: str,
|
|
927
932
|
signers: List[str] = None,
|
|
928
933
|
tag_suffix: str = "multisig",
|
|
929
|
-
service_name: Optional[str] = None
|
|
934
|
+
service_name: Optional[str] = None,
|
|
930
935
|
) -> Tuple[bool, str]:
|
|
931
936
|
"""Import a generic Safe."""
|
|
932
937
|
if not address:
|
|
@@ -1062,4 +1067,5 @@ class OlasServiceImporter:
|
|
|
1062
1067
|
key.signature_failed = True
|
|
1063
1068
|
logger.warning(f"Error verifying signature for key {key.address}: {e}")
|
|
1064
1069
|
|
|
1065
|
-
|
|
1070
|
+
|
|
1071
|
+
FLAGS_OWNER_SAFE = "deprecated"
|
iwa/plugins/olas/plugin.py
CHANGED
|
@@ -151,7 +151,9 @@ class OlasPlugin(Plugin):
|
|
|
151
151
|
|
|
152
152
|
is_signer = key_addr in [s.lower() for s in on_chain_signers]
|
|
153
153
|
if not is_signer:
|
|
154
|
-
safe_text +=
|
|
154
|
+
safe_text += (
|
|
155
|
+
f"\n[bold red]⚠ Agent {agent_key.address} - NOT A SIGNER![/bold red]"
|
|
156
|
+
)
|
|
155
157
|
else:
|
|
156
158
|
safe_text += f" (Signer: {agent_key.address[:6]}...)"
|
|
157
159
|
|
|
@@ -182,21 +184,21 @@ class OlasPlugin(Plugin):
|
|
|
182
184
|
# 1. Display Signer/EOA Owner
|
|
183
185
|
owner_key = next((k for k in service.keys if k.role == "owner"), None)
|
|
184
186
|
if owner_key:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
187
|
+
val = owner_key.address
|
|
188
|
+
if not val.startswith("0x"):
|
|
189
|
+
val = "0x" + val
|
|
190
|
+
|
|
191
|
+
if owner_key.signature_verified:
|
|
192
|
+
val = f"[green]{val}[/green]"
|
|
193
|
+
elif not owner_key.is_encrypted:
|
|
194
|
+
val = f"[red]{val}[/red]"
|
|
195
|
+
status = "🔒 encrypted" if owner_key.is_encrypted else "🔓 plaintext"
|
|
196
|
+
table.add_row("Owner (EOA)", f"{val} {status}")
|
|
195
197
|
elif service.service_owner_eoa_address:
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
# Fallback if we have an address but no key object
|
|
199
|
+
table.add_row("Owner (EOA)", service.service_owner_eoa_address)
|
|
198
200
|
else:
|
|
199
|
-
|
|
201
|
+
table.add_row("Owner (EOA)", "[yellow]N/A[/yellow]")
|
|
200
202
|
|
|
201
203
|
# 2. Display Safe Owner
|
|
202
204
|
if service.service_owner_multisig_address:
|
|
@@ -135,9 +135,7 @@ class DrainManagerMixin:
|
|
|
135
135
|
withdrawal_tag = self.wallet.get_tag_by_address(withdrawal_address) or withdrawal_address
|
|
136
136
|
multisig_tag = self.wallet.get_tag_by_address(multisig_address) or multisig_address
|
|
137
137
|
|
|
138
|
-
logger.info(
|
|
139
|
-
f"Withdrawing {olas_amount:.4f} OLAS from {multisig_tag} to {withdrawal_tag}"
|
|
140
|
-
)
|
|
138
|
+
logger.info(f"Withdrawing {olas_amount:.4f} OLAS from {multisig_tag} to {withdrawal_tag}")
|
|
141
139
|
|
|
142
140
|
# Transfer from Safe to withdrawal address
|
|
143
141
|
tx_hash = self.wallet.send(
|
|
@@ -429,9 +429,7 @@ class LifecycleManagerMixin:
|
|
|
429
429
|
)
|
|
430
430
|
|
|
431
431
|
if balance < bond_amount:
|
|
432
|
-
logger.error(
|
|
433
|
-
f"[ACTIVATE] FAIL: Owner balance {balance} < required {bond_amount}"
|
|
434
|
-
)
|
|
432
|
+
logger.error(f"[ACTIVATE] FAIL: Owner balance {balance} < required {bond_amount}")
|
|
435
433
|
return False
|
|
436
434
|
|
|
437
435
|
protocol_contracts = OLAS_CONTRACTS.get(self.chain_name.lower(), {})
|
|
@@ -682,7 +680,9 @@ class LifecycleManagerMixin:
|
|
|
682
680
|
)
|
|
683
681
|
return True
|
|
684
682
|
|
|
685
|
-
logger.info(
|
|
683
|
+
logger.info(
|
|
684
|
+
f"[REGISTER] Service Owner approving Token Utility for bond: {bond_amount_wei} wei"
|
|
685
|
+
)
|
|
686
686
|
|
|
687
687
|
utility_address = str(
|
|
688
688
|
OLAS_CONTRACTS[self.chain_name]["OLAS_SERVICE_REGISTRY_TOKEN_UTILITY"]
|
|
@@ -697,9 +697,7 @@ class LifecycleManagerMixin:
|
|
|
697
697
|
)
|
|
698
698
|
|
|
699
699
|
if allowance >= bond_amount_wei:
|
|
700
|
-
logger.debug(
|
|
701
|
-
f"[REGISTER] Sufficient allowance ({allowance} >= {bond_amount_wei})"
|
|
702
|
-
)
|
|
700
|
+
logger.debug(f"[REGISTER] Sufficient allowance ({allowance} >= {bond_amount_wei})")
|
|
703
701
|
return True
|
|
704
702
|
|
|
705
703
|
# Use service owner which holds the OLAS tokens (not necessarily master)
|
|
@@ -800,7 +798,9 @@ class LifecycleManagerMixin:
|
|
|
800
798
|
)
|
|
801
799
|
return False
|
|
802
800
|
|
|
803
|
-
logger.debug(
|
|
801
|
+
logger.debug(
|
|
802
|
+
f"[DEPLOY] Preparing deploy tx for owner {self._get_label(self.service.service_owner_address)}"
|
|
803
|
+
)
|
|
804
804
|
deploy_tx = self.manager.prepare_deploy_tx(
|
|
805
805
|
from_address=self.service.service_owner_address,
|
|
806
806
|
service_id=self.service.service_id,
|
|
@@ -855,7 +855,7 @@ class LifecycleManagerMixin:
|
|
|
855
855
|
_, agent_instances = self.registry.call("getAgentInstances", self.service.service_id)
|
|
856
856
|
service_info = self.registry.get_service(self.service.service_id)
|
|
857
857
|
threshold = service_info["threshold"]
|
|
858
|
-
|
|
858
|
+
# Store the multisig in the wallet with tag
|
|
859
859
|
multisig_tag = f"{self.service.service_name}_multisig"
|
|
860
860
|
|
|
861
861
|
# ARCHIVING LOGIC: If tag is already taken by a different address, rename the old one
|
|
@@ -90,6 +90,7 @@ class StakingManagerMixin:
|
|
|
90
90
|
# Try token/contract names
|
|
91
91
|
try:
|
|
92
92
|
from iwa.core.chain import ChainInterfaces
|
|
93
|
+
|
|
93
94
|
chain_interface = ChainInterfaces().get(self.chain_name)
|
|
94
95
|
token_name = chain_interface.chain.get_token_name(address)
|
|
95
96
|
if token_name:
|
|
@@ -407,7 +408,9 @@ class StakingManagerMixin:
|
|
|
407
408
|
# NOTE: We don't check OLAS balance here because OLAS was already
|
|
408
409
|
# deposited to the Token Utility during activation (min_staking_deposit)
|
|
409
410
|
# and registration (agent_bond). The staking contract pulls from there.
|
|
410
|
-
logger.debug(
|
|
411
|
+
logger.debug(
|
|
412
|
+
"[STAKE] OLAS already deposited to Token Utility during activation/registration"
|
|
413
|
+
)
|
|
411
414
|
|
|
412
415
|
return {"min_deposit": min_deposit, "staking_token": staking_token}
|
|
413
416
|
|
|
@@ -437,7 +440,9 @@ class StakingManagerMixin:
|
|
|
437
440
|
owner_address = self.service.service_owner_address or self.wallet.master_account.address
|
|
438
441
|
|
|
439
442
|
# Approve service NFT - this is an ERC-721 approval, not ERC-20
|
|
440
|
-
logger.debug(
|
|
443
|
+
logger.debug(
|
|
444
|
+
f"[STAKE] Approving service NFT for staking contract from {self._get_label(owner_address)}..."
|
|
445
|
+
)
|
|
441
446
|
approve_tx = self.registry.prepare_approve_tx(
|
|
442
447
|
from_address=owner_address,
|
|
443
448
|
spender=staking_contract.address,
|
|
@@ -483,7 +488,9 @@ class StakingManagerMixin:
|
|
|
483
488
|
# Use service owner which holds the NFT (not necessarily master)
|
|
484
489
|
owner_address = self.service.service_owner_address or self.wallet.master_account.address
|
|
485
490
|
|
|
486
|
-
logger.debug(
|
|
491
|
+
logger.debug(
|
|
492
|
+
f"[STAKE] Preparing stake transaction from {self._get_label(owner_address)}..."
|
|
493
|
+
)
|
|
487
494
|
stake_tx = staking_contract.prepare_stake_tx(
|
|
488
495
|
from_address=owner_address,
|
|
489
496
|
service_id=self.service.service_id,
|
|
@@ -601,9 +608,7 @@ class StakingManagerMixin:
|
|
|
601
608
|
return False
|
|
602
609
|
|
|
603
610
|
tx_hash = get_tx_hash(receipt)
|
|
604
|
-
logger.info(
|
|
605
|
-
f"Unstake transaction sent: {tx_hash if receipt else 'No Receipt'}"
|
|
606
|
-
)
|
|
611
|
+
logger.info(f"Unstake transaction sent: {tx_hash if receipt else 'No Receipt'}")
|
|
607
612
|
|
|
608
613
|
events = staking_contract.extract_events(receipt)
|
|
609
614
|
|
|
@@ -20,10 +20,12 @@ class TestOlasArchiving(unittest.TestCase):
|
|
|
20
20
|
self.mock_wallet.key_storage = self.mock_key_storage
|
|
21
21
|
|
|
22
22
|
# Initialize ServiceManager
|
|
23
|
-
with
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
with (
|
|
24
|
+
patch("iwa.core.models.Config"),
|
|
25
|
+
patch("iwa.plugins.olas.service_manager.base.ChainInterfaces"),
|
|
26
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache"),
|
|
27
|
+
patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract"),
|
|
28
|
+
):
|
|
27
29
|
self.sm = ServiceManager(self.mock_wallet)
|
|
28
30
|
self.sm.service = Service(service_name="trader_psi", chain_name="gnosis", service_id=1)
|
|
29
31
|
self.sm.chain_name = "gnosis"
|
|
@@ -40,23 +42,30 @@ class TestOlasArchiving(unittest.TestCase):
|
|
|
40
42
|
|
|
41
43
|
# 2. Mock required contract calls for deploy()
|
|
42
44
|
self.sm.registry.get_service.return_value = {
|
|
43
|
-
"state": ServiceState.FINISHED_REGISTRATION,
|
|
45
|
+
"state": ServiceState.FINISHED_REGISTRATION, # DEPLOYED
|
|
44
46
|
"security_deposit": 0,
|
|
45
47
|
"multisig": new_address,
|
|
46
48
|
"threshold": 1,
|
|
47
|
-
"configHash": b"\x00" * 32
|
|
49
|
+
"configHash": b"\x00" * 32,
|
|
48
50
|
}
|
|
49
|
-
self.sm.registry.call.return_value = (None, [])
|
|
51
|
+
self.sm.registry.call.return_value = (None, []) # getAgentInstances
|
|
50
52
|
|
|
51
53
|
# 3. Trigger deploy (archiving happens here)
|
|
52
|
-
with
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
with (
|
|
55
|
+
patch.object(
|
|
56
|
+
self.mock_wallet, "sign_and_send_transaction", return_value=(True, {"status": 1})
|
|
57
|
+
),
|
|
58
|
+
patch.object(
|
|
59
|
+
self.sm.registry,
|
|
60
|
+
"extract_events",
|
|
61
|
+
return_value=[
|
|
62
|
+
{"name": "DeployService", "args": {}},
|
|
63
|
+
{"name": "CreateMultisigWithAgents", "args": {"multisig": new_address}},
|
|
64
|
+
],
|
|
65
|
+
),
|
|
66
|
+
patch("iwa.plugins.olas.service_manager.lifecycle.get_tx_hash", return_value="0xhash"),
|
|
67
|
+
patch("iwa.core.models.StoredSafeAccount") as mock_safe_cls,
|
|
68
|
+
):
|
|
60
69
|
self.sm.deploy(fund_multisig=False)
|
|
61
70
|
|
|
62
71
|
# 4. Verify rename_account was called for the old address
|
|
@@ -69,5 +78,6 @@ class TestOlasArchiving(unittest.TestCase):
|
|
|
69
78
|
self.assertEqual(kwargs["address"], new_address)
|
|
70
79
|
self.assertEqual(kwargs["tag"], multisig_tag)
|
|
71
80
|
|
|
81
|
+
|
|
72
82
|
if __name__ == "__main__":
|
|
73
83
|
unittest.main()
|
|
@@ -309,9 +309,11 @@ def test_importer_import_service_config_duplicate(mock_wallet):
|
|
|
309
309
|
|
|
310
310
|
def test_sm_create_token_utility_missing(mock_wallet):
|
|
311
311
|
"""Cover create() with missing token utility (lines 204-206)."""
|
|
312
|
-
with
|
|
313
|
-
|
|
314
|
-
|
|
312
|
+
with (
|
|
313
|
+
patch("iwa.core.models.Config"),
|
|
314
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
315
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
316
|
+
):
|
|
315
317
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
316
318
|
sm = ServiceManager(mock_wallet)
|
|
317
319
|
|
|
@@ -323,9 +325,11 @@ def test_sm_create_token_utility_missing(mock_wallet):
|
|
|
323
325
|
|
|
324
326
|
def test_sm_get_staking_status_staked_info_fail(mock_wallet):
|
|
325
327
|
"""Cover get_staking_status with STAKED but get_service_info fails (lines 843-854)."""
|
|
326
|
-
with
|
|
327
|
-
|
|
328
|
-
|
|
328
|
+
with (
|
|
329
|
+
patch("iwa.core.models.Config"),
|
|
330
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
331
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
332
|
+
):
|
|
329
333
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
330
334
|
sm = ServiceManager(mock_wallet)
|
|
331
335
|
sm.service = Service(
|
|
@@ -347,9 +351,11 @@ def test_sm_get_staking_status_staked_info_fail(mock_wallet):
|
|
|
347
351
|
|
|
348
352
|
def test_sm_call_checkpoint_prepare_fail(mock_wallet):
|
|
349
353
|
"""Cover call_checkpoint prepare failure (lines 1100-1102)."""
|
|
350
|
-
with
|
|
351
|
-
|
|
352
|
-
|
|
354
|
+
with (
|
|
355
|
+
patch("iwa.core.models.Config"),
|
|
356
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
357
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
358
|
+
):
|
|
353
359
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
354
360
|
sm = ServiceManager(mock_wallet)
|
|
355
361
|
sm.service = Service(
|
|
@@ -369,9 +375,11 @@ def test_sm_call_checkpoint_prepare_fail(mock_wallet):
|
|
|
369
375
|
|
|
370
376
|
def test_sm_spin_up_no_service(mock_wallet):
|
|
371
377
|
"""Cover spin_up with no service (lines 1167-1170)."""
|
|
372
|
-
with
|
|
373
|
-
|
|
374
|
-
|
|
378
|
+
with (
|
|
379
|
+
patch("iwa.core.models.Config"),
|
|
380
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
381
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
382
|
+
):
|
|
375
383
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
376
384
|
sm = ServiceManager(mock_wallet)
|
|
377
385
|
sm.service = None
|
|
@@ -382,9 +390,11 @@ def test_sm_spin_up_no_service(mock_wallet):
|
|
|
382
390
|
|
|
383
391
|
def test_sm_spin_up_activation_fail(mock_wallet):
|
|
384
392
|
"""Cover spin_up activation failure (lines 1181-1183)."""
|
|
385
|
-
with
|
|
386
|
-
|
|
387
|
-
|
|
393
|
+
with (
|
|
394
|
+
patch("iwa.core.models.Config"),
|
|
395
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
396
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
397
|
+
):
|
|
388
398
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
389
399
|
sm = ServiceManager(mock_wallet)
|
|
390
400
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
@@ -402,9 +412,11 @@ def test_sm_spin_up_activation_fail(mock_wallet):
|
|
|
402
412
|
|
|
403
413
|
def test_sm_wind_down_no_service(mock_wallet):
|
|
404
414
|
"""Cover wind_down with no service (lines 1264-1266)."""
|
|
405
|
-
with
|
|
406
|
-
|
|
407
|
-
|
|
415
|
+
with (
|
|
416
|
+
patch("iwa.core.models.Config"),
|
|
417
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
418
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
419
|
+
):
|
|
408
420
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
409
421
|
sm = ServiceManager(mock_wallet)
|
|
410
422
|
sm.service = None
|
|
@@ -415,9 +427,11 @@ def test_sm_wind_down_no_service(mock_wallet):
|
|
|
415
427
|
|
|
416
428
|
def test_sm_wind_down_nonexistent(mock_wallet):
|
|
417
429
|
"""Cover wind_down with non-existent service (lines 1274-1276)."""
|
|
418
|
-
with
|
|
419
|
-
|
|
420
|
-
|
|
430
|
+
with (
|
|
431
|
+
patch("iwa.core.models.Config"),
|
|
432
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
433
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
434
|
+
):
|
|
421
435
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
422
436
|
sm = ServiceManager(mock_wallet)
|
|
423
437
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
@@ -432,9 +446,11 @@ def test_sm_wind_down_nonexistent(mock_wallet):
|
|
|
432
446
|
|
|
433
447
|
def test_sm_mech_request_no_service(mock_wallet):
|
|
434
448
|
"""Cover _send_legacy_mech_request with no service (lines 1502-1504)."""
|
|
435
|
-
with
|
|
436
|
-
|
|
437
|
-
|
|
449
|
+
with (
|
|
450
|
+
patch("iwa.core.models.Config"),
|
|
451
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
452
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
453
|
+
):
|
|
438
454
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
439
455
|
sm = ServiceManager(mock_wallet)
|
|
440
456
|
sm.service = None
|
|
@@ -445,9 +461,11 @@ def test_sm_mech_request_no_service(mock_wallet):
|
|
|
445
461
|
|
|
446
462
|
def test_sm_mech_request_no_address(mock_wallet):
|
|
447
463
|
"""Cover _send_legacy_mech_request missing mech address (lines 1510-1512)."""
|
|
448
|
-
with
|
|
449
|
-
|
|
450
|
-
|
|
464
|
+
with (
|
|
465
|
+
patch("iwa.core.models.Config"),
|
|
466
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
467
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
468
|
+
):
|
|
451
469
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
452
470
|
sm = ServiceManager(mock_wallet)
|
|
453
471
|
sm.service = Service(service_name="t", chain_name="unknown", service_id=1)
|
|
@@ -458,8 +476,10 @@ def test_sm_mech_request_no_address(mock_wallet):
|
|
|
458
476
|
|
|
459
477
|
def test_sm_marketplace_mech_no_service(mock_wallet):
|
|
460
478
|
"""Cover _send_marketplace_mech_request with no service (lines 1549-1551)."""
|
|
461
|
-
with
|
|
462
|
-
|
|
479
|
+
with (
|
|
480
|
+
patch("iwa.core.models.Config"),
|
|
481
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache"),
|
|
482
|
+
):
|
|
463
483
|
sm = ServiceManager(mock_wallet)
|
|
464
484
|
sm.service = None
|
|
465
485
|
|
|
@@ -104,8 +104,6 @@ def mock_chain_interfaces():
|
|
|
104
104
|
yield mock
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
107
|
@pytest.fixture
|
|
110
108
|
def service_manager(
|
|
111
109
|
mock_config,
|
|
@@ -113,13 +111,14 @@ def service_manager(
|
|
|
113
111
|
mock_registry,
|
|
114
112
|
mock_manager_contract,
|
|
115
113
|
mock_chain_interfaces,
|
|
116
|
-
|
|
117
114
|
mock_olas_config,
|
|
118
115
|
mock_service,
|
|
119
116
|
):
|
|
120
117
|
"""ServiceManager fixture with mocked dependencies."""
|
|
121
|
-
with
|
|
122
|
-
|
|
118
|
+
with (
|
|
119
|
+
patch("iwa.plugins.olas.service_manager.base.Config") as local_mock_config,
|
|
120
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
121
|
+
):
|
|
123
122
|
instance = local_mock_config.return_value
|
|
124
123
|
instance.plugins = {"olas": mock_olas_config}
|
|
125
124
|
instance.save_config = MagicMock()
|
|
@@ -136,7 +135,6 @@ def service_manager(
|
|
|
136
135
|
yield sm
|
|
137
136
|
|
|
138
137
|
|
|
139
|
-
|
|
140
138
|
def test_init(service_manager):
|
|
141
139
|
"""Test initialization."""
|
|
142
140
|
assert service_manager.registry is not None
|
|
@@ -179,9 +177,7 @@ def test_create_no_event(service_manager, mock_wallet):
|
|
|
179
177
|
mock_wallet.sign_and_send_transaction.return_value = (True, {})
|
|
180
178
|
service_manager.registry.extract_events.return_value = []
|
|
181
179
|
|
|
182
|
-
res = service_manager.create(
|
|
183
|
-
token_address_or_tag="0x1111111111111111111111111111111111111111"
|
|
184
|
-
)
|
|
180
|
+
res = service_manager.create(token_address_or_tag="0x1111111111111111111111111111111111111111")
|
|
185
181
|
# create() finds no ID, logs error, returns None for service_id.
|
|
186
182
|
assert res is None
|
|
187
183
|
|
|
@@ -911,7 +907,9 @@ def test_activate_registration_token_service_sends_security_deposit_as_value(
|
|
|
911
907
|
|
|
912
908
|
# Mock balance check to pass
|
|
913
909
|
mock_wallet.balance_service = MagicMock()
|
|
914
|
-
mock_wallet.balance_service.get_erc20_balance_wei.return_value =
|
|
910
|
+
mock_wallet.balance_service.get_erc20_balance_wei.return_value = (
|
|
911
|
+
100 * 10**18
|
|
912
|
+
) # Plenty of balance
|
|
915
913
|
|
|
916
914
|
# Mock allowance to pass check (return an int)
|
|
917
915
|
mock_wallet.transfer_service.get_erc20_allowance.return_value = 10**20 # Plenty of allowance
|
|
@@ -26,10 +26,11 @@ def mock_wallet():
|
|
|
26
26
|
@pytest.fixture
|
|
27
27
|
def mock_manager(mock_wallet):
|
|
28
28
|
"""Setup a ServiceManager with mocked dependencies."""
|
|
29
|
-
with
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
with (
|
|
30
|
+
patch("iwa.plugins.olas.service_manager.base.Config") as mock_cfg_cls,
|
|
31
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
32
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
33
|
+
):
|
|
33
34
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
34
35
|
|
|
35
36
|
mock_cfg = mock_cfg_cls.return_value
|
|
@@ -237,7 +237,9 @@ def test_activate_registration(
|
|
|
237
237
|
@patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
|
|
238
238
|
@patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
|
|
239
239
|
@patch("iwa.plugins.olas.service_manager.base.ContractCache")
|
|
240
|
-
def test_register_agent(
|
|
240
|
+
def test_register_agent(
|
|
241
|
+
mock_cache, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
|
|
242
|
+
):
|
|
241
243
|
"""Test agent registration flow."""
|
|
242
244
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
243
245
|
mock_config_inst = mock_config_cls.return_value
|
|
@@ -354,7 +356,9 @@ def test_deploy(mock_cache, mock_sm_contract, mock_registry_contract, mock_confi
|
|
|
354
356
|
@patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
|
|
355
357
|
@patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
|
|
356
358
|
@patch("iwa.plugins.olas.service_manager.base.ContractCache")
|
|
357
|
-
def test_terminate(
|
|
359
|
+
def test_terminate(
|
|
360
|
+
mock_cache, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
|
|
361
|
+
):
|
|
358
362
|
"""Test service termination."""
|
|
359
363
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
360
364
|
# Setup mock service
|
|
@@ -457,7 +461,6 @@ def test_stake(mock_cache, mock_sm_contract, mock_registry_contract, mock_config
|
|
|
457
461
|
"required_agent_bond": 50000000000000000000,
|
|
458
462
|
}
|
|
459
463
|
|
|
460
|
-
|
|
461
464
|
success = manager.stake(mock_staking)
|
|
462
465
|
assert success is True
|
|
463
466
|
assert manager.service.staking_contract_address == "0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd"
|
|
@@ -480,8 +483,6 @@ def test_stake(mock_cache, mock_sm_contract, mock_registry_contract, mock_config
|
|
|
480
483
|
assert manager.stake(mock_staking) is False
|
|
481
484
|
mock_staking.get_service_ids.return_value = []
|
|
482
485
|
|
|
483
|
-
|
|
484
|
-
|
|
485
486
|
# 4. Approve fail
|
|
486
487
|
mock_wallet.sign_and_send_transaction.return_value = (False, {})
|
|
487
488
|
assert manager.stake(mock_staking) is False
|