wayfinder-paths 0.1.15__py3-none-any.whl → 0.1.16__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.

Potentially problematic release.


This version of wayfinder-paths might be problematic. Click here for more details.

Files changed (47) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +19 -20
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +66 -37
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +2 -8
  4. wayfinder_paths/adapters/brap_adapter/README.md +22 -19
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +33 -34
  6. wayfinder_paths/adapters/brap_adapter/test_adapter.py +2 -18
  7. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +40 -56
  8. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +1 -8
  9. wayfinder_paths/adapters/moonwell_adapter/README.md +29 -31
  10. wayfinder_paths/adapters/moonwell_adapter/adapter.py +301 -662
  11. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +275 -179
  12. wayfinder_paths/core/config.py +8 -47
  13. wayfinder_paths/core/constants/base.py +0 -1
  14. wayfinder_paths/core/constants/erc20_abi.py +13 -13
  15. wayfinder_paths/core/strategies/Strategy.py +6 -2
  16. wayfinder_paths/core/utils/erc20_service.py +100 -0
  17. wayfinder_paths/core/utils/evm_helpers.py +1 -1
  18. wayfinder_paths/core/utils/transaction.py +191 -0
  19. wayfinder_paths/core/utils/web3.py +66 -0
  20. wayfinder_paths/run_strategy.py +37 -6
  21. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +200 -224
  22. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +128 -151
  23. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +0 -1
  24. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +52 -78
  25. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -12
  26. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +0 -1
  27. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +39 -64
  28. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +0 -1
  29. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +42 -85
  30. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -8
  31. wayfinder_paths/templates/strategy/README.md +1 -5
  32. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.16.dist-info}/METADATA +3 -41
  33. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.16.dist-info}/RECORD +35 -44
  34. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.16.dist-info}/WHEEL +1 -1
  35. wayfinder_paths/core/clients/sdk_example.py +0 -125
  36. wayfinder_paths/core/engine/__init__.py +0 -5
  37. wayfinder_paths/core/services/__init__.py +0 -0
  38. wayfinder_paths/core/services/base.py +0 -131
  39. wayfinder_paths/core/services/local_evm_txn.py +0 -350
  40. wayfinder_paths/core/services/local_token_txn.py +0 -238
  41. wayfinder_paths/core/services/web3_service.py +0 -43
  42. wayfinder_paths/core/wallets/README.md +0 -88
  43. wayfinder_paths/core/wallets/WalletManager.py +0 -56
  44. wayfinder_paths/core/wallets/__init__.py +0 -7
  45. wayfinder_paths/scripts/run_strategy.py +0 -152
  46. wayfinder_paths/strategies/config.py +0 -85
  47. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.16.dist-info}/LICENSE +0 -0
@@ -1,4 +1,5 @@
1
- from unittest.mock import AsyncMock, MagicMock
1
+ from contextlib import asynccontextmanager
2
+ from unittest.mock import AsyncMock, MagicMock, patch
2
3
 
3
4
  import pytest
4
5
 
@@ -14,27 +15,12 @@ class TestMoonwellAdapter:
14
15
  """Test cases for MoonwellAdapter"""
15
16
 
16
17
  @pytest.fixture
17
- def mock_web3_service(self):
18
- """Mock Web3Service for testing"""
19
- mock_service = MagicMock()
20
- mock_service.token_transactions = MagicMock()
21
- mock_service.evm_transactions = MagicMock()
22
-
23
- # Mock get_web3 to return a mock web3 instance
24
- mock_web3 = MagicMock()
25
- mock_service.get_web3 = MagicMock(return_value=mock_web3)
26
-
27
- return mock_service
28
-
29
- @pytest.fixture
30
- def adapter(self, mock_web3_service):
18
+ def adapter(self):
31
19
  """Create a MoonwellAdapter instance with mocked services"""
32
20
  config = {
33
21
  "strategy_wallet": {"address": "0x1234567890123456789012345678901234567890"}
34
22
  }
