iwa 0.0.19__py3-none-any.whl → 0.0.21__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 +8 -1
- iwa/core/chainlist.py +116 -0
- iwa/core/constants.py +1 -0
- iwa/core/contracts/cache.py +131 -0
- iwa/core/contracts/contract.py +7 -0
- iwa/core/monitor.py +2 -2
- iwa/core/rpc_monitor.py +60 -0
- iwa/core/services/safe.py +2 -2
- iwa/plugins/gnosis/safe.py +1 -1
- iwa/plugins/gnosis/tests/test_safe.py +1 -1
- iwa/plugins/olas/contracts/activity_checker.py +63 -25
- iwa/plugins/olas/contracts/staking.py +115 -19
- iwa/plugins/olas/events.py +141 -0
- iwa/plugins/olas/plugin.py +2 -2
- iwa/plugins/olas/service_manager/base.py +7 -2
- iwa/plugins/olas/service_manager/lifecycle.py +30 -5
- iwa/plugins/olas/service_manager/mech.py +9 -0
- iwa/plugins/olas/service_manager/staking.py +6 -2
- iwa/plugins/olas/tests/test_olas_integration.py +38 -10
- iwa/plugins/olas/tests/test_plugin_full.py +3 -5
- iwa/plugins/olas/tests/test_service_manager.py +7 -1
- iwa/plugins/olas/tests/test_service_manager_errors.py +22 -11
- iwa/plugins/olas/tests/test_service_manager_flows.py +24 -8
- iwa/plugins/olas/tests/test_service_staking.py +59 -15
- iwa/plugins/olas/tests/test_staking_validation.py +8 -14
- iwa/tools/test_chainlist.py +38 -0
- iwa/tui/rpc.py +1 -1
- iwa/tui/screens/wallets.py +2 -2
- iwa/tui/tests/test_rpc.py +2 -2
- iwa/tui/widgets/base.py +1 -1
- iwa/web/routers/olas/staking.py +9 -4
- {iwa-0.0.19.dist-info → iwa-0.0.21.dist-info}/METADATA +1 -1
- {iwa-0.0.19.dist-info → iwa-0.0.21.dist-info}/RECORD +39 -33
- tests/test_monitor.py +3 -3
- tests/test_rpc_efficiency.py +103 -0
- {iwa-0.0.19.dist-info → iwa-0.0.21.dist-info}/WHEEL +0 -0
- {iwa-0.0.19.dist-info → iwa-0.0.21.dist-info}/entry_points.txt +0 -0
- {iwa-0.0.19.dist-info → iwa-0.0.21.dist-info}/licenses/LICENSE +0 -0
- {iwa-0.0.19.dist-info → iwa-0.0.21.dist-info}/top_level.txt +0 -0
|
@@ -23,12 +23,19 @@ def mock_wallet():
|
|
|
23
23
|
return wallet
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def mock_manager(mock_wallet):
|
|
27
28
|
"""Setup a ServiceManager with mocked dependencies."""
|
|
28
|
-
with patch("iwa.plugins.olas.service_manager.base.Config") as mock_cfg_cls
|
|
29
|
+
with patch("iwa.plugins.olas.service_manager.base.Config") as mock_cfg_cls, \
|
|
30
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
31
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
32
|
+
|
|
33
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
34
|
+
|
|
29
35
|
mock_cfg = mock_cfg_cls.return_value
|
|
30
36
|
mock_cfg.plugins = {"olas": MagicMock()}
|
|
31
37
|
mock_cfg.plugins["olas"].get_service.return_value = None
|
|
38
|
+
|
|
32
39
|
with patch(
|
|
33
40
|
"iwa.plugins.olas.service_manager.OLAS_CONTRACTS",
|
|
34
41
|
{
|
|
@@ -43,18 +50,22 @@ def setup_manager(mock_wallet):
|
|
|
43
50
|
mock_if = mock_if_cls.return_value
|
|
44
51
|
mock_if.get.return_value.chain.name.lower.return_value = "gnosis"
|
|
45
52
|
mock_if.get.return_value.get_contract_address.return_value = VALID_ADDR
|
|
53
|
+
|
|
46
54
|
manager = ServiceManager(mock_wallet)
|
|
47
55
|
manager.registry = MagicMock()
|
|
48
56
|
manager.manager_contract = MagicMock()
|
|
49
57
|
manager.olas_config = mock_cfg.plugins["olas"]
|
|
50
58
|
manager.chain_name = "gnosis"
|
|
59
|
+
# Fix recursive mock issue by setting explicit return value
|
|
60
|
+
manager.registry.chain_interface = MagicMock()
|
|
51
61
|
manager.registry.chain_interface.get_contract_address.return_value = VALID_ADDR
|
|
52
|
-
|
|
62
|
+
|
|
63
|
+
yield manager
|
|
53
64
|
|
|
54
65
|
|
|
55
|
-
def test_service_manager_mech_requests_failures(
|
|
66
|
+
def test_service_manager_mech_requests_failures(mock_manager):
|
|
56
67
|
"""Test failure paths in mech requests."""
|
|
57
|
-
manager =
|
|
68
|
+
manager = mock_manager
|
|
58
69
|
|
|
59
70
|
# Service missing
|
|
60
71
|
manager.service = None
|
|
@@ -99,9 +110,9 @@ def test_service_manager_mech_requests_failures(mock_wallet):
|
|
|
99
110
|
assert manager.send_mech_request(b"data", use_marketplace=False) is None
|
|
100
111
|
|
|
101
112
|
|
|
102
|
-
def test_service_manager_lifecycle_failures(mock_wallet):
|
|
113
|
+
def test_service_manager_lifecycle_failures(mock_manager, mock_wallet):
|
|
103
114
|
"""Test failure paths in lifecycle methods."""
|
|
104
|
-
manager =
|
|
115
|
+
manager = mock_manager
|
|
105
116
|
manager.service = Service(service_name="t", chain_name="gnosis", service_id=1, agent_ids=[1])
|
|
106
117
|
|
|
107
118
|
# register_agent failures
|
|
@@ -147,9 +158,9 @@ def test_service_manager_lifecycle_failures(mock_wallet):
|
|
|
147
158
|
assert manager.wind_down() is True
|
|
148
159
|
|
|
149
160
|
|
|
150
|
-
def test_service_manager_staking_status_failures(
|
|
161
|
+
def test_service_manager_staking_status_failures(mock_manager):
|
|
151
162
|
"""Test failure paths in get_staking_status."""
|
|
152
|
-
manager =
|
|
163
|
+
manager = mock_manager
|
|
153
164
|
|
|
154
165
|
# Service missing
|
|
155
166
|
manager.service = None
|
|
@@ -178,9 +189,9 @@ def test_service_manager_staking_status_failures(mock_wallet):
|
|
|
178
189
|
assert status.mech_requests_this_epoch == 0
|
|
179
190
|
|
|
180
191
|
|
|
181
|
-
def test_service_manager_verify_event_exception(
|
|
192
|
+
def test_service_manager_verify_event_exception(mock_manager):
|
|
182
193
|
"""Test exception path in _execute_mech_tx."""
|
|
183
|
-
manager =
|
|
194
|
+
manager = mock_manager
|
|
184
195
|
manager.service = Service(
|
|
185
196
|
service_name="t",
|
|
186
197
|
chain_name="gnosis",
|
|
@@ -45,10 +45,12 @@ def mock_config():
|
|
|
45
45
|
@patch("iwa.plugins.olas.service_manager.base.Config")
|
|
46
46
|
@patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
|
|
47
47
|
@patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
|
|
48
|
+
@patch("iwa.plugins.olas.service_manager.base.ContractCache")
|
|
48
49
|
def test_create_service_success(
|
|
49
|
-
mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
|
|
50
|
+
mock_cache, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
|
|
50
51
|
):
|
|
51
52
|
"""Test successful service creation."""
|
|
53
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
52
54
|
# Setup Config with new OlasConfig structure
|
|
53
55
|
mock_config_inst = mock_config_cls.return_value
|
|
54
56
|
mock_olas_config = MagicMock()
|
|
@@ -84,10 +86,12 @@ def test_create_service_success(
|
|
|
84
86
|
@patch("iwa.plugins.olas.service_manager.base.Config")
|
|
85
87
|
@patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
|
|
86
88
|
@patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
|
|
89
|
+
@patch("iwa.plugins.olas.service_manager.base.ContractCache")
|
|
87
90
|
def test_create_service_failures(
|
|
88
|
-
mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
|
|
91
|
+
mock_cache, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
|
|
89
92
|
):
|
|
90
93
|
"""Test service creation failure modes."""
|
|
94
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
91
95
|
mock_config_inst = mock_config_cls.return_value
|
|
92
96
|
mock_olas_config = MagicMock()
|
|
93
97
|
mock_olas_config.get_service.return_value = None
|
|
@@ -139,10 +143,12 @@ def test_create_service_failures(
|
|
|
139
143
|
@patch("iwa.plugins.olas.service_manager.base.Config")
|
|
140
144
|
@patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
|
|
141
145
|
@patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
|
|
146
|
+
@patch("iwa.plugins.olas.service_manager.base.ContractCache")
|
|
142
147
|
def test_create_service_with_approval(
|
|
143
|
-
mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
|
|
148
|
+
mock_cache, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
|
|
144
149
|
):
|
|
145
150
|
"""Test service creation with token approval."""
|
|
151
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
146
152
|
mock_config_inst = mock_config_cls.return_value
|
|
147
153
|
mock_olas_config = MagicMock()
|
|
148
154
|
mock_olas_config.get_service.return_value = None
|
|
@@ -170,10 +176,12 @@ def test_create_service_with_approval(
|
|
|
170
176
|
@patch("iwa.plugins.olas.service_manager.base.Config")
|
|
171
177
|
@patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
|
|
172
178
|
@patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
|
|
179
|
+
@patch("iwa.plugins.olas.service_manager.base.ContractCache")
|
|
173
180
|
def test_activate_registration(
|
|
174
|
-
mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
|
|
181
|
+
mock_cache, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
|
|
175
182
|
):
|
|
176
183
|
"""Test service registration activation."""
|
|
184
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
177
185
|
mock_config_inst = mock_config_cls.return_value
|
|
178
186
|
mock_olas_config = MagicMock()
|
|
179
187
|
mock_service = MagicMock()
|
|
@@ -228,8 +236,10 @@ def test_activate_registration(
|
|
|
228
236
|
@patch("iwa.plugins.olas.service_manager.base.Config")
|
|
229
237
|
@patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
|
|
230
238
|
@patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
|
|
231
|
-
|
|
239
|
+
@patch("iwa.plugins.olas.service_manager.base.ContractCache")
|
|
240
|
+
def test_register_agent(mock_cache, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet):
|
|
232
241
|
"""Test agent registration flow."""
|
|
242
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
233
243
|
mock_config_inst = mock_config_cls.return_value
|
|
234
244
|
mock_olas_config = MagicMock()
|
|
235
245
|
mock_service = MagicMock()
|
|
@@ -277,8 +287,10 @@ def test_register_agent(mock_sm_contract, mock_registry_contract, mock_config_cl
|
|
|
277
287
|
@patch("iwa.plugins.olas.service_manager.base.Config")
|
|
278
288
|
@patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
|
|
279
289
|
@patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
|
|
280
|
-
|
|
290
|
+
@patch("iwa.plugins.olas.service_manager.base.ContractCache")
|
|
291
|
+
def test_deploy(mock_cache, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet):
|
|
281
292
|
"""Test service deployment."""
|
|
293
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
282
294
|
# Setup mock service
|
|
283
295
|
mock_service = MagicMock()
|
|
284
296
|
mock_service.service_id = 123
|
|
@@ -341,8 +353,10 @@ def test_deploy(mock_sm_contract, mock_registry_contract, mock_config_cls, mock_
|
|
|
341
353
|
@patch("iwa.plugins.olas.service_manager.base.Config")
|
|
342
354
|
@patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
|
|
343
355
|
@patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
|
|
344
|
-
|
|
356
|
+
@patch("iwa.plugins.olas.service_manager.base.ContractCache")
|
|
357
|
+
def test_terminate(mock_cache, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet):
|
|
345
358
|
"""Test service termination."""
|
|
359
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
346
360
|
# Setup mock service
|
|
347
361
|
mock_service = MagicMock()
|
|
348
362
|
mock_service.service_id = 123
|
|
@@ -401,8 +415,10 @@ def test_terminate(mock_sm_contract, mock_registry_contract, mock_config_cls, mo
|
|
|
401
415
|
@patch(
|
|
402
416
|
"iwa.plugins.olas.service_manager.base.ServiceManagerContract"
|
|
403
417
|
) # MUST mock specifically here
|
|
404
|
-
|
|
418
|
+
@patch("iwa.plugins.olas.service_manager.base.ContractCache")
|
|
419
|
+
def test_stake(mock_cache, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet):
|
|
405
420
|
"""Test service staking."""
|
|
421
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
406
422
|
# Setup mock service
|
|
407
423
|
mock_service = MagicMock()
|
|
408
424
|
mock_service.service_id = 123
|
|
@@ -33,7 +33,10 @@ def mock_wallet():
|
|
|
33
33
|
|
|
34
34
|
def test_sm_unstake_not_staked(mock_wallet):
|
|
35
35
|
"""Cover unstake when not staked (lines 736-738)."""
|
|
36
|
-
with patch("iwa.core.models.Config")
|
|
36
|
+
with patch("iwa.core.models.Config"), \
|
|
37
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
38
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
39
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
37
40
|
sm = ServiceManager(mock_wallet)
|
|
38
41
|
sm.service = Service(
|
|
39
42
|
service_name="t", chain_name="gnosis", service_id=1, multisig_address=VALID_ADDR
|
|
@@ -48,7 +51,10 @@ def test_sm_unstake_not_staked(mock_wallet):
|
|
|
48
51
|
|
|
49
52
|
def test_sm_unstake_tx_fails(mock_wallet):
|
|
50
53
|
"""Cover unstake transaction failure (lines 766-768)."""
|
|
51
|
-
with patch("iwa.core.models.Config")
|
|
54
|
+
with patch("iwa.core.models.Config"), \
|
|
55
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
56
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
57
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
52
58
|
sm = ServiceManager(mock_wallet)
|
|
53
59
|
sm.service = Service(
|
|
54
60
|
service_name="t", chain_name="gnosis", service_id=1, multisig_address=VALID_ADDR
|
|
@@ -71,7 +77,10 @@ def test_sm_unstake_tx_fails(mock_wallet):
|
|
|
71
77
|
|
|
72
78
|
def test_sm_get_staking_status_no_staking_address(mock_wallet):
|
|
73
79
|
"""Cover get_staking_status with no staking address (lines 831)."""
|
|
74
|
-
with patch("iwa.core.models.Config")
|
|
80
|
+
with patch("iwa.core.models.Config"), \
|
|
81
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
82
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
83
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
75
84
|
sm = ServiceManager(mock_wallet)
|
|
76
85
|
sm.service = Service(
|
|
77
86
|
service_name="t", chain_name="gnosis", service_id=1, staking_contract_address=VALID_ADDR
|
|
@@ -88,7 +97,10 @@ def test_sm_get_staking_status_no_staking_address(mock_wallet):
|
|
|
88
97
|
|
|
89
98
|
def test_sm_get_staking_status_with_full_info(mock_wallet):
|
|
90
99
|
"""Cover get_staking_status with complete info (lines 866-891)."""
|
|
91
|
-
with patch("iwa.core.models.Config")
|
|
100
|
+
with patch("iwa.core.models.Config"), \
|
|
101
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
102
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
103
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
92
104
|
sm = ServiceManager(mock_wallet)
|
|
93
105
|
sm.service = Service(
|
|
94
106
|
service_name="t", chain_name="gnosis", service_id=1, staking_contract_address=VALID_ADDR
|
|
@@ -130,7 +142,10 @@ def test_sm_get_staking_status_with_full_info(mock_wallet):
|
|
|
130
142
|
|
|
131
143
|
def test_sm_claim_rewards_no_service(mock_wallet):
|
|
132
144
|
"""Cover claim_rewards with no service (lines 936-938)."""
|
|
133
|
-
with patch("iwa.core.models.Config")
|
|
145
|
+
with patch("iwa.core.models.Config"), \
|
|
146
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
147
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
148
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
134
149
|
sm = ServiceManager(mock_wallet)
|
|
135
150
|
sm.service = None
|
|
136
151
|
|
|
@@ -141,7 +156,10 @@ def test_sm_claim_rewards_no_service(mock_wallet):
|
|
|
141
156
|
|
|
142
157
|
def test_sm_claim_rewards_no_staking_address(mock_wallet):
|
|
143
158
|
"""Cover claim_rewards with no staking address (lines 939-943)."""
|
|
144
|
-
with patch("iwa.core.models.Config")
|
|
159
|
+
with patch("iwa.core.models.Config"), \
|
|
160
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
161
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
162
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
145
163
|
sm = ServiceManager(mock_wallet)
|
|
146
164
|
sm.service = Service(
|
|
147
165
|
service_name="t", chain_name="gnosis", service_id=1, staking_contract_address=None
|
|
@@ -153,7 +171,10 @@ def test_sm_claim_rewards_no_staking_address(mock_wallet):
|
|
|
153
171
|
|
|
154
172
|
def test_sm_claim_rewards_tx_fails(mock_wallet):
|
|
155
173
|
"""Cover claim_rewards transaction failure (lines 967-968)."""
|
|
156
|
-
with patch("iwa.core.models.Config")
|
|
174
|
+
with patch("iwa.core.models.Config"), \
|
|
175
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
176
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
177
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
157
178
|
sm = ServiceManager(mock_wallet)
|
|
158
179
|
sm.service = Service(
|
|
159
180
|
service_name="t",
|
|
@@ -165,12 +186,22 @@ def test_sm_claim_rewards_tx_fails(mock_wallet):
|
|
|
165
186
|
|
|
166
187
|
mock_staking = MagicMock()
|
|
167
188
|
mock_staking.prepare_claim_tx.return_value = {"to": VALID_ADDR}
|
|
189
|
+
mock_staking.get_staking_state.return_value = StakingState.STAKED
|
|
190
|
+
mock_staking.get_accrued_rewards.return_value = 100
|
|
191
|
+
|
|
192
|
+
def get_contract_side_effect(cls, *args, **kwargs):
|
|
193
|
+
print(f"DEBUG: get_contract called with {cls!r}")
|
|
194
|
+
if "StakingContract" in str(cls):
|
|
195
|
+
return mock_staking
|
|
196
|
+
return cls(*args, **kwargs)
|
|
197
|
+
|
|
198
|
+
mock_cache.return_value.get_contract.side_effect = get_contract_side_effect
|
|
168
199
|
|
|
169
200
|
mock_wallet.sign_and_send_transaction.return_value = (False, None)
|
|
170
201
|
|
|
171
|
-
with patch("iwa.plugins.olas.service_manager.StakingContract", return_value=mock_staking):
|
|
172
|
-
|
|
173
|
-
|
|
202
|
+
with patch("iwa.plugins.olas.service_manager.drain.StakingContract", return_value=mock_staking):
|
|
203
|
+
success, amount = sm.claim_rewards()
|
|
204
|
+
assert success is False
|
|
174
205
|
|
|
175
206
|
|
|
176
207
|
# === SERVICE MANAGER SPIN_UP STATE TRANSITIONS (lines 1188-1241) ===
|
|
@@ -178,7 +209,10 @@ def test_sm_claim_rewards_tx_fails(mock_wallet):
|
|
|
178
209
|
|
|
179
210
|
def test_sm_spin_up_state_mismatch_after_activation(mock_wallet):
|
|
180
211
|
"""Cover spin_up state mismatch after activation (lines 1188-1191)."""
|
|
181
|
-
with patch("iwa.core.models.Config")
|
|
212
|
+
with patch("iwa.core.models.Config"), \
|
|
213
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
214
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
215
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
182
216
|
sm = ServiceManager(mock_wallet)
|
|
183
217
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
184
218
|
|
|
@@ -199,7 +233,10 @@ def test_sm_spin_up_state_mismatch_after_activation(mock_wallet):
|
|
|
199
233
|
|
|
200
234
|
def test_sm_spin_up_registration_fails(mock_wallet):
|
|
201
235
|
"""Cover spin_up registration failure (lines 1199-1201)."""
|
|
202
|
-
with patch("iwa.core.models.Config")
|
|
236
|
+
with patch("iwa.core.models.Config"), \
|
|
237
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
238
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
239
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
203
240
|
sm = ServiceManager(mock_wallet)
|
|
204
241
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
205
242
|
|
|
@@ -216,7 +253,10 @@ def test_sm_spin_up_registration_fails(mock_wallet):
|
|
|
216
253
|
|
|
217
254
|
def test_sm_spin_up_deploy_fails(mock_wallet):
|
|
218
255
|
"""Cover spin_up deploy failure (lines 1216-1218)."""
|
|
219
|
-
with patch("iwa.core.models.Config")
|
|
256
|
+
with patch("iwa.core.models.Config"), \
|
|
257
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
258
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
259
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
220
260
|
sm = ServiceManager(mock_wallet)
|
|
221
261
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
222
262
|
|
|
@@ -233,7 +273,10 @@ def test_sm_spin_up_deploy_fails(mock_wallet):
|
|
|
233
273
|
|
|
234
274
|
def test_sm_wind_down_terminate_fails(mock_wallet):
|
|
235
275
|
"""Cover wind_down terminate failure (lines 1299-1301)."""
|
|
236
|
-
with patch("iwa.core.models.Config")
|
|
276
|
+
with patch("iwa.core.models.Config"), \
|
|
277
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache, \
|
|
278
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache):
|
|
279
|
+
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
237
280
|
sm = ServiceManager(mock_wallet)
|
|
238
281
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
239
282
|
|
|
@@ -250,7 +293,8 @@ def test_sm_wind_down_terminate_fails(mock_wallet):
|
|
|
250
293
|
|
|
251
294
|
def test_sm_wind_down_unbond_fails(mock_wallet):
|
|
252
295
|
"""Cover wind_down unbond failure (lines 1315-1317)."""
|
|
253
|
-
with patch("iwa.core.models.Config")
|
|
296
|
+
with patch("iwa.core.models.Config"), \
|
|
297
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache"):
|
|
254
298
|
sm = ServiceManager(mock_wallet)
|
|
255
299
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
256
300
|
|
|
@@ -25,19 +25,12 @@ def mock_staking():
|
|
|
25
25
|
def test_staking_get_service_info_nested_tuple(mock_staking):
|
|
26
26
|
"""Test get_service_info with nested tuple result from web3."""
|
|
27
27
|
nested_result = (("0xMultisig", "0xOwner", (1, 2), 1000, 500, 0),)
|
|
28
|
-
mock_staking.call.side_effect =
|
|
29
|
-
"0x1"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
10, # maxNumServices
|
|
35
|
-
50, # minStakingDeposit
|
|
36
|
-
7200, # minStakingDuration
|
|
37
|
-
"0xToken", # stakingToken
|
|
38
|
-
nested_result, # getServiceInfo
|
|
39
|
-
2000, # getNextRewardCheckpointTimestamp
|
|
40
|
-
]
|
|
28
|
+
mock_staking.call.side_effect = (
|
|
29
|
+
["0x1"] # activityChecker
|
|
30
|
+
+ [nested_result] # getServiceInfo
|
|
31
|
+
+ [2000] * 5 # getNextRewardCheckpointTimestamp and any others
|
|
32
|
+
)
|
|
33
|
+
|
|
41
34
|
# Re-init to trigger calls
|
|
42
35
|
with (
|
|
43
36
|
patch("iwa.plugins.olas.contracts.staking.ActivityCheckerContract"),
|
|
@@ -45,7 +38,8 @@ def test_staking_get_service_info_nested_tuple(mock_staking):
|
|
|
45
38
|
):
|
|
46
39
|
mock_ci.get_instance.return_value.web3.eth.contract.return_value = MagicMock()
|
|
47
40
|
staking = StakingContract("0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB", "gnosis")
|
|
48
|
-
|
|
41
|
+
# Update call on the new instance
|
|
42
|
+
staking.call = MagicMock(side_effect=mock_staking.call.side_effect)
|
|
49
43
|
staking.activity_checker.get_multisig_nonces.return_value = (1, 3)
|
|
50
44
|
staking.get_required_requests = MagicMock(return_value=5)
|
|
51
45
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Script to verify ChainlistRPC functionality."""
|
|
2
|
+
from iwa.core.chainlist import ChainlistRPC
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def main() -> None:
|
|
6
|
+
"""Run the verification script."""
|
|
7
|
+
print("Initializing ChainlistRPC...")
|
|
8
|
+
chainlist = ChainlistRPC()
|
|
9
|
+
|
|
10
|
+
print("Fetching data...")
|
|
11
|
+
chainlist.fetch_data()
|
|
12
|
+
|
|
13
|
+
gnosis_chain_id = 100
|
|
14
|
+
print(f"\n--- Gnosis Chain (ID: {gnosis_chain_id}) ---")
|
|
15
|
+
|
|
16
|
+
all_rpcs = chainlist.get_rpcs(gnosis_chain_id)
|
|
17
|
+
print(f"Total RPCs found: {len(all_rpcs)}")
|
|
18
|
+
|
|
19
|
+
https_rpcs = chainlist.get_https_rpcs(gnosis_chain_id)
|
|
20
|
+
print(f"HTTPS RPCs ({len(https_rpcs)}):")
|
|
21
|
+
for url in https_rpcs[:5]:
|
|
22
|
+
print(f" - {url}")
|
|
23
|
+
if len(https_rpcs) > 5:
|
|
24
|
+
print(" ... and more")
|
|
25
|
+
|
|
26
|
+
wss_rpcs = chainlist.get_wss_rpcs(gnosis_chain_id)
|
|
27
|
+
print(f"WSS RPCs ({len(wss_rpcs)}):")
|
|
28
|
+
for url in wss_rpcs[:5]:
|
|
29
|
+
print(f" - {url}")
|
|
30
|
+
if len(wss_rpcs) > 5:
|
|
31
|
+
print(" ... and more")
|
|
32
|
+
|
|
33
|
+
print("\nTracking info for first 5 RPCs:")
|
|
34
|
+
for node in all_rpcs[:5]:
|
|
35
|
+
print(f" - {node.url}: Tracking={node.is_tracking} (Privacy={node.privacy}, Tracking={node.tracking})")
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
main()
|
iwa/tui/rpc.py
CHANGED
iwa/tui/screens/wallets.py
CHANGED
|
@@ -381,7 +381,7 @@ class WalletsScreen(VerticalScroll):
|
|
|
381
381
|
self.stop_monitor()
|
|
382
382
|
addresses = [acc.address for acc in self.wallet.key_storage.accounts.values()]
|
|
383
383
|
for chain_name, interface in ChainInterfaces().items():
|
|
384
|
-
if interface.
|
|
384
|
+
if interface.current_rpc:
|
|
385
385
|
monitor = EventMonitor(addresses, self.monitor_callback, chain_name)
|
|
386
386
|
|
|
387
387
|
# Worker wrapper
|
|
@@ -497,7 +497,7 @@ class WalletsScreen(VerticalScroll):
|
|
|
497
497
|
"""Handle blockchain selection changes."""
|
|
498
498
|
if event.value and event.value != self.active_chain:
|
|
499
499
|
interface = ChainInterfaces().get(event.value)
|
|
500
|
-
if not interface or not interface.
|
|
500
|
+
if not interface or not interface.current_rpc:
|
|
501
501
|
self.notify(f"No RPC for {event.value}", severity="warning")
|
|
502
502
|
event.control.value = self.active_chain
|
|
503
503
|
return
|
iwa/tui/tests/test_rpc.py
CHANGED
|
@@ -71,7 +71,7 @@ def test_check_rpcs_success(rpc_view, mock_chain_interfaces):
|
|
|
71
71
|
"""Test check_rpcs with successful connections."""
|
|
72
72
|
# Setup mock chain interfaces
|
|
73
73
|
mock_gnosis = MagicMock()
|
|
74
|
-
mock_gnosis.
|
|
74
|
+
mock_gnosis.current_rpc = "http://gnosis"
|
|
75
75
|
mock_gnosis.web3.is_connected.return_value = True
|
|
76
76
|
|
|
77
77
|
mock_chain_interfaces.get.side_effect = lambda name: mock_gnosis if name == "gnosis" else None
|
|
@@ -99,7 +99,7 @@ def test_check_rpcs_success(rpc_view, mock_chain_interfaces):
|
|
|
99
99
|
def test_check_rpcs_error(rpc_view, mock_chain_interfaces):
|
|
100
100
|
"""Test check_rpcs with connection error."""
|
|
101
101
|
mock_eth = MagicMock()
|
|
102
|
-
mock_eth.
|
|
102
|
+
mock_eth.current_rpc = "http://eth"
|
|
103
103
|
mock_eth.web3.is_connected.side_effect = Exception("Connection fail")
|
|
104
104
|
|
|
105
105
|
mock_chain_interfaces.get.side_effect = lambda name: mock_eth if name == "ethereum" else None
|
iwa/tui/widgets/base.py
CHANGED
iwa/web/routers/olas/staking.py
CHANGED
|
@@ -105,16 +105,21 @@ def _check_availability(name, address, interface):
|
|
|
105
105
|
"""Check availability of a single staking contract."""
|
|
106
106
|
# Import at module level would cause circular import, but we can cache it
|
|
107
107
|
# The import is cached by Python after first call so this is efficient
|
|
108
|
+
from iwa.core.contracts.cache import ContractCache
|
|
108
109
|
from iwa.plugins.olas.contracts.staking import StakingContract
|
|
109
110
|
|
|
110
111
|
try:
|
|
111
|
-
|
|
112
|
+
# Use ContractCache to benefit from shared instances and property caching
|
|
113
|
+
contract = ContractCache().get_contract(
|
|
114
|
+
StakingContract, address, chain_name=interface.chain.name
|
|
115
|
+
)
|
|
112
116
|
|
|
113
117
|
# StakingContract uses .call() which handles with_retry and rotation
|
|
118
|
+
# Use properties instead of .call() to leverage caching
|
|
114
119
|
service_ids = contract.call("getServiceIds")
|
|
115
|
-
max_services = contract.
|
|
116
|
-
min_deposit = contract.
|
|
117
|
-
staking_token = contract.
|
|
120
|
+
max_services = contract.max_num_services
|
|
121
|
+
min_deposit = contract.min_staking_deposit
|
|
122
|
+
staking_token = contract.staking_token_address
|
|
118
123
|
used = len(service_ids)
|
|
119
124
|
|
|
120
125
|
return {
|