iwa 0.0.33__py3-none-any.whl → 0.0.59__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 +130 -11
- iwa/core/chain/models.py +15 -3
- iwa/core/chain/rate_limiter.py +48 -12
- iwa/core/chainlist.py +15 -10
- iwa/core/cli.py +4 -1
- 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 +21 -7
- iwa/core/keys.py +65 -15
- iwa/core/models.py +58 -13
- iwa/core/pricing.py +10 -6
- iwa/core/rpc_monitor.py +1 -0
- iwa/core/secrets.py +27 -0
- iwa/core/services/account.py +1 -1
- iwa/core/services/balance.py +0 -23
- iwa/core/services/safe.py +72 -45
- iwa/core/services/safe_executor.py +350 -0
- iwa/core/services/transaction.py +43 -13
- iwa/core/services/transfer/erc20.py +14 -3
- iwa/core/services/transfer/native.py +14 -31
- iwa/core/services/transfer/swap.py +1 -0
- iwa/core/tests/test_gnosis_fee.py +91 -0
- iwa/core/tests/test_ipfs.py +85 -0
- iwa/core/tests/test_pricing.py +65 -0
- iwa/core/tests/test_regression_fixes.py +97 -0
- iwa/core/utils.py +2 -0
- iwa/core/wallet.py +6 -4
- iwa/plugins/gnosis/cow/quotes.py +2 -2
- iwa/plugins/gnosis/cow/swap.py +18 -32
- iwa/plugins/gnosis/tests/test_cow.py +19 -10
- 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 +29 -25
- iwa/plugins/olas/models.py +0 -3
- iwa/plugins/olas/plugin.py +16 -14
- iwa/plugins/olas/service_manager/drain.py +16 -9
- iwa/plugins/olas/service_manager/lifecycle.py +23 -12
- iwa/plugins/olas/service_manager/staking.py +15 -10
- iwa/plugins/olas/tests/test_importer_error_handling.py +13 -0
- iwa/plugins/olas/tests/test_olas_archiving.py +83 -0
- iwa/plugins/olas/tests/test_olas_integration.py +49 -29
- iwa/plugins/olas/tests/test_olas_view.py +5 -1
- iwa/plugins/olas/tests/test_service_manager.py +15 -17
- iwa/plugins/olas/tests/test_service_manager_errors.py +6 -5
- iwa/plugins/olas/tests/test_service_manager_flows.py +7 -6
- iwa/plugins/olas/tests/test_service_manager_rewards.py +5 -1
- iwa/plugins/olas/tests/test_service_staking.py +64 -38
- iwa/tools/drain_accounts.py +61 -0
- iwa/tools/list_contracts.py +2 -0
- iwa/tools/reset_env.py +2 -1
- iwa/tools/test_chainlist.py +5 -1
- iwa/tui/screens/wallets.py +2 -4
- iwa/web/routers/accounts.py +1 -1
- iwa/web/routers/olas/services.py +10 -5
- iwa/web/static/app.js +21 -9
- iwa/web/static/style.css +4 -0
- iwa/web/tests/test_web_endpoints.py +2 -2
- {iwa-0.0.33.dist-info → iwa-0.0.59.dist-info}/METADATA +6 -3
- {iwa-0.0.33.dist-info → iwa-0.0.59.dist-info}/RECORD +82 -71
- {iwa-0.0.33.dist-info → iwa-0.0.59.dist-info}/WHEEL +1 -1
- tests/test_balance_service.py +0 -43
- tests/test_chain.py +13 -5
- tests/test_cli.py +2 -2
- tests/test_drain_coverage.py +12 -6
- tests/test_keys.py +23 -23
- tests/test_rate_limiter.py +2 -2
- tests/test_rate_limiter_retry.py +103 -0
- tests/test_rpc_efficiency.py +4 -1
- tests/test_rpc_rate_limit.py +34 -0
- tests/test_rpc_rotation.py +59 -11
- tests/test_safe_coverage.py +37 -23
- tests/test_safe_executor.py +361 -0
- tests/test_safe_integration.py +153 -0
- tests/test_safe_service.py +1 -1
- tests/test_transfer_swap_unit.py +5 -1
- tests/test_pricing.py +0 -160
- {iwa-0.0.33.dist-info → iwa-0.0.59.dist-info}/entry_points.txt +0 -0
- {iwa-0.0.33.dist-info → iwa-0.0.59.dist-info}/licenses/LICENSE +0 -0
- {iwa-0.0.33.dist-info → iwa-0.0.59.dist-info}/top_level.txt +0 -0
|
@@ -147,21 +147,25 @@ async def test_olas_view_actions(mock_wallet, mock_olas_config):
|
|
|
147
147
|
|
|
148
148
|
app = OlasTestApp(mock_wallet)
|
|
149
149
|
async with app.run_test() as pilot:
|
|
150
|
-
|
|
150
|
+
# Wait for initial load to finish
|
|
151
|
+
await pilot.pause(0.5)
|
|
151
152
|
|
|
152
153
|
# 1. Test Claim
|
|
153
154
|
with patch.object(OlasView, "claim_rewards") as mock_claim:
|
|
154
155
|
await pilot.click("#claim-gnosis_1")
|
|
156
|
+
await pilot.pause()
|
|
155
157
|
mock_claim.assert_called_with("gnosis:1")
|
|
156
158
|
|
|
157
159
|
# 2. Test Unstake
|
|
158
160
|
with patch.object(OlasView, "unstake_service") as mock_unstake:
|
|
159
161
|
await pilot.click("#unstake-gnosis_1")
|
|
162
|
+
await pilot.pause()
|
|
160
163
|
mock_unstake.assert_called_with("gnosis:1")
|
|
161
164
|
|
|
162
165
|
# 3. Test Checkpoint
|
|
163
166
|
with patch.object(OlasView, "checkpoint_service") as mock_checkpoint:
|
|
164
167
|
await pilot.click("#checkpoint-gnosis_1")
|
|
168
|
+
await pilot.pause()
|
|
165
169
|
mock_checkpoint.assert_called_with("gnosis:1")
|
|
166
170
|
|
|
167
171
|
|
|
@@ -65,11 +65,11 @@ def mock_wallet():
|
|
|
65
65
|
wallet.master_account.address = "0x1234567890123456789012345678901234567890"
|
|
66
66
|
wallet.key_storage = MagicMock()
|
|
67
67
|
wallet.key_storage.get_account.return_value = None # Default
|
|
68
|
-
# Mock
|
|
68
|
+
# Mock generate_new_account which returns a StoredAccount or similar
|
|
69
69
|
new_acc = MagicMock()
|
|
70
70
|
new_acc.address = "0x0987654321098765432109876543210987654321"
|
|
71
|
-
wallet.key_storage.
|
|
72
|
-
wallet.key_storage.
|
|
71
|
+
wallet.key_storage.generate_new_account.return_value = new_acc
|
|
72
|
+
wallet.key_storage.generate_new_account.return_value = new_acc
|
|
73
73
|
# Mock account_service
|
|
74
74
|
wallet.account_service = MagicMock()
|
|
75
75
|
wallet.account_service.get_tag_by_address.return_value = "mock_tag"
|
|
@@ -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
|
|
|
@@ -219,7 +215,7 @@ def test_register_agent_success(service_manager, mock_wallet):
|
|
|
219
215
|
"security_deposit": 50000000000000000000,
|
|
220
216
|
}
|
|
221
217
|
|
|
222
|
-
#
|
|
218
|
+
# generate_new_account is already mocked
|
|
223
219
|
mock_wallet.send.return_value = "0xMockTxHash" # wallet.send returns tx_hash
|
|
224
220
|
mock_wallet.sign_and_send_transaction.return_value = (True, {})
|
|
225
221
|
service_manager.registry.extract_events.return_value = [{"name": "RegisterInstance"}]
|
|
@@ -331,7 +327,7 @@ def test_register_agent_with_existing_address(service_manager, mock_wallet):
|
|
|
331
327
|
assert service_manager.register_agent(agent_address=existing_agent) is True
|
|
332
328
|
assert service_manager.service.agent_address == TEST_EXISTING_AGENT_ADDR
|
|
333
329
|
# Should NOT create a new account
|
|
334
|
-
mock_wallet.key_storage.
|
|
330
|
+
mock_wallet.key_storage.generate_new_account.assert_not_called()
|
|
335
331
|
# Should NOT fund the agent (only for new accounts)
|
|
336
332
|
mock_wallet.send.assert_not_called()
|
|
337
333
|
|
|
@@ -349,7 +345,7 @@ def test_register_agent_creates_new_if_none(service_manager, mock_wallet):
|
|
|
349
345
|
|
|
350
346
|
assert service_manager.register_agent() is True
|
|
351
347
|
# Should create a new account
|
|
352
|
-
mock_wallet.key_storage.
|
|
348
|
+
mock_wallet.key_storage.generate_new_account.assert_called()
|
|
353
349
|
# Should fund the new agent
|
|
354
350
|
mock_wallet.send.assert_called()
|
|
355
351
|
|
|
@@ -661,7 +657,7 @@ def test_spin_up_with_existing_agent(service_manager, mock_wallet):
|
|
|
661
657
|
existing_agent = TEST_EXISTING_AGENT_ADDR
|
|
662
658
|
assert service_manager.spin_up(agent_address=existing_agent) is True
|
|
663
659
|
# Verify agent address was not newly created
|
|
664
|
-
mock_wallet.key_storage.
|
|
660
|
+
mock_wallet.key_storage.generate_new_account.assert_not_called()
|
|
665
661
|
|
|
666
662
|
|
|
667
663
|
# --- Tests for wind_down ---
|
|
@@ -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
|
|
@@ -18,7 +18,7 @@ def mock_wallet():
|
|
|
18
18
|
wallet.master_account.address = VALID_ADDR
|
|
19
19
|
wallet.chain_interface.chain_name = "gnosis"
|
|
20
20
|
wallet.sign_and_send_transaction.return_value = (True, {"transactionHash": b"\x00" * 32})
|
|
21
|
-
wallet.key_storage.
|
|
21
|
+
wallet.key_storage.generate_new_account.return_value.address = VALID_ADDR
|
|
22
22
|
wallet.key_storage.get_account.return_value.address = VALID_ADDR
|
|
23
23
|
return wallet
|
|
24
24
|
|
|
@@ -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
|
|
@@ -17,7 +17,7 @@ def mock_wallet():
|
|
|
17
17
|
wallet.key_storage.get_account.return_value.address = (
|
|
18
18
|
"0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
|
19
19
|
)
|
|
20
|
-
wallet.key_storage.
|
|
20
|
+
wallet.key_storage.generate_new_account.return_value.address = (
|
|
21
21
|
"0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
|
|
22
22
|
)
|
|
23
23
|
wallet.sign_and_send_transaction.return_value = (True, {"status": 1})
|
|
@@ -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
|
|
@@ -197,7 +197,11 @@ def test_withdraw_rewards_no_withdrawal_address(mock_wallet):
|
|
|
197
197
|
)
|
|
198
198
|
manager.olas_config.withdrawal_address = None
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
with patch("iwa.plugins.olas.service_manager.drain.ERC20Contract") as mock_erc20_cls:
|
|
201
|
+
mock_erc20 = mock_erc20_cls.return_value
|
|
202
|
+
mock_erc20.balance_of_wei.return_value = 0
|
|
203
|
+
|
|
204
|
+
success, amount = manager.withdraw_rewards()
|
|
201
205
|
|
|
202
206
|
assert success is False
|
|
203
207
|
assert amount == 0
|
|
@@ -33,9 +33,11 @@ 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
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
with (
|
|
37
|
+
patch("iwa.core.models.Config"),
|
|
38
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
39
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
40
|
+
):
|
|
39
41
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
40
42
|
sm = ServiceManager(mock_wallet)
|
|
41
43
|
sm.service = Service(
|
|
@@ -51,9 +53,11 @@ def test_sm_unstake_not_staked(mock_wallet):
|
|
|
51
53
|
|
|
52
54
|
def test_sm_unstake_tx_fails(mock_wallet):
|
|
53
55
|
"""Cover unstake transaction failure (lines 766-768)."""
|
|
54
|
-
with
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
with (
|
|
57
|
+
patch("iwa.core.models.Config"),
|
|
58
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
59
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
60
|
+
):
|
|
57
61
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
58
62
|
sm = ServiceManager(mock_wallet)
|
|
59
63
|
sm.service = Service(
|
|
@@ -77,9 +81,11 @@ def test_sm_unstake_tx_fails(mock_wallet):
|
|
|
77
81
|
|
|
78
82
|
def test_sm_get_staking_status_no_staking_address(mock_wallet):
|
|
79
83
|
"""Cover get_staking_status with no staking address (lines 831)."""
|
|
80
|
-
with
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
with (
|
|
85
|
+
patch("iwa.core.models.Config"),
|
|
86
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
87
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
88
|
+
):
|
|
83
89
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
84
90
|
sm = ServiceManager(mock_wallet)
|
|
85
91
|
sm.service = Service(
|
|
@@ -97,9 +103,11 @@ def test_sm_get_staking_status_no_staking_address(mock_wallet):
|
|
|
97
103
|
|
|
98
104
|
def test_sm_get_staking_status_with_full_info(mock_wallet):
|
|
99
105
|
"""Cover get_staking_status with complete info (lines 866-891)."""
|
|
100
|
-
with
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
with (
|
|
107
|
+
patch("iwa.core.models.Config"),
|
|
108
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
109
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
110
|
+
):
|
|
103
111
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
104
112
|
sm = ServiceManager(mock_wallet)
|
|
105
113
|
sm.service = Service(
|
|
@@ -142,9 +150,11 @@ def test_sm_get_staking_status_with_full_info(mock_wallet):
|
|
|
142
150
|
|
|
143
151
|
def test_sm_claim_rewards_no_service(mock_wallet):
|
|
144
152
|
"""Cover claim_rewards with no service (lines 936-938)."""
|
|
145
|
-
with
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
with (
|
|
154
|
+
patch("iwa.core.models.Config"),
|
|
155
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
156
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
157
|
+
):
|
|
148
158
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
149
159
|
sm = ServiceManager(mock_wallet)
|
|
150
160
|
sm.service = None
|
|
@@ -156,9 +166,11 @@ def test_sm_claim_rewards_no_service(mock_wallet):
|
|
|
156
166
|
|
|
157
167
|
def test_sm_claim_rewards_no_staking_address(mock_wallet):
|
|
158
168
|
"""Cover claim_rewards with no staking address (lines 939-943)."""
|
|
159
|
-
with
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
with (
|
|
170
|
+
patch("iwa.core.models.Config"),
|
|
171
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
172
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
173
|
+
):
|
|
162
174
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
163
175
|
sm = ServiceManager(mock_wallet)
|
|
164
176
|
sm.service = Service(
|
|
@@ -171,9 +183,11 @@ def test_sm_claim_rewards_no_staking_address(mock_wallet):
|
|
|
171
183
|
|
|
172
184
|
def test_sm_claim_rewards_tx_fails(mock_wallet):
|
|
173
185
|
"""Cover claim_rewards transaction failure (lines 967-968)."""
|
|
174
|
-
with
|
|
175
|
-
|
|
176
|
-
|
|
186
|
+
with (
|
|
187
|
+
patch("iwa.core.models.Config"),
|
|
188
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
189
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
190
|
+
):
|
|
177
191
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
178
192
|
sm = ServiceManager(mock_wallet)
|
|
179
193
|
sm.service = Service(
|
|
@@ -199,9 +213,11 @@ def test_sm_claim_rewards_tx_fails(mock_wallet):
|
|
|
199
213
|
|
|
200
214
|
mock_wallet.sign_and_send_transaction.return_value = (False, None)
|
|
201
215
|
|
|
202
|
-
with patch(
|
|
203
|
-
|
|
204
|
-
|
|
216
|
+
with patch(
|
|
217
|
+
"iwa.plugins.olas.service_manager.drain.StakingContract", return_value=mock_staking
|
|
218
|
+
):
|
|
219
|
+
success, amount = sm.claim_rewards()
|
|
220
|
+
assert success is False
|
|
205
221
|
|
|
206
222
|
|
|
207
223
|
# === SERVICE MANAGER SPIN_UP STATE TRANSITIONS (lines 1188-1241) ===
|
|
@@ -209,9 +225,11 @@ def test_sm_claim_rewards_tx_fails(mock_wallet):
|
|
|
209
225
|
|
|
210
226
|
def test_sm_spin_up_state_mismatch_after_activation(mock_wallet):
|
|
211
227
|
"""Cover spin_up state mismatch after activation (lines 1188-1191)."""
|
|
212
|
-
with
|
|
213
|
-
|
|
214
|
-
|
|
228
|
+
with (
|
|
229
|
+
patch("iwa.core.models.Config"),
|
|
230
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
231
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
232
|
+
):
|
|
215
233
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
216
234
|
sm = ServiceManager(mock_wallet)
|
|
217
235
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
@@ -233,9 +251,11 @@ def test_sm_spin_up_state_mismatch_after_activation(mock_wallet):
|
|
|
233
251
|
|
|
234
252
|
def test_sm_spin_up_registration_fails(mock_wallet):
|
|
235
253
|
"""Cover spin_up registration failure (lines 1199-1201)."""
|
|
236
|
-
with
|
|
237
|
-
|
|
238
|
-
|
|
254
|
+
with (
|
|
255
|
+
patch("iwa.core.models.Config"),
|
|
256
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
257
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
258
|
+
):
|
|
239
259
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
240
260
|
sm = ServiceManager(mock_wallet)
|
|
241
261
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
@@ -253,9 +273,11 @@ def test_sm_spin_up_registration_fails(mock_wallet):
|
|
|
253
273
|
|
|
254
274
|
def test_sm_spin_up_deploy_fails(mock_wallet):
|
|
255
275
|
"""Cover spin_up deploy failure (lines 1216-1218)."""
|
|
256
|
-
with
|
|
257
|
-
|
|
258
|
-
|
|
276
|
+
with (
|
|
277
|
+
patch("iwa.core.models.Config"),
|
|
278
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
279
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
280
|
+
):
|
|
259
281
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
260
282
|
sm = ServiceManager(mock_wallet)
|
|
261
283
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
@@ -273,9 +295,11 @@ def test_sm_spin_up_deploy_fails(mock_wallet):
|
|
|
273
295
|
|
|
274
296
|
def test_sm_wind_down_terminate_fails(mock_wallet):
|
|
275
297
|
"""Cover wind_down terminate failure (lines 1299-1301)."""
|
|
276
|
-
with
|
|
277
|
-
|
|
278
|
-
|
|
298
|
+
with (
|
|
299
|
+
patch("iwa.core.models.Config"),
|
|
300
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache") as mock_cache,
|
|
301
|
+
patch("iwa.plugins.olas.service_manager.staking.ContractCache", mock_cache),
|
|
302
|
+
):
|
|
279
303
|
mock_cache.return_value.get_contract.side_effect = lambda cls, *a, **k: cls(*a, **k)
|
|
280
304
|
sm = ServiceManager(mock_wallet)
|
|
281
305
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
@@ -293,8 +317,10 @@ def test_sm_wind_down_terminate_fails(mock_wallet):
|
|
|
293
317
|
|
|
294
318
|
def test_sm_wind_down_unbond_fails(mock_wallet):
|
|
295
319
|
"""Cover wind_down unbond failure (lines 1315-1317)."""
|
|
296
|
-
with
|
|
297
|
-
|
|
320
|
+
with (
|
|
321
|
+
patch("iwa.core.models.Config"),
|
|
322
|
+
patch("iwa.plugins.olas.service_manager.base.ContractCache"),
|
|
323
|
+
):
|
|
298
324
|
sm = ServiceManager(mock_wallet)
|
|
299
325
|
sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
|
|
300
326
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Tool to drain specific accounts to a master address."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
# Ensure src is in pythonpath
|
|
10
|
+
sys.path.append(str(Path(__file__).resolve().parents[2]))
|
|
11
|
+
|
|
12
|
+
from loguru import logger
|
|
13
|
+
|
|
14
|
+
from iwa.core.constants import SECRETS_PATH
|
|
15
|
+
from iwa.core.wallet import Wallet
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> None:
|
|
19
|
+
"""Run the account draining tool."""
|
|
20
|
+
parser = argparse.ArgumentParser(description="Drain specific accounts to master.")
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"tags",
|
|
23
|
+
nargs="+",
|
|
24
|
+
help="List of account tags to drain (or 'all' for all configured accounts)",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--chain",
|
|
28
|
+
"-c",
|
|
29
|
+
default="gnosis",
|
|
30
|
+
help="Target chain (default: gnosis)",
|
|
31
|
+
)
|
|
32
|
+
args = parser.parse_args()
|
|
33
|
+
|
|
34
|
+
# Load secrets
|
|
35
|
+
if SECRETS_PATH.exists():
|
|
36
|
+
load_dotenv(SECRETS_PATH, override=True)
|
|
37
|
+
|
|
38
|
+
wallet = Wallet()
|
|
39
|
+
|
|
40
|
+
tags = args.tags
|
|
41
|
+
if "all" in tags:
|
|
42
|
+
# Get all configured accounts except master
|
|
43
|
+
all_accounts = wallet.account_service.get_account_data()
|
|
44
|
+
tags = [acc.tag for acc in all_accounts.values() if acc.tag != "master"]
|
|
45
|
+
logger.info(f"Draining ALL accounts: {tags}")
|
|
46
|
+
|
|
47
|
+
for tag in tags:
|
|
48
|
+
logger.info(f"Processing drain for account: {tag}")
|
|
49
|
+
try:
|
|
50
|
+
tx_hash = wallet.drain(from_address_or_tag=tag, chain_name=args.chain)
|
|
51
|
+
if tx_hash:
|
|
52
|
+
logger.success(f"Drain tx sent for {tag}: {tx_hash}")
|
|
53
|
+
# Optional: link to explorer
|
|
54
|
+
else:
|
|
55
|
+
logger.warning(f"Drain failed or nothing to drain for {tag}")
|
|
56
|
+
except Exception:
|
|
57
|
+
logger.exception(f"Error draining {tag}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
main()
|
iwa/tools/list_contracts.py
CHANGED
|
@@ -101,6 +101,7 @@ def print_table(console, contract_data, chain_name, sort_criterion):
|
|
|
101
101
|
table.add_column("Available Rewards", justify="right", style="yellow")
|
|
102
102
|
table.add_column("Contract Balance", justify="right", style="blue")
|
|
103
103
|
table.add_column("Epoch End (UTC)", justify="right", style="white")
|
|
104
|
+
table.add_column("Epoch End (Local)", justify="right", style="white")
|
|
104
105
|
|
|
105
106
|
for item in contract_data:
|
|
106
107
|
if item.get("error"):
|
|
@@ -113,6 +114,7 @@ def print_table(console, contract_data, chain_name, sort_criterion):
|
|
|
113
114
|
f"{item['rewards_olas']:,.2f} OLAS",
|
|
114
115
|
f"{item['balance_olas']:,.2f} OLAS",
|
|
115
116
|
item["epoch_end"].strftime("%Y-%m-%d %H:%M:%S"),
|
|
117
|
+
item["epoch_end"].astimezone().strftime("%Y-%m-%d %H:%M:%S"),
|
|
116
118
|
)
|
|
117
119
|
console.print(table)
|
|
118
120
|
|
iwa/tools/reset_env.py
CHANGED
|
@@ -25,6 +25,7 @@ def _reset_tenderly(profile: int) -> None:
|
|
|
25
25
|
env = {"PYTHONPATH": "src"}
|
|
26
26
|
# Merge with current env to keep PATH etc.
|
|
27
27
|
import os
|
|
28
|
+
|
|
28
29
|
full_env = os.environ.copy()
|
|
29
30
|
full_env.update(env)
|
|
30
31
|
|
|
@@ -110,7 +111,7 @@ def main():
|
|
|
110
111
|
parser.add_argument(
|
|
111
112
|
"--keep-data",
|
|
112
113
|
action="store_true",
|
|
113
|
-
help="Reset only Tenderly, keeping config.yaml and wallet.json intact."
|
|
114
|
+
help="Reset only Tenderly, keeping config.yaml and wallet.json intact.",
|
|
114
115
|
)
|
|
115
116
|
args = parser.parse_args()
|
|
116
117
|
|
iwa/tools/test_chainlist.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Script to verify ChainlistRPC functionality."""
|
|
2
|
+
|
|
2
3
|
from iwa.core.chainlist import ChainlistRPC
|
|
3
4
|
|
|
4
5
|
|
|
@@ -32,7 +33,10 @@ def main() -> None:
|
|
|
32
33
|
|
|
33
34
|
print("\nTracking info for first 5 RPCs:")
|
|
34
35
|
for node in all_rpcs[:5]:
|
|
35
|
-
print(
|
|
36
|
+
print(
|
|
37
|
+
f" - {node.url}: Tracking={node.is_tracking} (Privacy={node.privacy}, Tracking={node.tracking})"
|
|
38
|
+
)
|
|
39
|
+
|
|
36
40
|
|
|
37
41
|
if __name__ == "__main__":
|
|
38
42
|
main()
|
iwa/tui/screens/wallets.py
CHANGED
|
@@ -330,9 +330,7 @@ class WalletsScreen(VerticalScroll):
|
|
|
330
330
|
|
|
331
331
|
def _fetch_single_token_balance(self, address: str, token: str, chain_name: str) -> str:
|
|
332
332
|
"""Fetch a single token balance using BalanceService."""
|
|
333
|
-
val_token = self.wallet.balance_service.
|
|
334
|
-
address, token, chain_name
|
|
335
|
-
)
|
|
333
|
+
val_token = self.wallet.balance_service.get_erc20_balance_eth(address, token, chain_name)
|
|
336
334
|
val_token_str = f"{val_token:.4f}" if val_token is not None else "-"
|
|
337
335
|
|
|
338
336
|
if val_token is not None:
|
|
@@ -442,7 +440,7 @@ class WalletsScreen(VerticalScroll):
|
|
|
442
440
|
def handler(tag):
|
|
443
441
|
if tag is not None:
|
|
444
442
|
tag = tag or f"Account {len(self.wallet.key_storage.accounts) + 1}"
|
|
445
|
-
self.wallet.key_storage.
|
|
443
|
+
self.wallet.key_storage.generate_new_account(tag)
|
|
446
444
|
self.notify(f"Created new EOA: {escape(tag)}")
|
|
447
445
|
self.refresh_accounts()
|
|
448
446
|
|
iwa/web/routers/accounts.py
CHANGED
|
@@ -68,7 +68,7 @@ def get_accounts(
|
|
|
68
68
|
def create_eoa(request: Request, req: AccountCreateRequest, auth: bool = Depends(verify_auth)):
|
|
69
69
|
"""Create a new EOA account with the given tag."""
|
|
70
70
|
try:
|
|
71
|
-
wallet.key_storage.
|
|
71
|
+
wallet.key_storage.generate_new_account(req.tag)
|
|
72
72
|
return {"status": "success"}
|
|
73
73
|
except Exception as e:
|
|
74
74
|
raise HTTPException(status_code=400, detail=str(e)) from None
|
iwa/web/routers/olas/services.py
CHANGED
|
@@ -41,7 +41,9 @@ def _determine_bond_amount(req: CreateServiceRequest) -> int:
|
|
|
41
41
|
|
|
42
42
|
if req.token_address and req.staking_contract:
|
|
43
43
|
# If a contract is specified, we MUST use its requirements
|
|
44
|
-
staking_name =
|
|
44
|
+
staking_name = (
|
|
45
|
+
wallet.account_service.get_tag_by_address(req.staking_contract) or req.staking_contract
|
|
46
|
+
)
|
|
45
47
|
logger.info(f"Fetching requirements from {staking_name}...")
|
|
46
48
|
staking_contract = StakingContract(req.staking_contract, req.chain)
|
|
47
49
|
reqs = staking_contract.get_requirements()
|
|
@@ -73,6 +75,7 @@ def _determine_bond_amount(req: CreateServiceRequest) -> int:
|
|
|
73
75
|
)
|
|
74
76
|
return bond_amount
|
|
75
77
|
|
|
78
|
+
|
|
76
79
|
@router.post(
|
|
77
80
|
"/create",
|
|
78
81
|
summary="Create Service",
|
|
@@ -81,7 +84,6 @@ def _determine_bond_amount(req: CreateServiceRequest) -> int:
|
|
|
81
84
|
def create_service(req: CreateServiceRequest, auth: bool = Depends(verify_auth)):
|
|
82
85
|
"""Create a new Olas service using spin_up for seamless deployment."""
|
|
83
86
|
try:
|
|
84
|
-
|
|
85
87
|
from iwa.plugins.olas.contracts.staking import StakingContract
|
|
86
88
|
from iwa.plugins.olas.service_manager import ServiceManager
|
|
87
89
|
|
|
@@ -196,7 +198,9 @@ def deploy_service(
|
|
|
196
198
|
if staking_contract:
|
|
197
199
|
try:
|
|
198
200
|
staking_obj = StakingContract(staking_contract, service.chain_name)
|
|
199
|
-
staking_name =
|
|
201
|
+
staking_name = (
|
|
202
|
+
wallet.account_service.get_tag_by_address(staking_contract) or staking_contract
|
|
203
|
+
)
|
|
200
204
|
logger.info(f"Will stake in {staking_name} after deployment")
|
|
201
205
|
except Exception as e:
|
|
202
206
|
logger.warning(f"Could not set up staking contract: {e}")
|
|
@@ -230,7 +234,6 @@ def deploy_service(
|
|
|
230
234
|
raise HTTPException(status_code=400, detail=str(e)) from None
|
|
231
235
|
|
|
232
236
|
|
|
233
|
-
|
|
234
237
|
def _resolve_service_accounts(service) -> dict:
|
|
235
238
|
"""Resolve basic accounts info including owner_signer if applicable."""
|
|
236
239
|
accounts = {}
|
|
@@ -279,7 +282,9 @@ def _resolve_service_balances(service, chain: str) -> dict:
|
|
|
279
282
|
if role == "owner" and stored and hasattr(stored, "signers") and stored.signers:
|
|
280
283
|
signer_addr = stored.signers[0]
|
|
281
284
|
s_native = wallet.get_native_balance_eth(signer_addr, chain)
|
|
282
|
-
s_olas_wei = wallet.balance_service.get_erc20_balance_wei(
|
|
285
|
+
s_olas_wei = wallet.balance_service.get_erc20_balance_wei(
|
|
286
|
+
signer_addr, "OLAS", chain
|
|
287
|
+
)
|
|
283
288
|
s_olas = float(s_olas_wei) / 1e18 if s_olas_wei else 0
|
|
284
289
|
s_stored = wallet.key_storage.find_stored_account(signer_addr)
|
|
285
290
|
balances["owner_signer"] = {
|