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,497 @@
1
+ """Lifecycle and operational flow tests for Olas ServiceManager."""
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.contracts.staking import StakingState
9
+ from iwa.plugins.olas.service_manager import ServiceManager
10
+
11
+
12
+ @pytest.fixture
13
+ def mock_wallet():
14
+ """Mock wallet for testing."""
15
+ wallet = MagicMock()
16
+ wallet.master_account.address = "0x1234567890123456789012345678901234567890"
17
+ wallet.key_storage.get_account.return_value.address = (
18
+ "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
19
+ )
20
+ wallet.key_storage.create_account.return_value.address = (
21
+ "0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
22
+ )
23
+ wallet.sign_and_send_transaction.return_value = (True, {"status": 1})
24
+ wallet.send.return_value = (True, {"status": 1})
25
+ wallet.balance_service = MagicMock()
26
+ wallet.transfer_service = MagicMock()
27
+ # Default behavior for balance checking calls to avoid AttributeError
28
+ wallet.balance_service.get_erc20_balance_wei.return_value = 10**20
29
+ return wallet
30
+
31
+
32
+ @pytest.fixture
33
+ def mock_config():
34
+ """Mock config for testing."""
35
+ config = MagicMock()
36
+ olas_config = MagicMock()
37
+ olas_config.services = {}
38
+ olas_config.get_service.return_value = None
39
+ olas_config.get_service.return_value = None
40
+ config.plugins = {"olas": olas_config}
41
+ config.save_config = MagicMock()
42
+ return config
43
+
44
+
45
+ @patch("iwa.plugins.olas.service_manager.base.Config")
46
+ @patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
47
+ @patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
48
+ @patch("iwa.plugins.olas.service_manager.staking.ERC20Contract")
49
+ def test_create_service_success(
50
+ mock_erc20, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
51
+ ):
52
+ """Test successful service creation."""
53
+ # Setup Config with new OlasConfig structure
54
+ mock_config_inst = mock_config_cls.return_value
55
+ mock_olas_config = MagicMock()
56
+ mock_olas_config.services = {}
57
+ mock_olas_config.get_service.return_value = None
58
+ mock_olas_config.get_service.return_value = None
59
+ mock_config_inst.plugins = {"olas": mock_olas_config}
60
+ mock_config_inst.save_config = MagicMock()
61
+
62
+ # Setup Registry to return event
63
+ mock_registry_inst = mock_registry_contract.return_value
64
+ mock_registry_inst.extract_events.return_value = [
65
+ {"name": "CreateService", "args": {"serviceId": 123}}
66
+ ]
67
+
68
+ # Setup Manager
69
+ manager = ServiceManager(mock_wallet, service_key="gnosis:123")
70
+
71
+ # Patch ChainInterfaces
72
+ with patch("iwa.plugins.olas.service_manager.base.ChainInterfaces") as mock_chains:
73
+ mock_chains.return_value.get.return_value.chain.get_token_address.return_value = None
74
+
75
+ # Call create
76
+ service_id = manager.create(bond_amount_wei=10)
77
+
78
+ assert service_id == 123
79
+ # Verify add_service was called
80
+ mock_olas_config.add_service.assert_called_once()
81
+ # Verify config was saved
82
+ mock_config_inst.save_config.assert_called_once()
83
+
84
+
85
+ @patch("iwa.plugins.olas.service_manager.base.Config")
86
+ @patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
87
+ @patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
88
+ @patch("iwa.plugins.olas.service_manager.staking.ERC20Contract")
89
+ def test_create_service_failures(
90
+ mock_erc20, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
91
+ ):
92
+ """Test service creation failure modes."""
93
+ mock_config_inst = mock_config_cls.return_value
94
+ mock_olas_config = MagicMock()
95
+ mock_olas_config.get_service.return_value = None
96
+ mock_config_inst.plugins = {"olas": mock_olas_config}
97
+
98
+ manager = ServiceManager(mock_wallet, service_key="gnosis:123")
99
+
100
+ # 1. Transaction fails
101
+ mock_wallet.sign_and_send_transaction.return_value = (False, {})
102
+ with patch("iwa.plugins.olas.service_manager.base.ChainInterfaces") as mock_chains:
103
+ mock_chains.return_value.get.return_value.chain.get_token_address.return_value = None
104
+ res = manager.create()
105
+ assert res is None
106
+
107
+ # Reset wallet
108
+ mock_wallet.sign_and_send_transaction.return_value = (True, {})
109
+
110
+ # 2. Event missing
111
+ mock_registry_contract.return_value.extract_events.return_value = []
112
+ with patch("iwa.plugins.olas.service_manager.base.ChainInterfaces") as mock_chains:
113
+ mock_chains.return_value.get.return_value.chain.get_token_address.return_value = None
114
+ res = manager.create()
115
+ # Should effectively return None/False depending on implementation,
116
+ # code says "if service_id is None: logger.error..." but proceeds to assign None
117
+ # and returns service_id which is None.
118
+ assert res is None
119
+
120
+ # 3. Approval fails
121
+ mock_registry_contract.return_value.extract_events.return_value = [
122
+ {"name": "CreateService", "args": {"serviceId": 123}}
123
+ ]
124
+ # First tx (create) success, Second tx (approve) fails
125
+ mock_wallet.sign_and_send_transaction.reset_mock()
126
+ mock_wallet.sign_and_send_transaction.return_value = (
127
+ None # Clear return_value to let side_effect work
128
+ )
129
+ mock_wallet.sign_and_send_transaction.side_effect = [(True, {}), (False, {})]
130
+ mock_wallet.transfer_service = MagicMock()
131
+ mock_wallet.transfer_service.approve_erc20.return_value = False
132
+
133
+ with patch("iwa.plugins.olas.service_manager.base.ChainInterfaces") as mock_chains:
134
+ mock_chains.return_value.get.return_value.chain.get_token_address.return_value = (
135
+ "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f"
136
+ )
137
+ res = manager.create(token_address_or_tag="0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f")
138
+ assert res == 123 or res is False # May succeed on create but fail on approval
139
+
140
+
141
+ @patch("iwa.plugins.olas.service_manager.base.Config")
142
+ @patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
143
+ @patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
144
+ @patch("iwa.plugins.olas.service_manager.staking.ERC20Contract")
145
+ def test_create_service_with_approval(
146
+ mock_erc20, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
147
+ ):
148
+ """Test service creation with token approval."""
149
+ mock_config_inst = mock_config_cls.return_value
150
+ mock_olas_config = MagicMock()
151
+ mock_olas_config.get_service.return_value = None
152
+ mock_config_inst.plugins = {"olas": mock_olas_config}
153
+
154
+ mock_registry_inst = mock_registry_contract.return_value
155
+ mock_registry_inst.extract_events.return_value = [
156
+ {"name": "CreateService", "args": {"serviceId": 123}}
157
+ ]
158
+
159
+ manager = ServiceManager(mock_wallet, service_key="gnosis:123")
160
+ mock_wallet.transfer_service = MagicMock()
161
+ mock_wallet.transfer_service.approve_erc20.return_value = True
162
+
163
+ with patch("iwa.plugins.olas.service_manager.base.ChainInterfaces") as mock_chains:
164
+ mock_chains.return_value.get.return_value.chain.get_token_address.return_value = (
165
+ "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f"
166
+ )
167
+ manager.create(token_address_or_tag="0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f")
168
+
169
+ # Verify wallet transfer_service was used for approval
170
+ assert mock_wallet.sign_and_send_transaction.call_count >= 1
171
+
172
+
173
+ @patch("iwa.plugins.olas.service_manager.base.Config")
174
+ @patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
175
+ @patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
176
+ def test_activate_registration(
177
+ mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet
178
+ ):
179
+ """Test service registration activation."""
180
+ mock_config_inst = mock_config_cls.return_value
181
+ mock_olas_config = MagicMock()
182
+ mock_service = MagicMock()
183
+ mock_service.service_id = 123
184
+ mock_service.chain_name = "gnosis"
185
+ mock_service.token_address = "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f"
186
+ mock_olas_config.get_service.return_value = mock_service
187
+ mock_config_inst.plugins = {"olas": mock_olas_config}
188
+
189
+ mock_registry_inst = mock_registry_contract.return_value
190
+ mock_registry_inst.get_service.return_value = {
191
+ "state": ServiceState.PRE_REGISTRATION,
192
+ "security_deposit": 50000000000000000000,
193
+ }
194
+ mock_registry_inst.extract_events.return_value = [{"name": "ActivateRegistration"}]
195
+
196
+ manager = ServiceManager(mock_wallet, service_key="gnosis:123")
197
+
198
+ # Explicitly set mock return values to ensure they aren't overridden
199
+ mock_wallet.balance_service.get_erc20_balance_wei.return_value = (
200
+ 50000000000000000000 * 2
201
+ ) # Enough balance
202
+ mock_wallet.transfer_service.get_erc20_allowance.return_value = (
203
+ 50000000000000000000 * 2
204
+ ) # Enough allowance
205
+
206
+ success = manager.activate_registration()
207
+ assert success is True
208
+
209
+ # Failures
210
+ # 1. Wrong state
211
+ mock_registry_inst.get_service.return_value = {
212
+ "state": ServiceState.DEPLOYED,
213
+ "security_deposit": 50000000000000000000,
214
+ }
215
+ assert manager.activate_registration() is False
216
+
217
+ # 2. Tx fail
218
+ mock_registry_inst.get_service.return_value = {
219
+ "state": ServiceState.PRE_REGISTRATION,
220
+ "security_deposit": 50000000000000000000,
221
+ }
222
+ mock_wallet.sign_and_send_transaction.return_value = (False, {})
223
+ assert manager.activate_registration() is False
224
+
225
+ # 3. Event missing
226
+ mock_wallet.sign_and_send_transaction.return_value = (True, {})
227
+ mock_registry_inst.extract_events.return_value = []
228
+ assert manager.activate_registration() is False
229
+
230
+
231
+ @patch("iwa.plugins.olas.service_manager.base.Config")
232
+ @patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
233
+ @patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
234
+ def test_register_agent(mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet):
235
+ """Test agent registration flow."""
236
+ mock_config_inst = mock_config_cls.return_value
237
+ mock_olas_config = MagicMock()
238
+ mock_service = MagicMock()
239
+ mock_service.service_id = 123
240
+ mock_service.chain_name = "gnosis"
241
+ mock_service.token_address = "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f"
242
+ mock_olas_config.get_service.return_value = mock_service
243
+ mock_config_inst.plugins = {"olas": mock_olas_config}
244
+
245
+ mock_registry_inst = mock_registry_contract.return_value
246
+ mock_registry_inst.get_service.return_value = {
247
+ "state": ServiceState.ACTIVE_REGISTRATION,
248
+ "security_deposit": 50000000000000000000,
249
+ }
250
+ mock_registry_inst.extract_events.return_value = [{"name": "RegisterInstance"}]
251
+
252
+ manager = ServiceManager(mock_wallet, service_key="gnosis:123")
253
+
254
+ success = manager.register_agent()
255
+ assert success is True
256
+ assert mock_olas_config.agent_address is not None
257
+
258
+ # Failures
259
+ # 1. Wrong state
260
+ mock_registry_inst.get_service.return_value = {
261
+ "state": ServiceState.PRE_REGISTRATION,
262
+ "security_deposit": 50000000000000000000,
263
+ }
264
+ assert manager.register_agent() is False
265
+
266
+ # 2. Tx fail
267
+ mock_registry_inst.get_service.return_value = {
268
+ "state": ServiceState.ACTIVE_REGISTRATION,
269
+ "security_deposit": 50000000000000000000,
270
+ }
271
+ mock_wallet.sign_and_send_transaction.return_value = (False, {})
272
+ assert manager.register_agent() is False
273
+
274
+ # 3. Event missing
275
+ mock_wallet.sign_and_send_transaction.return_value = (True, {})
276
+ mock_registry_inst.extract_events.return_value = []
277
+ assert manager.register_agent() is False
278
+
279
+
280
+ @patch("iwa.plugins.olas.service_manager.base.Config")
281
+ @patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
282
+ @patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
283
+ def test_deploy(mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet):
284
+ """Test service deployment."""
285
+ # Setup mock service
286
+ mock_service = MagicMock()
287
+ mock_service.service_id = 123
288
+ mock_service.chain_name = "gnosis"
289
+ mock_service.token_address = "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f"
290
+ mock_service.multisig_address = None
291
+
292
+ mock_olas_config = MagicMock()
293
+ mock_olas_config.get_service.return_value = mock_service
294
+
295
+ mock_config_inst = mock_config_cls.return_value
296
+ mock_config_inst.plugins = {"olas": mock_olas_config}
297
+ mock_config_inst.save_config = MagicMock()
298
+
299
+ mock_registry_inst = mock_registry_contract.return_value
300
+ mock_registry_inst.get_service.return_value = {
301
+ "state": ServiceState.FINISHED_REGISTRATION,
302
+ "security_deposit": 50000000000000000000,
303
+ }
304
+ mock_registry_inst.extract_events.return_value = [
305
+ {"name": "DeployService"},
306
+ {
307
+ "name": "CreateMultisigWithAgents",
308
+ "args": {"multisig": "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"},
309
+ },
310
+ ]
311
+
312
+ manager = ServiceManager(mock_wallet, service_key="gnosis:123")
313
+
314
+ multisig = manager.deploy()
315
+ assert multisig == "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
316
+ mock_config_inst.save_config.assert_called()
317
+
318
+ # Failures
319
+ # 1. Wrong state
320
+ mock_registry_inst.get_service.return_value = {
321
+ "state": ServiceState.PRE_REGISTRATION,
322
+ "security_deposit": 50000000000000000000,
323
+ }
324
+ assert manager.deploy() is False # returns False or None? Code says False if state wrong
325
+
326
+ # 2. Tx fail
327
+ mock_registry_inst.get_service.return_value = {
328
+ "state": ServiceState.FINISHED_REGISTRATION,
329
+ "security_deposit": 50000000000000000000,
330
+ }
331
+ mock_wallet.sign_and_send_transaction.return_value = (False, {})
332
+ assert manager.deploy() is None
333
+
334
+ # 3. Event missing (DeployService)
335
+ mock_wallet.sign_and_send_transaction.return_value = (True, {})
336
+ mock_registry_inst.extract_events.return_value = []
337
+ assert manager.deploy() is None
338
+
339
+ # 4. Multisig Missing
340
+ mock_registry_inst.extract_events.return_value = [{"name": "DeployService"}]
341
+ assert manager.deploy() is None
342
+
343
+
344
+ @patch("iwa.plugins.olas.service_manager.base.Config")
345
+ @patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
346
+ @patch("iwa.plugins.olas.service_manager.base.ServiceManagerContract")
347
+ def test_terminate(mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet):
348
+ """Test service termination."""
349
+ # Setup mock service
350
+ mock_service = MagicMock()
351
+ mock_service.service_id = 123
352
+ mock_service.chain_name = "gnosis"
353
+ mock_service.token_address = "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f"
354
+ mock_service.staking_contract_address = None # Not staked
355
+
356
+ mock_olas_config = MagicMock()
357
+ mock_olas_config.get_service.return_value = mock_service
358
+
359
+ mock_config_inst = mock_config_cls.return_value
360
+ mock_config_inst.plugins = {"olas": mock_olas_config}
361
+ mock_config_inst.save_config = MagicMock()
362
+
363
+ mock_registry_inst = mock_registry_contract.return_value
364
+ mock_registry_inst.get_service.return_value = {
365
+ "state": ServiceState.DEPLOYED,
366
+ "security_deposit": 50000000000000000000,
367
+ }
368
+ mock_registry_inst.extract_events.return_value = [{"name": "TerminateService"}]
369
+
370
+ manager = ServiceManager(mock_wallet, service_key="gnosis:123")
371
+
372
+ success = manager.terminate()
373
+ assert success is True
374
+
375
+ # Failures
376
+ # 1. Wrong state
377
+ mock_registry_inst.get_service.return_value = {
378
+ "state": ServiceState.PRE_REGISTRATION,
379
+ "security_deposit": 50000000000000000000,
380
+ }
381
+ assert manager.terminate() is False
382
+
383
+ # 2. Staked
384
+ mock_registry_inst.get_service.return_value = {
385
+ "state": ServiceState.DEPLOYED,
386
+ "security_deposit": 50000000000000000000,
387
+ }
388
+ manager.service.staking_contract_address = "0xStaked"
389
+ assert manager.terminate() is False
390
+ manager.service.staking_contract_address = None
391
+
392
+ # 3. Tx fail
393
+ mock_wallet.sign_and_send_transaction.return_value = (False, {})
394
+ assert manager.terminate() is False
395
+
396
+ # 4. No event
397
+ mock_wallet.sign_and_send_transaction.return_value = (True, {})
398
+ mock_registry_inst.extract_events.return_value = []
399
+ assert manager.terminate() is False
400
+
401
+
402
+ @patch("iwa.plugins.olas.service_manager.base.Config")
403
+ @patch("iwa.plugins.olas.service_manager.base.ServiceRegistryContract")
404
+ @patch(
405
+ "iwa.plugins.olas.service_manager.base.ServiceManagerContract"
406
+ ) # MUST mock specifically here
407
+ @patch("iwa.plugins.olas.service_manager.staking.ERC20Contract") # For checking balance
408
+ def test_stake(mock_erc20, mock_sm_contract, mock_registry_contract, mock_config_cls, mock_wallet):
409
+ """Test service staking."""
410
+ # Setup mock service
411
+ mock_service = MagicMock()
412
+ mock_service.service_id = 123
413
+ mock_service.chain_name = "gnosis"
414
+ mock_service.token_address = "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f"
415
+ mock_service.staking_contract_address = None
416
+
417
+ mock_olas_config = MagicMock()
418
+ mock_olas_config.get_service.return_value = mock_service
419
+
420
+ mock_config_inst = mock_config_cls.return_value
421
+ mock_config_inst.plugins = {"olas": mock_olas_config}
422
+ mock_config_inst.save_config = MagicMock()
423
+
424
+ mock_registry_inst = mock_registry_contract.return_value
425
+ mock_registry_inst.get_service.return_value = {
426
+ "state": ServiceState.DEPLOYED,
427
+ "security_deposit": 50000000000000000000,
428
+ }
429
+
430
+ manager = ServiceManager(mock_wallet, service_key="gnosis:123")
431
+
432
+ # Mock Staking Contract
433
+ mock_staking = MagicMock()
434
+ mock_staking.staking_token_address = "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f"
435
+ mock_staking.address = "0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd"
436
+ mock_staking.get_service_ids.return_value = [] # Not full
437
+ mock_staking.max_num_services = 10
438
+ mock_staking.min_staking_deposit = 100
439
+ mock_staking.extract_events.return_value = [{"name": "ServiceStaked"}]
440
+ mock_staking.get_staking_state.return_value = StakingState.STAKED
441
+ mock_staking.get_requirements.return_value = {
442
+ "staking_token": "0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f",
443
+ "min_staking_deposit": 50000000000000000000,
444
+ "num_agent_instances": 1,
445
+ "required_agent_bond": 50000000000000000000,
446
+ }
447
+
448
+ # Mock ERC20 balance check (100 OLAS = 100e18 wei, enough for 50 min deposit)
449
+ mock_erc20_inst = mock_erc20.return_value
450
+ mock_erc20_inst.balance_of_wei.return_value = 100000000000000000000 # 100 OLAS
451
+
452
+ success = manager.stake(mock_staking)
453
+ assert success is True
454
+ assert manager.service.staking_contract_address == "0xDDdDddDdDdddDDddDDddDDDDdDdDDdDDdDDDDDDd"
455
+ mock_config_inst.save_config.assert_called()
456
+
457
+ # Failures
458
+ # 1. State not deployed
459
+ mock_registry_inst.get_service.return_value = {
460
+ "state": ServiceState.PRE_REGISTRATION,
461
+ "security_deposit": 50000000000000000000,
462
+ }
463
+ assert manager.stake(mock_staking) is False
464
+
465
+ # 2. Full
466
+ mock_registry_inst.get_service.return_value = {
467
+ "state": ServiceState.DEPLOYED,
468
+ "security_deposit": 50000000000000000000,
469
+ }
470
+ mock_staking.get_service_ids.return_value = [0] * 10
471
+ assert manager.stake(mock_staking) is False
472
+ mock_staking.get_service_ids.return_value = []
473
+
474
+ # 3. Not enough funds
475
+ mock_erc20_inst.balance_of_wei.return_value = 50
476
+ assert manager.stake(mock_staking) is False
477
+ mock_erc20_inst.balance_of_wei.return_value = 200
478
+
479
+ # 4. Approve fail
480
+ mock_wallet.sign_and_send_transaction.return_value = (False, {})
481
+ assert manager.stake(mock_staking) is False
482
+
483
+ # 5. Stake fail
484
+ # First tx (approve) success, second (stake) fail
485
+ mock_wallet.sign_and_send_transaction.side_effect = [(True, {}), (False, {}), (False, {})]
486
+ assert manager.stake(mock_staking) is False
487
+
488
+ # 6. Event missing
489
+ mock_wallet.sign_and_send_transaction.side_effect = [(True, {}), (True, {}), (True, {})]
490
+ mock_staking.extract_events.return_value = []
491
+ assert manager.stake(mock_staking) is False
492
+
493
+ # 7. State check fail
494
+ mock_wallet.sign_and_send_transaction.side_effect = [(True, {}), (True, {}), (True, {})]
495
+ mock_staking.extract_events.return_value = [{"name": "ServiceStaked"}]
496
+ mock_staking.get_staking_state.return_value = StakingState.NOT_STAKED
497
+ assert manager.stake(mock_staking) is False
@@ -0,0 +1,135 @@
1
+ """Tests for Mech integration in ServiceManager."""
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ import pytest
6
+
7
+ from iwa.plugins.olas.constants import PAYMENT_TYPE_NATIVE
8
+ from iwa.plugins.olas.models import OlasConfig, Service
9
+ from iwa.plugins.olas.service_manager import ServiceManager
10
+
11
+ # Valid Ethereum address for testing
12
+ VALID_PRIORITY_MECH = "0x0000000000000000000000000000000000000001"
13
+ VALID_MULTISIG = "0x0000000000000000000000000000000000000002"
14
+ VALID_MARKETPLACE = "0x0000000000000000000000000000000000000003"
15
+
16
+
17
+ @pytest.fixture
18
+ def mock_wallet():
19
+ """Mock wallet fixture."""
20
+ wallet = MagicMock()
21
+ wallet.safe_service = MagicMock()
22
+ wallet.safe_service.execute_safe_transaction.return_value = "0xMockTxHash"
23
+ wallet.account_service = MagicMock()
24
+ return wallet
25
+
26
+
27
+ @pytest.fixture
28
+ def mock_service():
29
+ """Mock Service model."""
30
+ service = MagicMock(spec=Service)
31
+ service.service_id = 1
32
+ service.chain_name = "gnosis"
33
+ service.multisig_address = VALID_MULTISIG
34
+ service.staking_contract_address = "0xStakingAddress"
35
+ return service
36
+
37
+
38
+ @pytest.fixture
39
+ def mock_olas_config(mock_service):
40
+ """Mock OlasConfig."""
41
+ config = MagicMock(spec=OlasConfig)
42
+ config.get_service.return_value = mock_service
43
+ return config
44
+
45
+
46
+ @pytest.fixture
47
+ def service_manager(mock_wallet, mock_olas_config, mock_service):
48
+ """Create ServiceManager with mocks."""
49
+ with patch("iwa.plugins.olas.service_manager.Config") as mock_config_class:
50
+ mock_config = mock_config_class.return_value
51
+ mock_config.plugins = {"olas": mock_olas_config}
52
+
53
+ sm = ServiceManager(mock_wallet, service_key="gnosis:1")
54
+ sm.olas_config = mock_olas_config
55
+ sm.service = mock_service
56
+ # Mocking registry to avoid initialization calls
57
+ sm.registry = MagicMock()
58
+ sm.registry.chain_interface = MagicMock()
59
+ sm.registry.chain_interface.web3 = MagicMock()
60
+ sm.chain_interface = MagicMock()
61
+ sm.chain_interface.chain.name = "gnosis"
62
+ sm.chain_name = "gnosis"
63
+ return sm
64
+
65
+
66
+ class TestServiceManagerMech:
67
+ """Tests for Mech request functionality in ServiceManager."""
68
+
69
+ def test_send_mech_request_marketplace_requires_priority_mech(self, service_manager):
70
+ """Test that marketplace request requires priority_mech."""
71
+ data = b"marketplace data"
72
+
73
+ with patch(
74
+ "iwa.plugins.olas.service_manager.mech.MechMarketplaceContract"
75
+ ) as mock_market_class:
76
+ mock_market = mock_market_class.return_value
77
+ mock_market.prepare_request_tx.return_value = {
78
+ "data": "0xMarketplaceEncoded",
79
+ "value": 2 * 10**16,
80
+ }
81
+
82
+ # Should return None because priority_mech is not provided
83
+ tx_hash = service_manager.send_mech_request(
84
+ data=data,
85
+ use_marketplace=True,
86
+ )
87
+
88
+ assert tx_hash is None
89
+
90
+ def test_send_mech_request_marketplace(self, service_manager, mock_wallet):
91
+ """Test sending a marketplace Mech request."""
92
+ data = b"marketplace data"
93
+ payment_type_bytes = bytes.fromhex(PAYMENT_TYPE_NATIVE)
94
+ value = 2 * 10**16
95
+
96
+ # Mock the account resolution to return a Safe account
97
+ from iwa.core.models import StoredSafeAccount
98
+
99
+ mock_safe_account = MagicMock(spec=StoredSafeAccount)
100
+ mock_wallet.account_service.resolve_account.return_value = mock_safe_account
101
+
102
+ with patch(
103
+ "iwa.plugins.olas.service_manager.mech.MechMarketplaceContract"
104
+ ) as mock_market_class:
105
+ mock_market = mock_market_class.return_value
106
+ mock_market.prepare_request_tx.return_value = {
107
+ "data": "0xMarketplaceEncoded",
108
+ "value": value,
109
+ }
110
+ # Mock event extraction to simulate successful event
111
+ mock_market.extract_events.return_value = [{"name": "MarketplaceRequest"}]
112
+
113
+ # Mock wait_for_transaction_receipt
114
+ service_manager.registry.chain_interface.web3.eth.wait_for_transaction_receipt.return_value = {}
115
+
116
+ tx_hash = service_manager.send_mech_request(
117
+ data=data,
118
+ use_marketplace=True,
119
+ priority_mech=VALID_PRIORITY_MECH,
120
+ response_timeout=300, # Within bounds [60, 300]
121
+ value=value,
122
+ )
123
+
124
+ assert tx_hash == "0xMockTxHash"
125
+ # Note: max_delivery_rate now defaults to value
126
+ mock_market.prepare_request_tx.assert_called_once_with(
127
+ from_address=VALID_MULTISIG,
128
+ request_data=data,
129
+ priority_mech=VALID_PRIORITY_MECH,
130
+ response_timeout=300,
131
+ max_delivery_rate=value, # Defaults to value
132
+ payment_type=payment_type_bytes,
133
+ payment_data=b"",
134
+ value=value,
135
+ )