wayfinder-paths 0.1.19__py3-none-any.whl → 0.1.20__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/__init__.py +0 -2
- wayfinder_paths/adapters/balance_adapter/README.md +59 -45
- wayfinder_paths/adapters/balance_adapter/adapter.py +0 -21
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -14
- wayfinder_paths/adapters/brap_adapter/README.md +61 -184
- wayfinder_paths/adapters/brap_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/brap_adapter/adapter.py +0 -147
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +0 -15
- wayfinder_paths/adapters/hyperlend_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -9
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +0 -17
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +3 -312
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +1 -71
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +0 -57
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +0 -17
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +2 -42
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +1 -9
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +15 -47
- wayfinder_paths/adapters/hyperliquid_adapter/utils.py +0 -7
- wayfinder_paths/adapters/ledger_adapter/README.md +54 -74
- wayfinder_paths/adapters/ledger_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/ledger_adapter/adapter.py +0 -106
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +0 -12
- wayfinder_paths/adapters/moonwell_adapter/README.md +67 -106
- wayfinder_paths/adapters/moonwell_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +9 -121
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +84 -83
- wayfinder_paths/adapters/pool_adapter/README.md +30 -51
- wayfinder_paths/adapters/pool_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/pool_adapter/adapter.py +0 -19
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -8
- wayfinder_paths/adapters/token_adapter/README.md +41 -49
- wayfinder_paths/adapters/token_adapter/adapter.py +0 -32
- wayfinder_paths/adapters/token_adapter/test_adapter.py +1 -12
- wayfinder_paths/conftest.py +0 -8
- wayfinder_paths/core/__init__.py +0 -2
- wayfinder_paths/core/adapters/BaseAdapter.py +0 -22
- wayfinder_paths/core/adapters/__init__.py +0 -5
- wayfinder_paths/core/adapters/models.py +0 -5
- wayfinder_paths/core/analytics/__init__.py +0 -2
- wayfinder_paths/core/analytics/bootstrap.py +0 -16
- wayfinder_paths/core/analytics/stats.py +0 -7
- wayfinder_paths/core/analytics/test_analytics.py +5 -34
- wayfinder_paths/core/clients/BRAPClient.py +0 -35
- wayfinder_paths/core/clients/ClientManager.py +0 -51
- wayfinder_paths/core/clients/HyperlendClient.py +0 -77
- wayfinder_paths/core/clients/LedgerClient.py +2 -122
- wayfinder_paths/core/clients/PoolClient.py +0 -2
- wayfinder_paths/core/clients/TokenClient.py +0 -39
- wayfinder_paths/core/clients/WalletClient.py +0 -15
- wayfinder_paths/core/clients/WayfinderClient.py +0 -24
- wayfinder_paths/core/clients/__init__.py +0 -4
- wayfinder_paths/core/clients/protocols.py +25 -98
- wayfinder_paths/core/config.py +0 -24
- wayfinder_paths/core/constants/__init__.py +0 -7
- wayfinder_paths/core/constants/base.py +2 -9
- wayfinder_paths/core/constants/erc20_abi.py +0 -5
- wayfinder_paths/core/constants/hyperlend_abi.py +0 -7
- wayfinder_paths/core/constants/moonwell_abi.py +0 -35
- wayfinder_paths/core/engine/StrategyJob.py +0 -32
- wayfinder_paths/core/strategies/Strategy.py +0 -99
- wayfinder_paths/core/strategies/__init__.py +0 -2
- wayfinder_paths/core/utils/__init__.py +0 -1
- wayfinder_paths/core/utils/erc20_service.py +0 -1
- wayfinder_paths/core/utils/evm_helpers.py +0 -50
- wayfinder_paths/core/utils/transaction.py +0 -1
- wayfinder_paths/run_strategy.py +0 -46
- wayfinder_paths/scripts/create_strategy.py +0 -17
- wayfinder_paths/scripts/make_wallets.py +1 -4
- wayfinder_paths/strategies/basis_trading_strategy/README.md +71 -163
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +0 -24
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +36 -400
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +15 -64
- wayfinder_paths/strategies/basis_trading_strategy/types.py +0 -4
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +65 -56
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +4 -27
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -10
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +71 -72
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +23 -227
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +120 -113
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +64 -59
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +4 -44
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +2 -35
- wayfinder_paths/templates/adapter/README.md +107 -46
- wayfinder_paths/templates/adapter/adapter.py +0 -9
- wayfinder_paths/templates/adapter/test_adapter.py +0 -19
- wayfinder_paths/templates/strategy/README.md +113 -59
- wayfinder_paths/templates/strategy/strategy.py +0 -22
- wayfinder_paths/templates/strategy/test_strategy.py +0 -28
- wayfinder_paths/tests/test_test_coverage.py +2 -12
- wayfinder_paths/tests/test_utils.py +1 -31
- wayfinder_paths-0.1.20.dist-info/METADATA +355 -0
- wayfinder_paths-0.1.20.dist-info/RECORD +129 -0
- wayfinder_paths/core/adapters/base.py +0 -5
- wayfinder_paths-0.1.19.dist-info/METADATA +0 -592
- wayfinder_paths-0.1.19.dist-info/RECORD +0 -130
- {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.20.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.20.dist-info}/WHEEL +0 -0
|
@@ -12,22 +12,17 @@ from wayfinder_paths.adapters.moonwell_adapter.adapter import (
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class TestMoonwellAdapter:
|
|
15
|
-
"""Test cases for MoonwellAdapter"""
|
|
16
|
-
|
|
17
15
|
@pytest.fixture
|
|
18
16
|
def adapter(self):
|
|
19
|
-
"""Create a MoonwellAdapter instance with mocked services"""
|
|
20
17
|
config = {
|
|
21
18
|
"strategy_wallet": {"address": "0x1234567890123456789012345678901234567890"}
|
|
22
19
|
}
|
|
23
|
-
return MoonwellAdapter(config=config
|
|
20
|
+
return MoonwellAdapter(config=config)
|
|
24
21
|
|
|
25
22
|
def test_adapter_type(self, adapter):
|
|
26
|
-
"""Test adapter has correct adapter_type"""
|
|
27
23
|
assert adapter.adapter_type == "MOONWELL"
|
|
28
24
|
|
|
29
25
|
def test_default_addresses(self, adapter):
|
|
30
|
-
"""Test default Moonwell addresses are set correctly"""
|
|
31
26
|
assert (
|
|
32
27
|
adapter.comptroller_address.lower()
|
|
33
28
|
== MOONWELL_DEFAULTS["comptroller"].lower()
|
|
@@ -42,16 +37,13 @@ class TestMoonwellAdapter:
|
|
|
42
37
|
assert adapter.well_token.lower() == MOONWELL_DEFAULTS["well_token"].lower()
|
|
43
38
|
|
|
44
39
|
def test_chain_id(self, adapter):
|
|
45
|
-
"""Test default chain ID is Base"""
|
|
46
40
|
assert adapter.chain_id == BASE_CHAIN_ID
|
|
47
41
|
|
|
48
42
|
def test_chain_name(self, adapter):
|
|
49
|
-
"""Test chain name is base"""
|
|
50
43
|
assert adapter.chain_name == "base"
|
|
51
44
|
|
|
52
45
|
@pytest.mark.asyncio
|
|
53
46
|
async def test_health_check(self, adapter):
|
|
54
|
-
"""Test adapter health check"""
|
|
55
47
|
health = await adapter.health_check()
|
|
56
48
|
assert isinstance(health, dict)
|
|
57
49
|
assert health.get("status") in {"healthy", "unhealthy", "error"}
|
|
@@ -59,21 +51,28 @@ class TestMoonwellAdapter:
|
|
|
59
51
|
|
|
60
52
|
@pytest.mark.asyncio
|
|
61
53
|
async def test_connect(self, adapter):
|
|
62
|
-
"""Test adapter connection"""
|
|
63
54
|
ok = await adapter.connect()
|
|
64
55
|
assert isinstance(ok, bool)
|
|
65
56
|
assert ok is True
|
|
66
57
|
|
|
67
58
|
@pytest.mark.asyncio
|
|
68
|
-
async def
|
|
69
|
-
"""
|
|
70
|
-
with
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
async def test_lend(self, adapter):
|
|
60
|
+
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
61
|
+
with (
|
|
62
|
+
patch.object(
|
|
63
|
+
adapter, "_ensure_allowance", new_callable=AsyncMock
|
|
64
|
+
) as mock_allowance,
|
|
65
|
+
patch.object(
|
|
66
|
+
adapter, "_encode_call", new_callable=AsyncMock
|
|
67
|
+
) as mock_encode,
|
|
68
|
+
patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
|
|
69
|
+
):
|
|
70
|
+
mock_allowance.return_value = (True, {})
|
|
73
71
|
mock_encode.return_value = {
|
|
74
72
|
"data": "0x1234",
|
|
75
73
|
"to": MOONWELL_DEFAULTS["m_usdc"],
|
|
76
74
|
}
|
|
75
|
+
mock_send.return_value = (True, mock_tx_hash)
|
|
77
76
|
|
|
78
77
|
success, result = await adapter.lend(
|
|
79
78
|
mtoken=MOONWELL_DEFAULTS["m_usdc"],
|
|
@@ -82,11 +81,10 @@ class TestMoonwellAdapter:
|
|
|
82
81
|
)
|
|
83
82
|
|
|
84
83
|
assert success
|
|
85
|
-
assert
|
|
84
|
+
assert result == mock_tx_hash
|
|
86
85
|
|
|
87
86
|
@pytest.mark.asyncio
|
|
88
87
|
async def test_lend_invalid_amount(self, adapter):
|
|
89
|
-
"""Test lend with invalid amount"""
|
|
90
88
|
success, result = await adapter.lend(
|
|
91
89
|
mtoken=MOONWELL_DEFAULTS["m_usdc"],
|
|
92
90
|
underlying_token=MOONWELL_DEFAULTS["usdc"],
|
|
@@ -97,8 +95,7 @@ class TestMoonwellAdapter:
|
|
|
97
95
|
assert "positive" in result.lower()
|
|
98
96
|
|
|
99
97
|
@pytest.mark.asyncio
|
|
100
|
-
async def
|
|
101
|
-
"""Test unlend operation in simulation mode"""
|
|
98
|
+
async def test_unlend(self, adapter):
|
|
102
99
|
# Mock contract encoding
|
|
103
100
|
mock_contract = MagicMock()
|
|
104
101
|
mock_contract.functions.redeem = MagicMock(
|
|
@@ -113,21 +110,26 @@ class TestMoonwellAdapter:
|
|
|
113
110
|
async def mock_web3_ctx(_chain_id):
|
|
114
111
|
yield mock_web3
|
|
115
112
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
114
|
+
|
|
115
|
+
with (
|
|
116
|
+
patch(
|
|
117
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
|
|
118
|
+
mock_web3_ctx,
|
|
119
|
+
),
|
|
120
|
+
patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
|
|
119
121
|
):
|
|
122
|
+
mock_send.return_value = (True, mock_tx_hash)
|
|
120
123
|
success, result = await adapter.unlend(
|
|
121
124
|
mtoken=MOONWELL_DEFAULTS["m_usdc"],
|
|
122
125
|
amount=10**8,
|
|
123
126
|
)
|
|
124
127
|
|
|
125
128
|
assert success
|
|
126
|
-
assert
|
|
129
|
+
assert result == mock_tx_hash
|
|
127
130
|
|
|
128
131
|
@pytest.mark.asyncio
|
|
129
132
|
async def test_unlend_invalid_amount(self, adapter):
|
|
130
|
-
"""Test unlend with invalid amount"""
|
|
131
133
|
success, result = await adapter.unlend(
|
|
132
134
|
mtoken=MOONWELL_DEFAULTS["m_usdc"],
|
|
133
135
|
amount=-1,
|
|
@@ -137,12 +139,11 @@ class TestMoonwellAdapter:
|
|
|
137
139
|
assert "positive" in result.lower()
|
|
138
140
|
|
|
139
141
|
@pytest.mark.asyncio
|
|
140
|
-
async def
|
|
141
|
-
"""Test borrow operation in simulation mode"""
|
|
142
|
+
async def test_borrow(self, adapter):
|
|
142
143
|
# Track calls to return different values (0 before, 10**6 after)
|
|
143
144
|
borrow_balance_calls = [0]
|
|
144
145
|
|
|
145
|
-
async def mock_borrow_balance_call():
|
|
146
|
+
async def mock_borrow_balance_call(**kwargs):
|
|
146
147
|
result = borrow_balance_calls[0]
|
|
147
148
|
# Next call returns increased balance
|
|
148
149
|
borrow_balance_calls[0] = 10**6
|
|
@@ -155,7 +156,7 @@ class TestMoonwellAdapter:
|
|
|
155
156
|
)
|
|
156
157
|
mock_mtoken.functions.borrow = MagicMock(
|
|
157
158
|
return_value=MagicMock(
|
|
158
|
-
call=AsyncMock(return_value=0),
|
|
159
|
+
call=AsyncMock(return_value=0),
|
|
159
160
|
_encode_transaction_data=MagicMock(return_value="0x1234"),
|
|
160
161
|
build_transaction=AsyncMock(return_value={"data": "0x1234"}),
|
|
161
162
|
)
|
|
@@ -168,28 +169,42 @@ class TestMoonwellAdapter:
|
|
|
168
169
|
async def mock_web3_ctx(_chain_id):
|
|
169
170
|
yield mock_web3
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
173
|
+
|
|
174
|
+
with (
|
|
175
|
+
patch(
|
|
176
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
|
|
177
|
+
mock_web3_ctx,
|
|
178
|
+
),
|
|
179
|
+
patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
|
|
174
180
|
):
|
|
181
|
+
mock_send.return_value = (True, mock_tx_hash)
|
|
175
182
|
success, result = await adapter.borrow(
|
|
176
183
|
mtoken=MOONWELL_DEFAULTS["m_usdc"],
|
|
177
184
|
amount=10**6,
|
|
178
185
|
)
|
|
179
186
|
|
|
180
187
|
assert success
|
|
181
|
-
assert
|
|
188
|
+
assert result == mock_tx_hash
|
|
182
189
|
|
|
183
190
|
@pytest.mark.asyncio
|
|
184
|
-
async def
|
|
185
|
-
"""
|
|
186
|
-
with
|
|
187
|
-
|
|
188
|
-
|
|
191
|
+
async def test_repay(self, adapter):
|
|
192
|
+
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
193
|
+
with (
|
|
194
|
+
patch.object(
|
|
195
|
+
adapter, "_ensure_allowance", new_callable=AsyncMock
|
|
196
|
+
) as mock_allowance,
|
|
197
|
+
patch.object(
|
|
198
|
+
adapter, "_encode_call", new_callable=AsyncMock
|
|
199
|
+
) as mock_encode,
|
|
200
|
+
patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
|
|
201
|
+
):
|
|
202
|
+
mock_allowance.return_value = (True, {})
|
|
189
203
|
mock_encode.return_value = {
|
|
190
204
|
"data": "0x1234",
|
|
191
205
|
"to": MOONWELL_DEFAULTS["m_usdc"],
|
|
192
206
|
}
|
|
207
|
+
mock_send.return_value = (True, mock_tx_hash)
|
|
193
208
|
|
|
194
209
|
success, result = await adapter.repay(
|
|
195
210
|
mtoken=MOONWELL_DEFAULTS["m_usdc"],
|
|
@@ -198,11 +213,10 @@ class TestMoonwellAdapter:
|
|
|
198
213
|
)
|
|
199
214
|
|
|
200
215
|
assert success
|
|
201
|
-
assert
|
|
216
|
+
assert result == mock_tx_hash
|
|
202
217
|
|
|
203
218
|
@pytest.mark.asyncio
|
|
204
|
-
async def
|
|
205
|
-
"""Test set collateral operation in simulation mode"""
|
|
219
|
+
async def test_set_collateral(self, adapter):
|
|
206
220
|
# Mock comptroller contract for verification
|
|
207
221
|
mock_comptroller = MagicMock()
|
|
208
222
|
mock_comptroller.functions.checkMembership = MagicMock(
|
|
@@ -222,20 +236,25 @@ class TestMoonwellAdapter:
|
|
|
222
236
|
async def mock_web3_ctx(_chain_id):
|
|
223
237
|
yield mock_web3
|
|
224
238
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
239
|
+
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
240
|
+
|
|
241
|
+
with (
|
|
242
|
+
patch(
|
|
243
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
|
|
244
|
+
mock_web3_ctx,
|
|
245
|
+
),
|
|
246
|
+
patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
|
|
228
247
|
):
|
|
248
|
+
mock_send.return_value = (True, mock_tx_hash)
|
|
229
249
|
success, result = await adapter.set_collateral(
|
|
230
250
|
mtoken=MOONWELL_DEFAULTS["m_wsteth"],
|
|
231
251
|
)
|
|
232
252
|
|
|
233
253
|
assert success is True
|
|
234
|
-
assert
|
|
254
|
+
assert result == mock_tx_hash
|
|
235
255
|
|
|
236
256
|
@pytest.mark.asyncio
|
|
237
|
-
async def
|
|
238
|
-
"""Test claim rewards operation in simulation mode"""
|
|
257
|
+
async def test_claim_rewards(self, adapter):
|
|
239
258
|
# Mock contract for getting outstanding rewards
|
|
240
259
|
mock_reward_contract = MagicMock()
|
|
241
260
|
mock_reward_contract.functions.getOutstandingRewardsForUser = MagicMock(
|
|
@@ -265,7 +284,6 @@ class TestMoonwellAdapter:
|
|
|
265
284
|
|
|
266
285
|
@pytest.mark.asyncio
|
|
267
286
|
async def test_get_pos_success(self, adapter):
|
|
268
|
-
"""Test get position data"""
|
|
269
287
|
underlying_addr = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
270
288
|
|
|
271
289
|
# Mock mtoken contract calls
|
|
@@ -317,7 +335,6 @@ class TestMoonwellAdapter:
|
|
|
317
335
|
|
|
318
336
|
@pytest.mark.asyncio
|
|
319
337
|
async def test_get_collateral_factor_success(self, adapter):
|
|
320
|
-
"""Test get collateral factor"""
|
|
321
338
|
# Clear cache to ensure fresh test
|
|
322
339
|
adapter._cf_cache.clear()
|
|
323
340
|
|
|
@@ -348,7 +365,6 @@ class TestMoonwellAdapter:
|
|
|
348
365
|
|
|
349
366
|
@pytest.mark.asyncio
|
|
350
367
|
async def test_get_collateral_factor_not_listed(self, adapter):
|
|
351
|
-
"""Test get collateral factor for unlisted market"""
|
|
352
368
|
mock_contract = MagicMock()
|
|
353
369
|
mock_contract.functions.markets = MagicMock(
|
|
354
370
|
return_value=MagicMock(call=AsyncMock(return_value=(False, 0)))
|
|
@@ -373,7 +389,6 @@ class TestMoonwellAdapter:
|
|
|
373
389
|
|
|
374
390
|
@pytest.mark.asyncio
|
|
375
391
|
async def test_get_collateral_factor_caching(self, adapter):
|
|
376
|
-
"""Test that collateral factor is cached and subsequent calls don't hit RPC"""
|
|
377
392
|
# Clear cache to ensure fresh test
|
|
378
393
|
adapter._cf_cache.clear()
|
|
379
394
|
|
|
@@ -411,24 +426,22 @@ class TestMoonwellAdapter:
|
|
|
411
426
|
success2, result2 = await adapter.get_collateral_factor(mtoken=mtoken)
|
|
412
427
|
assert success2 is True
|
|
413
428
|
assert result2 == 0.80
|
|
414
|
-
assert call_count == 1
|
|
429
|
+
assert call_count == 1
|
|
415
430
|
|
|
416
431
|
# Third call for same mtoken should still use cache
|
|
417
432
|
success3, result3 = await adapter.get_collateral_factor(mtoken=mtoken)
|
|
418
433
|
assert success3 is True
|
|
419
434
|
assert result3 == 0.80
|
|
420
|
-
assert call_count == 1
|
|
435
|
+
assert call_count == 1
|
|
421
436
|
|
|
422
|
-
# Call for different mtoken should hit RPC
|
|
423
437
|
success4, result4 = await adapter.get_collateral_factor(
|
|
424
438
|
mtoken=MOONWELL_DEFAULTS["m_usdc"]
|
|
425
439
|
)
|
|
426
440
|
assert success4 is True
|
|
427
|
-
assert call_count == 2
|
|
441
|
+
assert call_count == 2
|
|
428
442
|
|
|
429
443
|
@pytest.mark.asyncio
|
|
430
444
|
async def test_get_collateral_factor_cache_expiry(self, adapter):
|
|
431
|
-
"""Test that collateral factor cache expires after TTL"""
|
|
432
445
|
import time
|
|
433
446
|
|
|
434
447
|
from wayfinder_paths.adapters.moonwell_adapter import adapter as adapter_module
|
|
@@ -436,12 +449,10 @@ class TestMoonwellAdapter:
|
|
|
436
449
|
# Clear cache to ensure fresh test
|
|
437
450
|
adapter._cf_cache.clear()
|
|
438
451
|
|
|
439
|
-
# Save original TTL
|
|
440
452
|
original_ttl = adapter_module.CF_CACHE_TTL
|
|
441
453
|
|
|
442
454
|
try:
|
|
443
|
-
|
|
444
|
-
adapter_module.CF_CACHE_TTL = 0.1 # 100ms
|
|
455
|
+
adapter_module.CF_CACHE_TTL = 0.1
|
|
445
456
|
|
|
446
457
|
call_count = 0
|
|
447
458
|
|
|
@@ -478,7 +489,6 @@ class TestMoonwellAdapter:
|
|
|
478
489
|
# Wait for cache to expire
|
|
479
490
|
time.sleep(0.15)
|
|
480
491
|
|
|
481
|
-
# Call after expiry should hit RPC again
|
|
482
492
|
await adapter.get_collateral_factor(mtoken=mtoken)
|
|
483
493
|
assert call_count == 2
|
|
484
494
|
|
|
@@ -488,7 +498,6 @@ class TestMoonwellAdapter:
|
|
|
488
498
|
|
|
489
499
|
@pytest.mark.asyncio
|
|
490
500
|
async def test_get_apy_supply(self, adapter):
|
|
491
|
-
"""Test get supply APY"""
|
|
492
501
|
rate_per_second = int(1.5e9)
|
|
493
502
|
|
|
494
503
|
# Mock mtoken contract
|
|
@@ -534,7 +543,6 @@ class TestMoonwellAdapter:
|
|
|
534
543
|
|
|
535
544
|
@pytest.mark.asyncio
|
|
536
545
|
async def test_get_apy_borrow(self, adapter):
|
|
537
|
-
"""Test get borrow APY"""
|
|
538
546
|
rate_per_second = int(2e9)
|
|
539
547
|
|
|
540
548
|
# Mock mtoken contract
|
|
@@ -580,14 +588,9 @@ class TestMoonwellAdapter:
|
|
|
580
588
|
|
|
581
589
|
@pytest.mark.asyncio
|
|
582
590
|
async def test_get_borrowable_amount_success(self, adapter):
|
|
583
|
-
"""Test get borrowable amount"""
|
|
584
591
|
mock_contract = MagicMock()
|
|
585
592
|
mock_contract.functions.getAccountLiquidity = MagicMock(
|
|
586
|
-
return_value=MagicMock(
|
|
587
|
-
call=AsyncMock(
|
|
588
|
-
return_value=(0, 10**18, 0)
|
|
589
|
-
) # error, liquidity, shortfall
|
|
590
|
-
)
|
|
593
|
+
return_value=MagicMock(call=AsyncMock(return_value=(0, 10**18, 0)))
|
|
591
594
|
)
|
|
592
595
|
mock_web3 = MagicMock()
|
|
593
596
|
mock_web3.eth.contract = MagicMock(return_value=mock_contract)
|
|
@@ -607,12 +610,9 @@ class TestMoonwellAdapter:
|
|
|
607
610
|
|
|
608
611
|
@pytest.mark.asyncio
|
|
609
612
|
async def test_get_borrowable_amount_shortfall(self, adapter):
|
|
610
|
-
"""Test get borrowable amount when account has shortfall"""
|
|
611
613
|
mock_contract = MagicMock()
|
|
612
614
|
mock_contract.functions.getAccountLiquidity = MagicMock(
|
|
613
|
-
return_value=MagicMock(
|
|
614
|
-
call=AsyncMock(return_value=(0, 0, 10**16)) # has shortfall
|
|
615
|
-
)
|
|
615
|
+
return_value=MagicMock(call=AsyncMock(return_value=(0, 0, 10**16)))
|
|
616
616
|
)
|
|
617
617
|
mock_web3 = MagicMock()
|
|
618
618
|
mock_web3.eth.contract = MagicMock(return_value=mock_contract)
|
|
@@ -631,8 +631,7 @@ class TestMoonwellAdapter:
|
|
|
631
631
|
assert "shortfall" in result.lower()
|
|
632
632
|
|
|
633
633
|
@pytest.mark.asyncio
|
|
634
|
-
async def
|
|
635
|
-
"""Test wrap ETH operation in simulation mode"""
|
|
634
|
+
async def test_wrap_eth(self, adapter):
|
|
636
635
|
mock_contract = MagicMock()
|
|
637
636
|
mock_contract.functions.deposit = MagicMock(
|
|
638
637
|
return_value=MagicMock(
|
|
@@ -646,29 +645,32 @@ class TestMoonwellAdapter:
|
|
|
646
645
|
async def mock_web3_ctx(_chain_id):
|
|
647
646
|
yield mock_web3
|
|
648
647
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
648
|
+
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
649
|
+
|
|
650
|
+
with (
|
|
651
|
+
patch(
|
|
652
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
|
|
653
|
+
mock_web3_ctx,
|
|
654
|
+
),
|
|
655
|
+
patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
|
|
652
656
|
):
|
|
657
|
+
mock_send.return_value = (True, mock_tx_hash)
|
|
653
658
|
success, result = await adapter.wrap_eth(amount=10**18)
|
|
654
659
|
|
|
655
660
|
assert success
|
|
656
|
-
assert
|
|
661
|
+
assert result == mock_tx_hash
|
|
657
662
|
|
|
658
663
|
def test_strategy_address_missing(self):
|
|
659
|
-
|
|
660
|
-
adapter = MoonwellAdapter(config={}, simulation=True)
|
|
664
|
+
adapter = MoonwellAdapter(config={})
|
|
661
665
|
|
|
662
666
|
with pytest.raises(ValueError, match="strategy_wallet"):
|
|
663
667
|
adapter._strategy_address()
|
|
664
668
|
|
|
665
669
|
def test_checksum_missing_address(self, adapter):
|
|
666
|
-
"""Test error when address is missing"""
|
|
667
670
|
with pytest.raises(ValueError, match="Missing required"):
|
|
668
671
|
adapter._checksum(None)
|
|
669
672
|
|
|
670
673
|
def test_config_override(self):
|
|
671
|
-
"""Test config can override default addresses"""
|
|
672
674
|
custom_comptroller = "0x1111111111111111111111111111111111111111"
|
|
673
675
|
custom_well = "0x2222222222222222222222222222222222222222"
|
|
674
676
|
config = {
|
|
@@ -682,7 +684,7 @@ class TestMoonwellAdapter:
|
|
|
682
684
|
},
|
|
683
685
|
}
|
|
684
686
|
|
|
685
|
-
adapter = MoonwellAdapter(config=config
|
|
687
|
+
adapter = MoonwellAdapter(config=config)
|
|
686
688
|
|
|
687
689
|
assert adapter.comptroller_address.lower() == custom_comptroller.lower()
|
|
688
690
|
assert adapter.well_token.lower() == custom_well.lower()
|
|
@@ -690,7 +692,6 @@ class TestMoonwellAdapter:
|
|
|
690
692
|
|
|
691
693
|
@pytest.mark.asyncio
|
|
692
694
|
async def test_max_withdrawable_mtoken_zero_balance(self, adapter):
|
|
693
|
-
"""Test max withdrawable when balance is zero"""
|
|
694
695
|
# Mock contracts
|
|
695
696
|
mock_mtoken = MagicMock()
|
|
696
697
|
mock_mtoken.functions.balanceOf = MagicMock(
|
|
@@ -1,37 +1,30 @@
|
|
|
1
1
|
# Pool Adapter
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Adapter for DeFi pool data and yield analytics.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- **Type**: `POOL`
|
|
6
|
+
- **Module**: `wayfinder_paths.adapters.pool_adapter.adapter.PoolAdapter`
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- `pool.analytics`: Get comprehensive pool analytics
|
|
9
|
-
- `pool.discovery`: Find and search pools
|
|
10
|
-
- `llama.data`: Access Llama protocol data
|
|
11
|
-
- `pool.reports`: Get pool reports and analytics
|
|
8
|
+
## Overview
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
The PoolClient will automatically:
|
|
18
|
-
|
|
19
|
-
- Use the WAYFINDER_API_URL from settings
|
|
20
|
-
- Handle authentication via config.json
|
|
21
|
-
- Manage token refresh and retry logic
|
|
10
|
+
The PoolAdapter provides:
|
|
11
|
+
- Pool information and metadata
|
|
12
|
+
- Yield analytics via DeFi Llama integration
|
|
13
|
+
- Pool discovery and filtering
|
|
22
14
|
|
|
23
15
|
## Usage
|
|
24
16
|
|
|
25
|
-
### Initialize the Adapter
|
|
26
|
-
|
|
27
17
|
```python
|
|
28
18
|
from wayfinder_paths.adapters.pool_adapter.adapter import PoolAdapter
|
|
29
19
|
|
|
30
|
-
# No configuration needed - uses PoolClient with automatic settings
|
|
31
20
|
adapter = PoolAdapter()
|
|
32
21
|
```
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
## Methods
|
|
24
|
+
|
|
25
|
+
### get_pools_by_ids
|
|
26
|
+
|
|
27
|
+
Fetch pool information by pool IDs.
|
|
35
28
|
|
|
36
29
|
```python
|
|
37
30
|
success, data = await adapter.get_pools_by_ids(
|
|
@@ -39,51 +32,37 @@ success, data = await adapter.get_pools_by_ids(
|
|
|
39
32
|
)
|
|
40
33
|
if success:
|
|
41
34
|
pools = data.get("pools", [])
|
|
42
|
-
print(f"Found {len(pools)} pools")
|
|
43
|
-
else:
|
|
44
|
-
print(f"Error: {data}")
|
|
45
35
|
```
|
|
46
36
|
|
|
47
|
-
###
|
|
37
|
+
### get_pools
|
|
48
38
|
|
|
49
|
-
|
|
39
|
+
Fetch pools with optional filtering.
|
|
50
40
|
|
|
51
41
|
```python
|
|
52
|
-
success, data = await adapter.get_pools(
|
|
42
|
+
success, data = await adapter.get_pools(
|
|
43
|
+
chain_id=8453, # Filter by chain (e.g., Base)
|
|
44
|
+
project="lido", # Optional: filter by project
|
|
45
|
+
)
|
|
53
46
|
if success:
|
|
54
47
|
matches = data.get("matches", [])
|
|
55
|
-
for match in matches:
|
|
56
|
-
if match.get("stablecoin"):
|
|
57
|
-
print(f"Pool {match.get('id')} - APY: {match.get('apy')}%")
|
|
58
|
-
else:
|
|
59
|
-
print(f"Error: {data}")
|
|
60
48
|
```
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
## API Endpoints
|
|
50
|
+
## Response Format
|
|
65
51
|
|
|
66
|
-
|
|
52
|
+
Pool data includes:
|
|
53
|
+
- `id` - Pool identifier
|
|
54
|
+
- `apy` - Current APY
|
|
55
|
+
- `tvl` - Total value locked
|
|
56
|
+
- `chain` - Chain information
|
|
57
|
+
- `project` - Protocol name
|
|
58
|
+
- `stablecoin` - Whether pool is stablecoin-based
|
|
67
59
|
|
|
68
|
-
|
|
69
|
-
- `POST /v1/blockchain/pools/` - Get pools by IDs (body: `{"pool_ids": ["id1", "id2"]}`)
|
|
70
|
-
|
|
71
|
-
## Error Handling
|
|
72
|
-
|
|
73
|
-
All methods return a tuple of `(success: bool, data: Any)` where:
|
|
60
|
+
## Dependencies
|
|
74
61
|
|
|
75
|
-
- `
|
|
76
|
-
- `data` contains the response data on success or error message on failure
|
|
62
|
+
- `PoolClient` - Low-level API client
|
|
77
63
|
|
|
78
64
|
## Testing
|
|
79
65
|
|
|
80
|
-
Run the adapter tests:
|
|
81
|
-
|
|
82
66
|
```bash
|
|
83
|
-
pytest wayfinder_paths/adapters/pool_adapter/
|
|
67
|
+
poetry run pytest wayfinder_paths/adapters/pool_adapter/ -v
|
|
84
68
|
```
|
|
85
|
-
|
|
86
|
-
## Dependencies
|
|
87
|
-
|
|
88
|
-
- `PoolClient` - Low-level API client for pool operations
|
|
89
|
-
- `BaseAdapter` - Base adapter class with common functionality
|
|
@@ -9,16 +9,6 @@ from wayfinder_paths.core.clients.PoolClient import (
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class PoolAdapter(BaseAdapter):
|
|
12
|
-
"""
|
|
13
|
-
Pool adapter for DeFi pool data and analytics operations.
|
|
14
|
-
|
|
15
|
-
Provides high-level operations for:
|
|
16
|
-
- Fetching pool information and metadata
|
|
17
|
-
- Getting pool analytics and reports
|
|
18
|
-
- Accessing Llama protocol data
|
|
19
|
-
- Pool discovery and filtering
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
12
|
adapter_type: str = "POOL"
|
|
23
13
|
|
|
24
14
|
def __init__(
|
|
@@ -32,15 +22,6 @@ class PoolAdapter(BaseAdapter):
|
|
|
32
22
|
async def get_pools_by_ids(
|
|
33
23
|
self, pool_ids: list[str]
|
|
34
24
|
) -> tuple[bool, PoolList | str]:
|
|
35
|
-
"""
|
|
36
|
-
Get pool information by pool IDs.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
pool_ids: List of pool identifiers
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
Tuple of (success, data) where data is pool information or error message
|
|
43
|
-
"""
|
|
44
25
|
try:
|
|
45
26
|
data = await self.pool_client.get_pools_by_ids(pool_ids=pool_ids)
|
|
46
27
|
return (True, data)
|
|
@@ -6,24 +6,19 @@ from wayfinder_paths.adapters.pool_adapter.adapter import PoolAdapter
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TestPoolAdapter:
|
|
9
|
-
"""Test cases for PoolAdapter"""
|
|
10
|
-
|
|
11
9
|
@pytest.fixture
|
|
12
10
|
def mock_pool_client(self):
|
|
13
|
-
"""Mock PoolClient for testing"""
|
|
14
11
|
mock_client = AsyncMock()
|
|
15
12
|
return mock_client
|
|
16
13
|
|
|
17
14
|
@pytest.fixture
|
|
18
15
|
def adapter(self, mock_pool_client):
|
|
19
|
-
"""Create a PoolAdapter instance with mocked client for testing"""
|
|
20
16
|
adapter = PoolAdapter()
|
|
21
17
|
adapter.pool_client = mock_pool_client
|
|
22
18
|
return adapter
|
|
23
19
|
|
|
24
20
|
@pytest.mark.asyncio
|
|
25
21
|
async def test_get_pools_by_ids_success(self, adapter, mock_pool_client):
|
|
26
|
-
"""Test successful pool retrieval by IDs"""
|
|
27
22
|
mock_response = {
|
|
28
23
|
"pools": [
|
|
29
24
|
{
|
|
@@ -49,7 +44,6 @@ class TestPoolAdapter:
|
|
|
49
44
|
|
|
50
45
|
@pytest.mark.asyncio
|
|
51
46
|
async def test_get_pools_success(self, adapter, mock_pool_client):
|
|
52
|
-
"""Test successful Llama matches retrieval"""
|
|
53
47
|
# Mock response
|
|
54
48
|
mock_response = {
|
|
55
49
|
"matches": [
|
|
@@ -71,7 +65,6 @@ class TestPoolAdapter:
|
|
|
71
65
|
|
|
72
66
|
@pytest.mark.asyncio
|
|
73
67
|
async def test_get_pools_by_ids_failure(self, adapter, mock_pool_client):
|
|
74
|
-
"""Test pool retrieval failure"""
|
|
75
68
|
mock_pool_client.get_pools_by_ids = AsyncMock(
|
|
76
69
|
side_effect=Exception("API Error")
|
|
77
70
|
)
|
|
@@ -82,5 +75,4 @@ class TestPoolAdapter:
|
|
|
82
75
|
assert "API Error" in data
|
|
83
76
|
|
|
84
77
|
def test_adapter_type(self, adapter):
|
|
85
|
-
"""Test adapter has adapter_type"""
|
|
86
78
|
assert adapter.adapter_type == "POOL"
|