35
- return MoonwellAdapter(
36
- config=config, web3_service=mock_web3_service, simulation=True
37
- )
23
+ return MoonwellAdapter(config=config, simulation=True)
38
24
 
39
25
  def test_adapter_type(self, adapter):
40
26
  """Test adapter has correct adapter_type"""
@@ -79,32 +65,24 @@ class TestMoonwellAdapter:
79
65
  assert ok is True
80
66
 
81
67
  @pytest.mark.asyncio
82
- async def test_lend_simulation(self, adapter, mock_web3_service):
68
+ async def test_lend_simulation(self, adapter):
83
69
  """Test lend operation in simulation mode"""
84
- # Mock the allowance check
85
- mock_web3_service.token_transactions.read_erc20_allowance = AsyncMock(
86
- return_value={"allowance": 10**18}
87
- )
88
-
89
- # Mock contract encoding
90
- mock_contract = MagicMock()
91
- mock_contract.functions.mint = MagicMock(
92
- return_value=MagicMock(
93
- build_transaction=AsyncMock(return_value={"data": "0x1234"})
70
+ with patch.object(
71
+ adapter, "_encode_call", new_callable=AsyncMock
72
+ ) as mock_encode:
73
+ mock_encode.return_value = {
74
+ "data": "0x1234",
75
+ "to": MOONWELL_DEFAULTS["m_usdc"],
76
+ }
77
+
78
+ success, result = await adapter.lend(
79
+ mtoken=MOONWELL_DEFAULTS["m_usdc"],
80
+ underlying_token=MOONWELL_DEFAULTS["usdc"],
81
+ amount=10**6,
94
82
  )
95
- )
96
- mock_web3 = MagicMock()
97
- mock_web3.eth.contract = MagicMock(return_value=mock_contract)
98
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
99
-
100
- success, result = await adapter.lend(
101
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
102
- underlying_token=MOONWELL_DEFAULTS["usdc"],
103
- amount=10**6,
104
- )
105
83
 
106
- assert success
107
- assert "simulation" in result
84
+ assert success
85
+ assert "simulation" in result
108
86
 
109
87
  @pytest.mark.asyncio
110
88
  async def test_lend_invalid_amount(self, adapter):
@@ -119,7 +97,7 @@ class TestMoonwellAdapter:
119
97
  assert "positive" in result.lower()
120
98
 
121
99
  @pytest.mark.asyncio
122
- async def test_unlend_simulation(self, adapter, mock_web3_service):
100
+ async def test_unlend_simulation(self, adapter):
123
101
  """Test unlend operation in simulation mode"""
124
102
  # Mock contract encoding
125
103
  mock_contract = MagicMock()
@@ -130,12 +108,19 @@ class TestMoonwellAdapter:
130
108
  )
131
109
  mock_web3 = MagicMock()
132
110
  mock_web3.eth.contract = MagicMock(return_value=mock_contract)
133
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
134
111
 
135
- success, result = await adapter.unlend(
136
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
137
- amount=10**8,
138
- )
112
+ @asynccontextmanager
113
+ async def mock_web3_ctx(_chain_id):
114
+ yield mock_web3
115
+
116
+ with patch(
117
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
118
+ mock_web3_ctx,
119
+ ):
120
+ success, result = await adapter.unlend(
121
+ mtoken=MOONWELL_DEFAULTS["m_usdc"],
122
+ amount=10**8,
123
+ )
139
124
 
140
125
  assert success
141
126
  assert "simulation" in result
@@ -152,78 +137,104 @@ class TestMoonwellAdapter:
152
137
  assert "positive" in result.lower()
153
138
 
154
139
  @pytest.mark.asyncio
155
- async def test_borrow_simulation(self, adapter, mock_web3_service):
140
+ async def test_borrow_simulation(self, adapter):
156
141
  """Test borrow operation in simulation mode"""
