wayfinder-paths 0.1.14__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.
- wayfinder_paths/adapters/balance_adapter/README.md +19 -20
- wayfinder_paths/adapters/balance_adapter/adapter.py +91 -22
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +5 -11
- wayfinder_paths/adapters/brap_adapter/README.md +22 -19
- wayfinder_paths/adapters/brap_adapter/adapter.py +95 -45
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +8 -24
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +40 -42
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +8 -15
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +12 -12
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/moonwell_adapter/README.md +29 -31
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +326 -364
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +285 -189
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +2 -2
- wayfinder_paths/adapters/token_adapter/test_adapter.py +4 -4
- wayfinder_paths/core/config.py +8 -47
- wayfinder_paths/core/constants/base.py +0 -1
- wayfinder_paths/core/constants/erc20_abi.py +13 -24
- wayfinder_paths/core/engine/StrategyJob.py +3 -1
- wayfinder_paths/core/services/test_local_evm_txn.py +145 -0
- wayfinder_paths/core/strategies/Strategy.py +22 -4
- wayfinder_paths/core/utils/erc20_service.py +100 -0
- wayfinder_paths/core/utils/evm_helpers.py +1 -8
- wayfinder_paths/core/utils/transaction.py +191 -0
- wayfinder_paths/core/utils/web3.py +66 -0
- wayfinder_paths/policies/erc20.py +1 -1
- wayfinder_paths/run_strategy.py +42 -6
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +263 -220
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +132 -155
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +0 -1
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +123 -80
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -12
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +6 -6
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2270 -1328
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +282 -121
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +0 -1
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +107 -85
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -8
- wayfinder_paths/templates/adapter/README.md +1 -1
- wayfinder_paths/templates/strategy/README.md +1 -5
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/METADATA +3 -41
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/RECORD +45 -54
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/WHEEL +1 -1
- wayfinder_paths/abis/generic/erc20.json +0 -383
- wayfinder_paths/core/clients/sdk_example.py +0 -125
- wayfinder_paths/core/engine/__init__.py +0 -5
- wayfinder_paths/core/services/__init__.py +0 -0
- wayfinder_paths/core/services/base.py +0 -130
- wayfinder_paths/core/services/local_evm_txn.py +0 -334
- wayfinder_paths/core/services/local_token_txn.py +0 -242
- wayfinder_paths/core/services/web3_service.py +0 -43
- wayfinder_paths/core/wallets/README.md +0 -88
- wayfinder_paths/core/wallets/WalletManager.py +0 -56
- wayfinder_paths/core/wallets/__init__.py +0 -7
- wayfinder_paths/scripts/run_strategy.py +0 -152
- wayfinder_paths/strategies/config.py +0 -85
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/LICENSE +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from
|
|
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
|
|
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
|
|
68
|
+
async def test_lend_simulation(self, adapter):
|
|
83
69
|
"""Test lend operation in simulation mode"""
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
|
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,14 +108,21 @@ 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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
assert success
|
|
125
|
+
assert success
|
|
141
126
|
assert "simulation" in result
|
|
142
127
|
|
|
143
128
|
@pytest.mark.asyncio
|
|
@@ -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
|
|
140
|
+
async def test_borrow_simulation(self, adapter):
|
|
156
141
|
"""Test borrow operation in simulation mode"""
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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=
|
|
166
|
-
mock_web3_service.get_web3 = MagicMock(return_value=mock_web3)
|
|
165
|
+
mock_web3.eth.contract = MagicMock(return_value=mock_mtoken)
|
|
167
166
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
assert success
|
|
180
|
+
assert success
|
|
174
181
|
assert "simulation" in result
|
|
175
182
|
|
|
176
183
|
@pytest.mark.asyncio
|
|
177
|
-
async def test_repay_simulation(self, adapter
|
|
184
|
+
async def test_repay_simulation(self, adapter):
|
|
178
185
|
"""Test repay operation in simulation mode"""
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
195
|
-
success, result = await adapter.repay(
|
|
196
|
-
mtoken=MOONWELL_DEFAULTS["m_usdc"],
|
|
197
|
-
underlying_token=MOONWELL_DEFAULTS["usdc"],
|
|
198
|
-
amount=10**6,
|
|
199
|
-
)
|
|
200
199
|
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
assert success
|
|
201
|
+
assert "simulation" in result
|
|
203
202
|
|
|
204
203
|
@pytest.mark.asyncio
|
|
205
|
-
async def test_set_collateral_simulation(self, adapter
|
|
204
|
+
async def test_set_collateral_simulation(self, adapter):
|
|
206
205
|
"""Test set collateral operation in simulation mode"""
|
|
207
|
-
# Mock contract
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
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
|
-
|
|
223
|
-
|
|
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
|
|
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,15 +257,14 @@ 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
|
|
|
253
|
-
assert success
|
|
263
|
+
assert success
|
|
254
264
|
assert isinstance(result, dict)
|
|
255
265
|
|
|
256
266
|
@pytest.mark.asyncio
|
|
257
|
-
async def test_get_pos_success(self, adapter
|
|
267
|
+
async def test_get_pos_success(self, adapter):
|
|
258
268
|
"""Test get position data"""
|
|
259
269
|
underlying_addr = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
260
270
|
|
|
@@ -286,11 +296,18 @@ 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
|
-
|
|
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
|
-
assert success
|
|
310
|
+
assert success
|
|
294
311
|
assert "mtoken_balance" in result
|
|
295
312
|
assert "underlying_balance" in result
|
|
296
313
|
assert "borrow_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
|
|
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
|
-
|
|
316
|
-
|
|
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
|
-
assert success
|
|
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
|
|
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
|
-
|
|
334
|
-
|
|
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
|
|
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
|
-
|
|
358
|
-
mtoken = MOONWELL_DEFAULTS["m_wsteth"]
|
|
359
393
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
474
|
+
# Immediate second call should use cache
|
|
475
|
+
await adapter.get_collateral_factor(mtoken=mtoken)
|
|
476
|
+
assert call_count == 1
|
|
423
477
|
|
|
424
|
-
|
|
425
|
-
|
|
478
|
+
# Wait for cache to expire
|
|
479
|
+
time.sleep(0.15)
|
|
426
480
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
|
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
|
-
|
|
465
|
-
|
|
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
|
-
assert success
|
|
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
|
|
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
|
-
|
|
502
|
-
|
|
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
|
-
assert success
|
|
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
|
|
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
|
-
|
|
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
|
-
assert success
|
|
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
|
|
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
|
-
|
|
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
|
|
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,11 +641,18 @@ 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
|
-
|
|
645
|
+
@asynccontextmanager
|
|
646
|
+
async def mock_web3_ctx(_chain_id):
|
|
647
|
+
yield mock_web3
|
|
561
648
|
|
|
562
|
-
|
|
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)
|
|
654
|
+
|
|
655
|
+
assert success
|
|
563
656
|
assert "simulation" in result
|
|
564
657
|
|
|
565
658
|
def test_strategy_address_missing(self):
|
|
@@ -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
|
|
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,12 +713,19 @@ 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
|
-
|
|
630
|
-
|
|
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
|
-
assert success
|
|
729
|
+
assert success
|
|
634
730
|
assert result["cTokens_raw"] == 0
|
|
635
731
|
assert result["underlying_raw"] == 0
|