iwa 0.0.0__py3-none-any.whl → 0.0.1a2__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.
Files changed (191) hide show
  1. conftest.py +22 -0
  2. iwa/__init__.py +1 -0
  3. iwa/__main__.py +6 -0
  4. iwa/core/__init__.py +1 -0
  5. iwa/core/chain/__init__.py +68 -0
  6. iwa/core/chain/errors.py +47 -0
  7. iwa/core/chain/interface.py +514 -0
  8. iwa/core/chain/manager.py +38 -0
  9. iwa/core/chain/models.py +128 -0
  10. iwa/core/chain/rate_limiter.py +193 -0
  11. iwa/core/cli.py +210 -0
  12. iwa/core/constants.py +28 -0
  13. iwa/core/contracts/__init__.py +1 -0
  14. iwa/core/contracts/contract.py +297 -0
  15. iwa/core/contracts/erc20.py +79 -0
  16. iwa/core/contracts/multisend.py +71 -0
  17. iwa/core/db.py +317 -0
  18. iwa/core/keys.py +361 -0
  19. iwa/core/mnemonic.py +385 -0
  20. iwa/core/models.py +344 -0
  21. iwa/core/monitor.py +209 -0
  22. iwa/core/plugins.py +45 -0
  23. iwa/core/pricing.py +91 -0
  24. iwa/core/services/__init__.py +17 -0
  25. iwa/core/services/account.py +57 -0
  26. iwa/core/services/balance.py +113 -0
  27. iwa/core/services/plugin.py +88 -0
  28. iwa/core/services/safe.py +392 -0
  29. iwa/core/services/transaction.py +172 -0
  30. iwa/core/services/transfer/__init__.py +166 -0
  31. iwa/core/services/transfer/base.py +260 -0
  32. iwa/core/services/transfer/erc20.py +247 -0
  33. iwa/core/services/transfer/multisend.py +386 -0
  34. iwa/core/services/transfer/native.py +262 -0
  35. iwa/core/services/transfer/swap.py +326 -0
  36. iwa/core/settings.py +95 -0
  37. iwa/core/tables.py +60 -0
  38. iwa/core/test.py +27 -0
  39. iwa/core/tests/test_wallet.py +255 -0
  40. iwa/core/types.py +59 -0
  41. iwa/core/ui.py +99 -0
  42. iwa/core/utils.py +59 -0
  43. iwa/core/wallet.py +380 -0
  44. iwa/plugins/__init__.py +1 -0
  45. iwa/plugins/gnosis/__init__.py +5 -0
  46. iwa/plugins/gnosis/cow/__init__.py +6 -0
  47. iwa/plugins/gnosis/cow/quotes.py +148 -0
  48. iwa/plugins/gnosis/cow/swap.py +403 -0
  49. iwa/plugins/gnosis/cow/types.py +20 -0
  50. iwa/plugins/gnosis/cow_utils.py +44 -0
  51. iwa/plugins/gnosis/plugin.py +68 -0
  52. iwa/plugins/gnosis/safe.py +157 -0
  53. iwa/plugins/gnosis/tests/test_cow.py +227 -0
  54. iwa/plugins/gnosis/tests/test_safe.py +100 -0
  55. iwa/plugins/olas/__init__.py +5 -0
  56. iwa/plugins/olas/constants.py +106 -0
  57. iwa/plugins/olas/contracts/activity_checker.py +93 -0
  58. iwa/plugins/olas/contracts/base.py +10 -0
  59. iwa/plugins/olas/contracts/mech.py +49 -0
  60. iwa/plugins/olas/contracts/mech_marketplace.py +43 -0
  61. iwa/plugins/olas/contracts/service.py +215 -0
  62. iwa/plugins/olas/contracts/staking.py +403 -0
  63. iwa/plugins/olas/importer.py +736 -0
  64. iwa/plugins/olas/mech_reference.py +135 -0
  65. iwa/plugins/olas/models.py +110 -0
  66. iwa/plugins/olas/plugin.py +243 -0
  67. iwa/plugins/olas/scripts/test_full_mech_flow.py +259 -0
  68. iwa/plugins/olas/scripts/test_simple_lifecycle.py +74 -0
  69. iwa/plugins/olas/service_manager/__init__.py +60 -0
  70. iwa/plugins/olas/service_manager/base.py +113 -0
  71. iwa/plugins/olas/service_manager/drain.py +336 -0
  72. iwa/plugins/olas/service_manager/lifecycle.py +839 -0
  73. iwa/plugins/olas/service_manager/mech.py +322 -0
  74. iwa/plugins/olas/service_manager/staking.py +530 -0
  75. iwa/plugins/olas/tests/conftest.py +30 -0
  76. iwa/plugins/olas/tests/test_importer.py +128 -0
  77. iwa/plugins/olas/tests/test_importer_error_handling.py +349 -0
  78. iwa/plugins/olas/tests/test_mech_contracts.py +85 -0
  79. iwa/plugins/olas/tests/test_olas_contracts.py +249 -0
  80. iwa/plugins/olas/tests/test_olas_integration.py +561 -0
  81. iwa/plugins/olas/tests/test_olas_models.py +144 -0
  82. iwa/plugins/olas/tests/test_olas_view.py +258 -0
  83. iwa/plugins/olas/tests/test_olas_view_actions.py +137 -0
  84. iwa/plugins/olas/tests/test_olas_view_modals.py +120 -0
  85. iwa/plugins/olas/tests/test_plugin.py +70 -0
  86. iwa/plugins/olas/tests/test_plugin_full.py +212 -0
  87. iwa/plugins/olas/tests/test_service_lifecycle.py +150 -0
  88. iwa/plugins/olas/tests/test_service_manager.py +1065 -0
  89. iwa/plugins/olas/tests/test_service_manager_errors.py +208 -0
  90. iwa/plugins/olas/tests/test_service_manager_flows.py +497 -0
  91. iwa/plugins/olas/tests/test_service_manager_mech.py +135 -0
  92. iwa/plugins/olas/tests/test_service_manager_rewards.py +360 -0
  93. iwa/plugins/olas/tests/test_service_manager_validation.py +145 -0
  94. iwa/plugins/olas/tests/test_service_staking.py +342 -0
  95. iwa/plugins/olas/tests/test_staking_integration.py +269 -0
  96. iwa/plugins/olas/tests/test_staking_validation.py +109 -0
  97. iwa/plugins/olas/tui/__init__.py +1 -0
  98. iwa/plugins/olas/tui/olas_view.py +952 -0
  99. iwa/tools/check_profile.py +67 -0
  100. iwa/tools/release.py +111 -0
  101. iwa/tools/reset_env.py +111 -0
  102. iwa/tools/reset_tenderly.py +362 -0
  103. iwa/tools/restore_backup.py +82 -0
  104. iwa/tui/__init__.py +1 -0
  105. iwa/tui/app.py +174 -0
  106. iwa/tui/modals/__init__.py +5 -0
  107. iwa/tui/modals/base.py +406 -0
  108. iwa/tui/rpc.py +63 -0
  109. iwa/tui/screens/__init__.py +1 -0
  110. iwa/tui/screens/wallets.py +749 -0
  111. iwa/tui/tests/test_app.py +125 -0
  112. iwa/tui/tests/test_rpc.py +139 -0
  113. iwa/tui/tests/test_wallets_refactor.py +30 -0
  114. iwa/tui/tests/test_widgets.py +123 -0
  115. iwa/tui/widgets/__init__.py +5 -0
  116. iwa/tui/widgets/base.py +100 -0
  117. iwa/tui/workers.py +42 -0
  118. iwa/web/dependencies.py +76 -0
  119. iwa/web/models.py +76 -0
  120. iwa/web/routers/accounts.py +115 -0
  121. iwa/web/routers/olas/__init__.py +24 -0
  122. iwa/web/routers/olas/admin.py +169 -0
  123. iwa/web/routers/olas/funding.py +135 -0
  124. iwa/web/routers/olas/general.py +29 -0
  125. iwa/web/routers/olas/services.py +378 -0
  126. iwa/web/routers/olas/staking.py +341 -0
  127. iwa/web/routers/state.py +65 -0
  128. iwa/web/routers/swap.py +617 -0
  129. iwa/web/routers/transactions.py +153 -0
  130. iwa/web/server.py +155 -0
  131. iwa/web/tests/test_web_endpoints.py +713 -0
  132. iwa/web/tests/test_web_olas.py +430 -0
  133. iwa/web/tests/test_web_swap.py +103 -0
  134. iwa-0.0.1a2.dist-info/METADATA +234 -0
  135. iwa-0.0.1a2.dist-info/RECORD +186 -0
  136. iwa-0.0.1a2.dist-info/entry_points.txt +2 -0
  137. iwa-0.0.1a2.dist-info/licenses/LICENSE +21 -0
  138. iwa-0.0.1a2.dist-info/top_level.txt +4 -0
  139. tests/legacy_cow.py +248 -0
  140. tests/legacy_safe.py +93 -0
  141. tests/legacy_transaction_retry_logic.py +51 -0
  142. tests/legacy_tui.py +440 -0
  143. tests/legacy_wallets_screen.py +554 -0
  144. tests/legacy_web.py +243 -0
  145. tests/test_account_service.py +120 -0
  146. tests/test_balance_service.py +186 -0
  147. tests/test_chain.py +490 -0
  148. tests/test_chain_interface.py +210 -0
  149. tests/test_cli.py +139 -0
  150. tests/test_contract.py +195 -0
  151. tests/test_db.py +180 -0
  152. tests/test_drain_coverage.py +174 -0
  153. tests/test_erc20.py +95 -0
  154. tests/test_gnosis_plugin.py +111 -0
  155. tests/test_keys.py +449 -0
  156. tests/test_legacy_wallet.py +1285 -0
  157. tests/test_main.py +13 -0
  158. tests/test_mnemonic.py +217 -0
  159. tests/test_modals.py +109 -0
  160. tests/test_models.py +213 -0
  161. tests/test_monitor.py +202 -0
  162. tests/test_multisend.py +84 -0
  163. tests/test_plugin_service.py +119 -0
  164. tests/test_pricing.py +143 -0
  165. tests/test_rate_limiter.py +199 -0
  166. tests/test_reset_tenderly.py +202 -0
  167. tests/test_rpc_view.py +73 -0
  168. tests/test_safe_coverage.py +139 -0
  169. tests/test_safe_service.py +168 -0
  170. tests/test_service_manager_integration.py +61 -0
  171. tests/test_service_manager_structure.py +31 -0
  172. tests/test_service_transaction.py +176 -0
  173. tests/test_staking_router.py +71 -0
  174. tests/test_staking_simple.py +31 -0
  175. tests/test_tables.py +76 -0
  176. tests/test_transaction_service.py +161 -0
  177. tests/test_transfer_multisend.py +179 -0
  178. tests/test_transfer_native.py +220 -0
  179. tests/test_transfer_security.py +93 -0
  180. tests/test_transfer_structure.py +37 -0
  181. tests/test_transfer_swap_unit.py +155 -0
  182. tests/test_ui_coverage.py +66 -0
  183. tests/test_utils.py +53 -0
  184. tests/test_workers.py +91 -0
  185. tools/verify_drain.py +183 -0
  186. __init__.py +0 -2
  187. hello.py +0 -6
  188. iwa-0.0.0.dist-info/METADATA +0 -10
  189. iwa-0.0.0.dist-info/RECORD +0 -6
  190. iwa-0.0.0.dist-info/top_level.txt +0 -2
  191. {iwa-0.0.0.dist-info → iwa-0.0.1a2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,70 @@
1
+ """Tests for Olas Plugin."""
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ from iwa.plugins.olas.models import OlasConfig
6
+ from iwa.plugins.olas.plugin import OlasPlugin
7
+
8
+
9
+ class TestOlasPlugin:
10
+ """Tests for OlasPlugin class."""
11
+
12
+ def test_name_property(self):
13
+ """Test plugin name property."""
14
+ plugin = OlasPlugin()
15
+ assert plugin.name == "olas"
16
+
17
+ def test_config_model_returns_olas_config(self):
18
+ """Test config_model property returns OlasConfig."""
19
+ plugin = OlasPlugin()
20
+ assert plugin.config_model == OlasConfig
21
+
22
+ def test_get_cli_commands(self):
23
+ """Test get_cli_commands returns dict with commands."""
24
+ plugin = OlasPlugin()
25
+ commands = plugin.get_cli_commands()
26
+
27
+ assert isinstance(commands, dict)
28
+ assert "create" in commands
29
+ assert callable(commands["create"])
30
+
31
+ @patch("iwa.plugins.olas.plugin.Wallet")
32
+ @patch("iwa.plugins.olas.plugin.ServiceManager")
33
+ def test_create_service(self, mock_sm_class, mock_wallet_class):
34
+ """Test create_service calls ServiceManager.create."""
35
+ mock_wallet = MagicMock()
36
+ mock_wallet_class.return_value = mock_wallet
37
+
38
+ mock_manager = MagicMock()
39
+ mock_sm_class.return_value = mock_manager
40
+
41
+ plugin = OlasPlugin()
42
+ plugin.create_service(
43
+ chain_name="gnosis",
44
+ owner="0x1234",
45
+ token="OLAS",
46
+ bond=100,
47
+ )
48
+
49
+ mock_wallet_class.assert_called_once()
50
+ mock_sm_class.assert_called_once_with(mock_wallet)
51
+ mock_manager.create.assert_called_once_with("gnosis", "0x1234", "OLAS", 100)
52
+
53
+ @patch("iwa.plugins.olas.plugin.Wallet")
54
+ @patch("iwa.plugins.olas.plugin.ServiceManager")
55
+ def test_create_service_defaults(self, mock_sm_class, mock_wallet_class):
56
+ """Test create_service with default parameters."""
57
+ mock_wallet = MagicMock()
58
+ mock_wallet_class.return_value = mock_wallet
59
+
60
+ mock_manager = MagicMock()
61
+ mock_sm_class.return_value = mock_manager
62
+
63
+ plugin = OlasPlugin()
64
+ # Note: when called directly (not via typer), defaults are OptionInfo objects
65
+ # so we can only verify the method was called
66
+ plugin.create_service()
67
+
68
+ mock_wallet_class.assert_called_once()
69
+ mock_sm_class.assert_called_once_with(mock_wallet)
70
+ mock_manager.create.assert_called_once() # Any arguments
@@ -0,0 +1,212 @@
1
+ """Integration tests for OlasPlugin."""
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ import pytest
6
+ import typer
7
+ from typer.testing import CliRunner
8
+
9
+ from iwa.plugins.olas.importer import DiscoveredKey, DiscoveredService, ImportResult
10
+ from iwa.plugins.olas.plugin import OlasPlugin
11
+
12
+
13
+ @pytest.fixture
14
+ def plugin():
15
+ """Mock Olas plugin."""
16
+ return OlasPlugin()
17
+
18
+
19
+ @pytest.fixture
20
+ def runner():
21
+ """CLI runner fixture."""
22
+ return CliRunner()
23
+
24
+
25
+ def test_plugin_metadata(plugin):
26
+ """Test plugin metadata methods."""
27
+ assert plugin.name == "olas"
28
+ assert plugin.config_model.__name__ == "OlasConfig"
29
+ assert "create" in plugin.get_cli_commands()
30
+ assert "import" in plugin.get_cli_commands()
31
+
32
+
33
+ def test_get_tui_view(plugin):
34
+ """Test TUI view creation."""
35
+ with patch("iwa.plugins.olas.tui.olas_view.OlasView") as mock_view:
36
+ view = plugin.get_tui_view()
37
+ assert view is not None
38
+ mock_view.assert_called_once()
39
+
40
+
41
+ def test_create_service_cli(plugin, runner):
42
+ """Test create_service CLI command."""
43
+ app = typer.Typer()
44
+ app.command()(plugin.create_service)
45
+
46
+ with (
47
+ patch("iwa.plugins.olas.plugin.ServiceManager") as mock_sm_cls,
48
+ patch("iwa.plugins.olas.plugin.Wallet"),
49
+ ):
50
+ mock_sm = mock_sm_cls.return_value
51
+ result = runner.invoke(app, ["--chain", "gnosis", "--bond", "1"])
52
+ assert result.exit_code == 0
53
+ mock_sm.create.assert_called_once()
54
+
55
+
56
+ def test_import_services_cli_scan_only(plugin, runner):
57
+ """Test import_services CLI in dry-run mode."""
58
+ app = typer.Typer()
59
+ app.command()(plugin.import_services)
60
+
61
+ with (
62
+ patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls,
63
+ patch.object(OlasPlugin, "_get_safe_signers", return_value=(None, None)),
64
+ ):
65
+ mock_importer = mock_importer_cls.return_value
66
+ mock_importer.scan_directory.return_value = [
67
+ DiscoveredService(service_id=1, service_name="Test", chain_name="gnosis")
68
+ ]
69
+
70
+ # Test dry-run
71
+ result = runner.invoke(app, ["/tmp/test", "--dry-run"])
72
+ assert result.exit_code == 0
73
+ assert "Found 1 service(s)" in result.output
74
+ assert "Dry run mode" in result.output
75
+
76
+
77
+ def test_import_services_cli_full(plugin, runner):
78
+ """Test full import_services CLI with confirmation."""
79
+ app = typer.Typer()
80
+ app.command()(plugin.import_services)
81
+
82
+ with patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls:
83
+ mock_importer = mock_importer_cls.return_value
84
+ # Mock discovered service with an encrypted key to trigger password prompt
85
+ key = DiscoveredKey(address="0x1", is_encrypted=True, role="agent")
86
+ service = DiscoveredService(
87
+ service_id=1, service_name="Test", chain_name="gnosis", keys=[key]
88
+ )
89
+ mock_importer.scan_directory.return_value = [service]
90
+
91
+ # Mock successful import
92
+ mock_importer.import_service.return_value = ImportResult(
93
+ success=True, message="Imported", imported_services=["gnosis:1"]
94
+ )
95
+
96
+ # Test with -y and providing password
97
+ result = runner.invoke(app, ["/tmp/test", "-y", "-p", "secret"])
98
+ assert result.exit_code == 0
99
+ assert "Imported services: gnosis:1" in result.output
100
+ assert "Summary" in result.output
101
+
102
+
103
+ def test_get_safe_signers_edge_cases(plugin):
104
+ """Test _get_safe_signers with various failure scenarios."""
105
+ # 1. No RPC configured
106
+ with patch("iwa.core.settings.settings") as mock_settings:
107
+ mock_settings.gnosis_rpc = None
108
+ signers, exists = plugin._get_safe_signers("0x1", "gnosis")
109
+ assert signers is None
110
+ assert exists is None
111
+
112
+ # 2. Safe doesn't exist (raises exception)
113
+ with patch("iwa.core.settings.settings") as mock_settings:
114
+ mock_settings.gnosis_rpc = MagicMock()
115
+ with patch("safe_eth.eth.EthereumClient"), patch("safe_eth.safe.Safe") as mock_safe_cls:
116
+ mock_safe = mock_safe_cls.return_value
117
+ mock_safe.retrieve_owners.side_effect = Exception("Generic error")
118
+
119
+ signers, exists = plugin._get_safe_signers("0x1", "gnosis")
120
+ assert signers == []
121
+ assert exists is False
122
+
123
+ # 3. Success path
124
+ with patch("iwa.core.settings.settings") as mock_settings:
125
+ mock_settings.gnosis_rpc = MagicMock()
126
+ with patch("safe_eth.eth.EthereumClient"), patch("safe_eth.safe.Safe") as mock_safe_cls:
127
+ mock_safe = mock_safe_cls.return_value
128
+ mock_safe.retrieve_owners.return_value = ["0xAgent"]
129
+
130
+ signers, exists = plugin._get_safe_signers("0x1", "gnosis")
131
+ assert signers == ["0xAgent"]
132
+ assert exists is True
133
+
134
+
135
+ def test_import_services_cli_abort(plugin, runner):
136
+ """Test import_services CLI aborting on confirmation."""
137
+ app = typer.Typer()
138
+ app.command()(plugin.import_services)
139
+
140
+ with patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls:
141
+ mock_importer = mock_importer_cls.return_value
142
+ mock_importer.scan_directory.return_value = [
143
+ DiscoveredService(service_id=1, service_name="Test", chain_name="gnosis")
144
+ ]
145
+
146
+ # Simulate 'n' to confirmation prompt
147
+ result = runner.invoke(app, ["/tmp/test"], input="n\n")
148
+ assert result.exit_code == 0
149
+ assert "Aborted" in result.output
150
+ mock_importer.import_service.assert_not_called()
151
+
152
+
153
+ def test_import_services_cli_no_services(plugin, runner):
154
+ """Test import_services CLI when no services are found."""
155
+ app = typer.Typer()
156
+ app.command()(plugin.import_services)
157
+
158
+ with patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls:
159
+ mock_importer = mock_importer_cls.return_value
160
+ mock_importer.scan_directory.return_value = []
161
+
162
+ result = runner.invoke(app, ["/tmp/test"])
163
+ assert result.exit_code == 0
164
+ assert "No Olas services found" in result.output
165
+
166
+
167
+ def test_import_services_cli_complex_display(plugin, runner):
168
+ """Test import_services CLI display logic with Safe verification and signer check."""
169
+ app = typer.Typer()
170
+ app.command()(plugin.import_services)
171
+
172
+ with (
173
+ patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls,
174
+ patch.object(OlasPlugin, "_get_safe_signers") as mock_get_signers,
175
+ ):
176
+ mock_importer = mock_importer_cls.return_value
177
+ # 1. Service with valid Safe and agent is signer
178
+ key = DiscoveredKey(address="0xAgent", role="agent")
179
+ service = DiscoveredService(service_id=1, safe_address="0xSafe", keys=[key])
180
+ mock_importer.scan_directory.return_value = [service]
181
+
182
+ # Mock Safe exists with Agent as signer
183
+ mock_get_signers.return_value = (["0xAgent"], True)
184
+
185
+ result = runner.invoke(app, ["/tmp/test", "--dry-run"])
186
+ assert "0xSafe" in result.output
187
+ assert "✓" in result.output
188
+ assert "0xAgent 🔓 plaintext" in result.output # Not a warning
189
+
190
+ # 2. Service where agent is NOT a signer
191
+ mock_get_signers.return_value = (["0xOther"], True)
192
+ result = runner.invoke(app, ["/tmp/test", "--dry-run"])
193
+ assert "NOT A SIGNER OF THE SAFE" in result.output
194
+
195
+
196
+ def test_import_services_cli_password_prompt(plugin, runner):
197
+ """Test import_services CLI prompting for password."""
198
+ app = typer.Typer()
199
+ app.command()(plugin.import_services)
200
+
201
+ with patch("iwa.plugins.olas.importer.OlasServiceImporter") as mock_importer_cls:
202
+ mock_importer = mock_importer_cls.return_value
203
+ key = DiscoveredKey(address="0x1", is_encrypted=True, role="agent")
204
+ service = DiscoveredService(service_id=1, keys=[key])
205
+ mock_importer.scan_directory.return_value = [service]
206
+ mock_importer.import_service.return_value = ImportResult(success=True, message="OK")
207
+
208
+ # input="y\nsecret\n" -> 'y' for confirm import, 'secret' for password prompt
209
+ result = runner.invoke(app, ["/tmp/test"], input="y\nsecret\n")
210
+ assert result.exit_code == 0
211
+ assert "password" in result.output.lower()
212
+ mock_importer.import_service.assert_called_with(service, "secret")
@@ -0,0 +1,150 @@
1
+ """Tests for Olas service lifecycle: create, activate, register, deploy."""
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ import pytest
6
+
7
+ from iwa.plugins.olas.contracts.service import ServiceState
8
+ from iwa.plugins.olas.importer import DiscoveredKey, DiscoveredService, OlasServiceImporter
9
+ from iwa.plugins.olas.models import OlasConfig, Service
10
+ from iwa.plugins.olas.service_manager import ServiceManager
11
+
12
+
13
+ @pytest.fixture
14
+ def mock_wallet():
15
+ """Mock wallet with master account."""
16
+ w = MagicMock()
17
+ w.master_account.address = "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
18
+ return w
19
+
20
+
21
+ @pytest.fixture
22
+ def sm(mock_wallet):
23
+ """ServiceManager fixture."""
24
+ with (
25
+ patch("iwa.core.models.Config"),
26
+ patch("iwa.core.contracts.contract.ChainInterfaces") as mock_ci,
27
+ ):
28
+ mock_ci.get_instance.return_value.get.return_value.chain.get_token_address.side_effect = (
29
+ lambda x: x
30
+ )
31
+ return ServiceManager(mock_wallet)
32
+
33
+
34
+ @pytest.fixture
35
+ def importer(mock_wallet):
36
+ """OlasServiceImporter fixture."""
37
+ with patch("iwa.core.models.Config"):
38
+ return OlasServiceImporter(mock_wallet)
39
+
40
+
41
+ # --- ServiceManager Edge Cases ---
42
+
43
+
44
+ def test_sm_create_utility_not_found(sm):
45
+ """Target service_manager.py:203 (utility not found)."""
46
+ with patch("iwa.plugins.olas.constants.OLAS_CONTRACTS", {"gnosis": {}}):
47
+ sm.wallet.sign_and_send_transaction.return_value = (True, {"status": 1})
48
+ # Mocking registry.extract_events which is what sm.create now uses
49
+ sm.registry.extract_events = MagicMock(
50
+ return_value=[{"name": "CreateService", "args": {"serviceId": 42}}]
51
+ )
52
+
53
+ sid = sm.create("gnosis", "test")
54
+ assert sid == 42
55
+
56
+
57
+ def test_sm_create_approve_fail(sm):
58
+ """Target service_manager.py:217 (approve fail)."""
59
+ sm.wallet.sign_and_send_transaction.return_value = (True, {"status": 1})
60
+ sm.registry.extract_events = MagicMock(
61
+ return_value=[{"name": "CreateService", "args": {"serviceId": 42}}]
62
+ )
63
+
64
+ sm.transfer_service.approve_erc20.return_value = False
65
+ sid = sm.create(
66
+ "gnosis", "test", token_address_or_tag="0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
67
+ )
68
+ assert sid == 42
69
+
70
+
71
+ def test_sm_activate_not_preregistration(sm):
72
+ """Target service_manager.py:228 (state mismatch)."""
73
+ sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
74
+ sm.registry = MagicMock()
75
+ sm.registry.get_service.return_value = {"state": ServiceState.DEPLOYED}
76
+ success = sm.activate_registration()
77
+ assert success is False
78
+
79
+
80
+ def test_sm_checkpoint_check_exception(sm):
81
+ """Target service_manager.py:597 (checkpoint check exception)."""
82
+ with patch("iwa.plugins.olas.contracts.staking.StakingContract") as mock_stk_cls:
83
+ mock_stk = mock_stk_cls.return_value
84
+ mock_stk.is_checkpoint_needed.side_effect = Exception("error")
85
+ success = sm.call_checkpoint("gnosis")
86
+ assert success is False
87
+
88
+
89
+ def test_sm_stake_fail(sm):
90
+ """Target service_manager.py:690 (stake fail)."""
91
+ addr = "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
92
+ sm.service = Service(service_name="t", chain_name="gnosis", service_id=1)
93
+ sm.registry = MagicMock()
94
+ sm.registry.get_service.return_value = {"state": ServiceState.DEPLOYED, "security_deposit": 1}
95
+ sm.transfer_service.approve_erc20.return_value = True
96
+ sm.wallet.sign_and_send_transaction.return_value = (False, None)
97
+
98
+ with (
99
+ patch("iwa.plugins.olas.contracts.staking.StakingContract") as mock_stk_cls,
100
+ patch("iwa.plugins.olas.service_manager.staking.ERC20Contract"),
101
+ ):
102
+ mock_stk = mock_stk_cls.return_value
103
+ mock_stk.get_service_info.return_value = {"staking_state": 1}
104
+ mock_stk.staking_token_address = addr
105
+ mock_stk.get_requirements.return_value = {
106
+ "staking_token": addr,
107
+ "min_staking_deposit": 50000000000000000000,
108
+ "num_agent_instances": 1,
109
+ "required_agent_bond": 50000000000000000000,
110
+ }
111
+ success = sm.stake(mock_stk)
112
+ assert success is False
113
+
114
+
115
+ # --- Importer Edge Cases ---
116
+
117
+
118
+ def test_importer_encrypted_no_pwd(importer):
119
+ """Target importer.py:192 (encrypted key without password)."""
120
+ addr = "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
121
+ key = DiscoveredKey(address=addr, is_encrypted=True, encrypted_keystore={"crypto": {}})
122
+ importer.key_storage.find_stored_account.return_value = None
123
+ success, msg = importer._import_key(key, "service", password=None)
124
+ assert success is False
125
+ assert "password" in msg
126
+
127
+
128
+ def test_importer_safe_duplicate(importer):
129
+ """Target importer.py:308 (duplicate safe)."""
130
+ addr = "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
131
+ service = DiscoveredService(service_id=1, chain_name="gnosis", safe_address=addr)
132
+ importer.key_storage.find_stored_account.return_value = MagicMock()
133
+ success, msg = importer._import_safe(service)
134
+ assert success is False
135
+ assert msg == "duplicate"
136
+
137
+
138
+ def test_olas_config_remove_not_exists():
139
+ """Target models.py:85-88 (remove service not exists)."""
140
+ config = OlasConfig()
141
+ assert config.remove_service("not:exists") is False
142
+
143
+
144
+ def test_olas_config_get_service_by_multisig():
145
+ """Target models.py:106-110 (get by multisig)."""
146
+ addr = "0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB"
147
+ s = Service(service_name="t", chain_name="g", service_id=1, multisig_address=addr)
148
+ config = OlasConfig(services={"g:1": s})
149
+ assert config.get_service_by_multisig(addr) == s
150
+ assert config.get_service_by_multisig("0x0000000000000000000000000000000000000000") is None