157
- # Mock contract encoding
158
- mock_contract = MagicMock()
159
- mock_contract.functions.borrow = MagicMock(
142
+ # Track calls to return different values (0 before, 10**6 after)
143
+ borrow_balance_calls = [0]
144
+
145
+ async def mock_borrow_balance_call():
146
+ result = borrow_balance_calls[0]
147
+ # Next call returns increased balance
148
+ borrow_balance_calls[0] = 10**6
149
+ return result
150
+
151
+ # Mock mtoken contract for pre-check and verification
152
+ mock_mtoken = MagicMock()
153
+ mock_mtoken.functions.borrowBalanceStored = MagicMock(
154
+ return_value=MagicMock(call=mock_borrow_balance_call)
155
+ )
156
+ mock_mtoken.functions.borrow = MagicMock(
160
157
  return_value=MagicMock(
161
- build_transaction=AsyncMock(return_value={"data": "0x1234"})
158
+ call=AsyncMock(return_value=0), # 0 = success
159
+ _encode_transaction_data=MagicMock(return_value="0x1234"),
160
+ build_transaction=AsyncMock(return_value={"data": "0x1234"}),
162
161
  )
163
162
  )
163
+
164
164
  mock_web3 = MagicMock()
165
- mock_web3.eth.contract = MagicMock(return_value=mock_contract)
166
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
165
+ mock_web3.eth.contract = MagicMock(return_value=mock_mtoken)
167
166
 
168
- success, result = await adapter.borrow(
169
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
170
- amount=10**6,
171
- )
167
+ @asynccontextmanager
168
+ async def mock_web3_ctx(_chain_id):
169
+ yield mock_web3
170
+
171
+ with patch(
172
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
173
+ mock_web3_ctx,
174
+ ):
175
+ success, result = await adapter.borrow(
176
+ mtoken=MOONWELL_DEFAULTS["m_usdc"],
177
+ amount=10**6,
178
+ )
172
179
 
173
180
  assert success
174
181
  assert "simulation" in result
175
182
 
176
183
  @pytest.mark.asyncio
177
- async def test_repay_simulation(self, adapter, mock_web3_service):
184
+ async def test_repay_simulation(self, adapter):
178
185
  """Test repay operation in simulation mode"""
179
- # Mock the allowance check
180
- mock_web3_service.token_transactions.read_erc20_allowance = AsyncMock(
181
- return_value={"allowance": 10**18}
182
- )
183
-
184
- # Mock contract encoding
185
- mock_contract = MagicMock()
186
- mock_contract.functions.repayBorrow = MagicMock(
187
- return_value=MagicMock(
188
- build_transaction=AsyncMock(return_value={"data": "0x1234"})
186
+ with patch.object(
187
+ adapter, "_encode_call", new_callable=AsyncMock
188
+ ) as mock_encode:
189
+ mock_encode.return_value = {
190
+ "data": "0x1234",
191
+ "to": MOONWELL_DEFAULTS["m_usdc"],
192
+ }
193
+
194
+ success, result = await adapter.repay(
195
+ mtoken=MOONWELL_DEFAULTS["m_usdc"],
196
+ underlying_token=MOONWELL_DEFAULTS["usdc"],
197
+ amount=10**6,
189
198
  )
190
- )
191
- mock_web3 = MagicMock()
192
- mock_web3.eth.contract = MagicMock(return_value=mock_contract)
193
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
194
199
 
195
- success, result = await adapter.repay(
196
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
197
- underlying_token=MOONWELL_DEFAULTS["usdc"],
198
- amount=10**6,
199
- )
200
-
201
- assert success
202
- assert "simulation" in result
200
+ assert success
201
+ assert "simulation" in result
203
202
 
204
203
  @pytest.mark.asyncio
205
- async def test_set_collateral_simulation(self, adapter, mock_web3_service):
204
+ async def test_set_collateral_simulation(self, adapter):
206
205
  """Test set collateral operation in simulation mode"""
207
- # Mock contract encoding
208
- mock_contract = MagicMock()
209
- mock_contract.functions.enterMarkets = MagicMock(
206
+ # Mock comptroller contract for verification
207
+ mock_comptroller = MagicMock()
208
+ mock_comptroller.functions.checkMembership = MagicMock(
209
+ return_value=MagicMock(call=AsyncMock(return_value=True))
210
+ )
211
+ mock_comptroller.functions.enterMarkets = MagicMock(
210
212
  return_value=MagicMock(
211
- build_transaction=AsyncMock(return_value={"data": "0x1234"})
213
+ _encode_transaction_data=MagicMock(return_value="0x1234"),
214
+ build_transaction=AsyncMock(return_value={"data": "0x1234"}),
212
215
  )
213
216
  )
214
- mock_web3 = MagicMock()
215
- mock_web3.eth.contract = MagicMock(return_value=mock_contract)
216
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
217
217
 
218
- success, result = await adapter.set_collateral(
219
- mtoken=MOONWELL_DEFAULTS["m_wsteth"],
220
- )
218
+ mock_web3 = MagicMock()
219
+ mock_web3.eth.contract = MagicMock(return_value=mock_comptroller)
220
+
221
+ @asynccontextmanager
222
+ async def mock_web3_ctx(_chain_id):
223
+ yield mock_web3
224
+
225
+ with patch(
226
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
227
+ mock_web3_ctx,
228
+ ):
229
+ success, result = await adapter.set_collateral(
230
+ mtoken=MOONWELL_DEFAULTS["m_wsteth"],
231
+ )
221
232
 
222
- assert success
223
- assert "simulation" in result
233
+ assert success is True
234
+ assert "simulation" in result
224
235
 
225
236
  @pytest.mark.asyncio
226
- async def test_claim_rewards_simulation(self, adapter, mock_web3_service):
237
+ async def test_claim_rewards_simulation(self, adapter):
227
238
  """Test claim rewards operation in simulation mode"""
228
239
  # Mock contract for getting outstanding rewards
229
240
  mock_reward_contract = MagicMock()
@@ -246,7 +257,6 @@ class TestMoonwellAdapter:
246
257
 
247
258
  mock_web3 = MagicMock()
248
259
  mock_web3.eth.contract = MagicMock(side_effect=mock_contract)
249
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
250
260
 
251
261
  success, result = await adapter.claim_rewards()
252
262
 
@@ -254,7 +264,7 @@ class TestMoonwellAdapter:
254
264
  assert isinstance(result, dict)
255
265
 
256
266
  @pytest.mark.asyncio
257
- async def test_get_pos_success(self, adapter, mock_web3_service):
267
+ async def test_get_pos_success(self, adapter):
258
268
  """Test get position data"""
259
269
  underlying_addr = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
260
270
 
@@ -286,9 +296,16 @@ class TestMoonwellAdapter:
286
296
 
287
297
  mock_web3 = MagicMock()
288
298
  mock_web3.eth.contract = MagicMock(side_effect=mock_contract)
289
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
290
299
 
291
- success, result = await adapter.get_pos(mtoken=MOONWELL_DEFAULTS["m_usdc"])
300
+ @asynccontextmanager
301
+ async def mock_web3_ctx(_chain_id):
302
+ yield mock_web3
303
+
304
+ with patch(
305
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
306
+ mock_web3_ctx,
307
+ ):
308
+ success, result = await adapter.get_pos(mtoken=MOONWELL_DEFAULTS["m_usdc"])
292
309
 
293
310
  assert success
294
311
  assert "mtoken_balance" in result
@@ -299,8 +316,11 @@ class TestMoonwellAdapter:
299
316
  assert result["borrow_balance"] == 10**6
300
317
 
301
318
  @pytest.mark.asyncio
302
- async def test_get_collateral_factor_success(self, adapter, mock_web3_service):
319
+ async def test_get_collateral_factor_success(self, adapter):
303
320
  """Test get collateral factor"""
321
+ # Clear cache to ensure fresh test
322
+ adapter._cf_cache.clear()
323
+
304
324
  # Mock contract calls - returns (isListed, collateralFactorMantissa)
305
325
  mock_contract = MagicMock()
306
326
  mock_contract.functions.markets = MagicMock(
@@ -310,17 +330,24 @@ class TestMoonwellAdapter:
310
330
  )
311
331
  mock_web3 = MagicMock()
312
332
  mock_web3.eth.contract = MagicMock(return_value=mock_contract)
313
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
314
333
 
315
- success, result = await adapter.get_collateral_factor(
316
- mtoken=MOONWELL_DEFAULTS["m_wsteth"]
317
- )
334
+ @asynccontextmanager
335
+ async def mock_web3_ctx(_chain_id):
336
+ yield mock_web3
337
+
338
+ with patch(
339
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
340
+ mock_web3_ctx,
341
+ ):
342
+ success, result = await adapter.get_collateral_factor(
343
+ mtoken=MOONWELL_DEFAULTS["m_wsteth"]
344
+ )
318
345
 
319
346
  assert success
320
347
  assert result == 0.75
321
348
 
322
349
  @pytest.mark.asyncio
323
- async def test_get_collateral_factor_not_listed(self, adapter, mock_web3_service):
350
+ async def test_get_collateral_factor_not_listed(self, adapter):
324
351
  """Test get collateral factor for unlisted market"""
325
352
  mock_contract = MagicMock()
326
353
  mock_contract.functions.markets = MagicMock(
@@ -328,21 +355,31 @@ class TestMoonwellAdapter:
328
355
  )
329
356
  mock_web3 = MagicMock()
330
357
  mock_web3.eth.contract = MagicMock(return_value=mock_contract)
331
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
332
358
 
333
- success, result = await adapter.get_collateral_factor(
334
- mtoken="0x0000000000000000000000000000000000000001"
335
- )
359
+ @asynccontextmanager
360
+ async def mock_web3_ctx(_chain_id):
361
+ yield mock_web3
362
+
363
+ with patch(
364
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
365
+ mock_web3_ctx,
366
+ ):
367
+ success, result = await adapter.get_collateral_factor(
368
+ mtoken="0x0000000000000000000000000000000000000001"
369
+ )
336
370
 
337
371
  assert success is False
338
372
  assert "not listed" in result.lower()
339
373
 
340
374
  @pytest.mark.asyncio
341
- async def test_get_collateral_factor_caching(self, adapter, mock_web3_service):
375
+ async def test_get_collateral_factor_caching(self, adapter):
342
376
  """Test that collateral factor is cached and subsequent calls don't hit RPC"""
377
+ # Clear cache to ensure fresh test
378
+ adapter._cf_cache.clear()
379
+
343
380
  call_count = 0
344
381
 
345
- async def mock_markets_call():
382
+ async def mock_markets_call(**kwargs):
346
383
  nonlocal call_count
347
384
  call_count += 1
348
385
  return (True, int(0.80 * MANTISSA))
@@ -353,42 +390,52 @@ class TestMoonwellAdapter:
353
390
  )
354
391
  mock_web3 = MagicMock()
355
392
  mock_web3.eth.contract = MagicMock(return_value=mock_contract)
356
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
357
393
 
358
- mtoken = MOONWELL_DEFAULTS["m_wsteth"]
359
-
360
- # First call should hit RPC
361
- success1, result1 = await adapter.get_collateral_factor(mtoken=mtoken)
362
- assert success1 is True
363
- assert result1 == 0.80
364
- assert call_count == 1
394
+ @asynccontextmanager
395
+ async def mock_web3_ctx(_chain_id):
396
+ yield mock_web3
365
397
 
366
- # Second call should use cache (no additional RPC call)
367
- success2, result2 = await adapter.get_collateral_factor(mtoken=mtoken)
368
- assert success2 is True
369
- assert result2 == 0.80
370
- assert call_count == 1 # Still 1, cache was used
398
+ mtoken = MOONWELL_DEFAULTS["m_wsteth"]
371
399
 
372
- # Third call for same mtoken should still use cache
373
- success3, result3 = await adapter.get_collateral_factor(mtoken=mtoken)
374
- assert success3 is True
375
- assert result3 == 0.80
376
- assert call_count == 1 # Still 1
400
+ with patch(
401
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
402
+ mock_web3_ctx,
403
+ ):
404
+ # First call should hit RPC
405
+ success1, result1 = await adapter.get_collateral_factor(mtoken=mtoken)
406
+ assert success1 is True
407
+ assert result1 == 0.80
408
+ assert call_count == 1
377
409
 
378
- # Call for different mtoken should hit RPC
379
- success4, result4 = await adapter.get_collateral_factor(
380
- mtoken=MOONWELL_DEFAULTS["m_usdc"]
381
- )
382
- assert success4 is True
383
- assert call_count == 2 # Incremented for new mtoken
410
+ # Second call should use cache (no additional RPC call)
411
+ success2, result2 = await adapter.get_collateral_factor(mtoken=mtoken)
412
+ assert success2 is True
413
+ assert result2 == 0.80
414
+ assert call_count == 1 # Still 1, cache was used
415
+
416
+ # Third call for same mtoken should still use cache
417
+ success3, result3 = await adapter.get_collateral_factor(mtoken=mtoken)
418
+ assert success3 is True
419
+ assert result3 == 0.80
420
+ assert call_count == 1 # Still 1
421
+
422
+ # Call for different mtoken should hit RPC
423
+ success4, result4 = await adapter.get_collateral_factor(
424
+ mtoken=MOONWELL_DEFAULTS["m_usdc"]
425
+ )
426
+ assert success4 is True
427
+ assert call_count == 2 # Incremented for new mtoken
384
428
 
385
429
  @pytest.mark.asyncio
386
- async def test_get_collateral_factor_cache_expiry(self, adapter, mock_web3_service):
430
+ async def test_get_collateral_factor_cache_expiry(self, adapter):
387
431
  """Test that collateral factor cache expires after TTL"""
388
432
  import time
389
433
 
390
434
  from wayfinder_paths.adapters.moonwell_adapter import adapter as adapter_module
391
435
 
436
+ # Clear cache to ensure fresh test
437
+ adapter._cf_cache.clear()
438
+
392
439
  # Save original TTL
393
440
  original_ttl = adapter_module.CF_CACHE_TTL
394
441
 
@@ -398,7 +445,7 @@ class TestMoonwellAdapter:
398
445
 
399
446
  call_count = 0
400
447
 
401
- async def mock_markets_call():
448
+ async def mock_markets_call(**kwargs):
402
449
  nonlocal call_count
403
450
  call_count += 1
404
451
  return (True, int(0.75 * MANTISSA))
@@ -409,31 +456,38 @@ class TestMoonwellAdapter:
409
456
  )
410
457
  mock_web3 = MagicMock()
411
458
  mock_web3.eth.contract = MagicMock(return_value=mock_contract)
412
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
459
+
460
+ @asynccontextmanager
461
+ async def mock_web3_ctx(_chain_id):
462
+ yield mock_web3
413
463
 
414
464
  mtoken = MOONWELL_DEFAULTS["m_wsteth"]
415
465
 
416
- # First call
417
- await adapter.get_collateral_factor(mtoken=mtoken)
418
- assert call_count == 1
466
+ with patch(
467
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
468
+ mock_web3_ctx,
469
+ ):
470
+ # First call
471
+ await adapter.get_collateral_factor(mtoken=mtoken)
472
+ assert call_count == 1
419
473
 
420
- # Immediate second call should use cache
421
- await adapter.get_collateral_factor(mtoken=mtoken)
422
- assert call_count == 1
474
+ # Immediate second call should use cache
475
+ await adapter.get_collateral_factor(mtoken=mtoken)
476
+ assert call_count == 1
423
477
 
424
- # Wait for cache to expire
425
- time.sleep(0.15)
478
+ # Wait for cache to expire
479
+ time.sleep(0.15)
426
480
 
427
- # Call after expiry should hit RPC again
428
- await adapter.get_collateral_factor(mtoken=mtoken)
429
- assert call_count == 2
481
+ # Call after expiry should hit RPC again
482
+ await adapter.get_collateral_factor(mtoken=mtoken)
483
+ assert call_count == 2
430
484
 
431
485
  finally:
432
486
  # Restore original TTL
433
487
  adapter_module.CF_CACHE_TTL = original_ttl
434
488
 
435
489
  @pytest.mark.asyncio
436
- async def test_get_apy_supply(self, adapter, mock_web3_service):
490
+ async def test_get_apy_supply(self, adapter):
437
491
  """Test get supply APY"""
438
492
  rate_per_second = int(1.5e9)
439
493
 
@@ -459,18 +513,27 @@ class TestMoonwellAdapter:
459
513
 
460
514
  mock_web3 = MagicMock()
461
515
  mock_web3.eth.contract = MagicMock(side_effect=mock_contract)
462
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
463
516
 
464
- success, result = await adapter.get_apy(
465
- mtoken=MOONWELL_DEFAULTS["m_usdc"], apy_type="supply", include_rewards=False
466
- )
517
+ @asynccontextmanager
518
+ async def mock_web3_ctx(_chain_id):
519
+ yield mock_web3
520
+
521
+ with patch(
522
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
523
+ mock_web3_ctx,
524
+ ):
525
+ success, result = await adapter.get_apy(
526
+ mtoken=MOONWELL_DEFAULTS["m_usdc"],
527
+ apy_type="supply",
528
+ include_rewards=False,
529
+ )
467
530
 
468
531
  assert success
469
532
  assert isinstance(result, float)
470
533
  assert result >= 0
471
534
 
472
535
  @pytest.mark.asyncio
473
- async def test_get_apy_borrow(self, adapter, mock_web3_service):
536
+ async def test_get_apy_borrow(self, adapter):
474
537
  """Test get borrow APY"""
475
538
  rate_per_second = int(2e9)
476
539
 
@@ -496,18 +559,27 @@ class TestMoonwellAdapter:
496
559
 
497
560
  mock_web3 = MagicMock()
498
561
  mock_web3.eth.contract = MagicMock(side_effect=mock_contract)
499
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
500
562
 
501
- success, result = await adapter.get_apy(
502
- mtoken=MOONWELL_DEFAULTS["m_usdc"], apy_type="borrow", include_rewards=False
503
- )
563
+ @asynccontextmanager
564
+ async def mock_web3_ctx(_chain_id):
565
+ yield mock_web3
566
+
567
+ with patch(
568
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
569
+ mock_web3_ctx,
570
+ ):
571
+ success, result = await adapter.get_apy(
572
+ mtoken=MOONWELL_DEFAULTS["m_usdc"],
573
+ apy_type="borrow",
574
+ include_rewards=False,
575
+ )
504
576
 
505
577
  assert success
506
578
  assert isinstance(result, float)
507
579
  assert result >= 0
508
580
 
509
581
  @pytest.mark.asyncio
510
- async def test_get_borrowable_amount_success(self, adapter, mock_web3_service):
582
+ async def test_get_borrowable_amount_success(self, adapter):
511
583
  """Test get borrowable amount"""
512
584
  mock_contract = MagicMock()
513
585
  mock_contract.functions.getAccountLiquidity = MagicMock(
@@ -519,15 +591,22 @@ class TestMoonwellAdapter:
519
591
  )
520
592
  mock_web3 = MagicMock()
521
593
  mock_web3.eth.contract = MagicMock(return_value=mock_contract)
522
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
523
594
 
524
- success, result = await adapter.get_borrowable_amount()
595
+ @asynccontextmanager
596
+ async def mock_web3_ctx(_chain_id):
597
+ yield mock_web3
598
+
599
+ with patch(
600
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
601
+ mock_web3_ctx,
602
+ ):
603
+ success, result = await adapter.get_borrowable_amount()
525
604
 
526
605
  assert success
527
606
  assert result == 10**18
528
607
 
529
608
  @pytest.mark.asyncio
530
- async def test_get_borrowable_amount_shortfall(self, adapter, mock_web3_service):
609
+ async def test_get_borrowable_amount_shortfall(self, adapter):
531
610
  """Test get borrowable amount when account has shortfall"""
532
611
  mock_contract = MagicMock()
533
612
  mock_contract.functions.getAccountLiquidity = MagicMock(
@@ -537,15 +616,22 @@ class TestMoonwellAdapter:
537
616
  )
538
617
  mock_web3 = MagicMock()
539
618
  mock_web3.eth.contract = MagicMock(return_value=mock_contract)
540
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
541
619
 
542
- success, result = await adapter.get_borrowable_amount()
620
+ @asynccontextmanager
621
+ async def mock_web3_ctx(_chain_id):
622
+ yield mock_web3
623
+
624
+ with patch(
625
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
626
+ mock_web3_ctx,
627
+ ):
628
+ success, result = await adapter.get_borrowable_amount()
543
629
 
544
630
  assert success is False
545
631
  assert "shortfall" in result.lower()
546
632
 
547
633
  @pytest.mark.asyncio
548
- async def test_wrap_eth_simulation(self, adapter, mock_web3_service):
634
+ async def test_wrap_eth_simulation(self, adapter):
549
635
  """Test wrap ETH operation in simulation mode"""
550
636
  mock_contract = MagicMock()
551
637
  mock_contract.functions.deposit = MagicMock(
@@ -555,9 +641,16 @@ class TestMoonwellAdapter:
555
641
  )
556
642
  mock_web3 = MagicMock()
557
643
  mock_web3.eth.contract = MagicMock(return_value=mock_contract)
558
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
559
644
 
560
- success, result = await adapter.wrap_eth(amount=10**18)
645
+ @asynccontextmanager
646
+ async def mock_web3_ctx(_chain_id):
647
+ yield mock_web3
648
+
649
+ with patch(
650
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
651
+ mock_web3_ctx,
652
+ ):
653
+ success, result = await adapter.wrap_eth(amount=10**18)
561
654
 
562
655
  assert success
563
656
  assert "simulation" in result
@@ -574,7 +667,7 @@ class TestMoonwellAdapter:
574
667
  with pytest.raises(ValueError, match="Missing required"):
575
668
  adapter._checksum(None)
576
669
 
577
- def test_config_override(self, mock_web3_service):
670
+ def test_config_override(self):
578
671
  """Test config can override default addresses"""
579
672
  custom_comptroller = "0x1111111111111111111111111111111111111111"
580
673
  custom_well = "0x2222222222222222222222222222222222222222"
@@ -589,18 +682,14 @@ class TestMoonwellAdapter:
589
682
  },
590
683
  }
591
684
 
592
- adapter = MoonwellAdapter(
593
- config=config, web3_service=mock_web3_service, simulation=True
594
- )
685
+ adapter = MoonwellAdapter(config=config, simulation=True)
595
686
 
596
687
  assert adapter.comptroller_address.lower() == custom_comptroller.lower()
597
688
  assert adapter.well_token.lower() == custom_well.lower()
598
689
  assert adapter.chain_id == 1
599
690
 
600
691
  @pytest.mark.asyncio
601
- async def test_max_withdrawable_mtoken_zero_balance(
602
- self, adapter, mock_web3_service
603
- ):
692
+ async def test_max_withdrawable_mtoken_zero_balance(self, adapter):
604
693
  """Test max withdrawable when balance is zero"""
605
694
  # Mock contracts
606
695
  mock_mtoken = MagicMock()
@@ -624,11 +713,18 @@ class TestMoonwellAdapter:
624
713
 
625
714
  mock_web3 = MagicMock()
626
715
  mock_web3.eth.contract = MagicMock(return_value=mock_mtoken)
627
- mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
628
716
 
629
- success, result = await adapter.max_withdrawable_mtoken(
630
- mtoken=MOONWELL_DEFAULTS["m_usdc"]
631
- )
717
+ @asynccontextmanager
718
+ async def mock_web3_ctx(_chain_id):
719
+ yield mock_web3
720
+
721
+ with patch(
722
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
723
+ mock_web3_ctx,
724
+ ):
725
+ success, result = await adapter.max_withdrawable_mtoken(
726
+ mtoken=MOONWELL_DEFAULTS["m_usdc"]
727
+ )
632
728
 
633
729
  assert success
634
730
  assert result["cTokens_raw"] == 0