wayfinder-paths 0.1.22__py3-none-any.whl → 0.1.24__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 -4
- wayfinder_paths/adapters/balance_adapter/README.md +0 -1
- wayfinder_paths/adapters/balance_adapter/adapter.py +313 -167
- wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +41 -124
- wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
- wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
- wayfinder_paths/adapters/boros_adapter/client.py +476 -0
- wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
- wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
- wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
- wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
- wayfinder_paths/adapters/boros_adapter/types.py +70 -0
- wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
- wayfinder_paths/adapters/brap_adapter/README.md +22 -75
- wayfinder_paths/adapters/brap_adapter/adapter.py +187 -576
- wayfinder_paths/adapters/brap_adapter/examples.json +21 -140
- wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +6 -234
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +180 -92
- wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +82 -14
- wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +586 -61
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
- wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
- wayfinder_paths/adapters/ledger_adapter/README.md +4 -1
- wayfinder_paths/adapters/ledger_adapter/adapter.py +3 -3
- wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +649 -547
- wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +160 -239
- wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
- wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
- wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
- wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
- wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
- wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
- wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
- wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
- wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
- wayfinder_paths/adapters/token_adapter/adapter.py +14 -0
- wayfinder_paths/adapters/token_adapter/examples.json +0 -4
- wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
- wayfinder_paths/conftest.py +24 -17
- wayfinder_paths/core/__init__.py +0 -3
- wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
- wayfinder_paths/core/adapters/models.py +17 -7
- wayfinder_paths/core/clients/BRAPClient.py +4 -1
- wayfinder_paths/core/clients/ClientManager.py +0 -7
- wayfinder_paths/core/clients/LedgerClient.py +196 -172
- wayfinder_paths/core/clients/TokenClient.py +47 -1
- wayfinder_paths/core/clients/WayfinderClient.py +1 -3
- wayfinder_paths/core/clients/__init__.py +0 -5
- wayfinder_paths/core/clients/protocols.py +21 -35
- wayfinder_paths/core/clients/test_ledger_client.py +448 -0
- wayfinder_paths/core/config.py +10 -162
- wayfinder_paths/core/constants/__init__.py +73 -2
- wayfinder_paths/core/constants/base.py +8 -17
- wayfinder_paths/core/constants/chains.py +36 -0
- wayfinder_paths/core/constants/contracts.py +52 -0
- wayfinder_paths/core/constants/erc20_abi.py +0 -1
- wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
- wayfinder_paths/core/constants/hyperliquid.py +16 -0
- wayfinder_paths/core/constants/moonwell_abi.py +0 -15
- wayfinder_paths/core/constants/tokens.py +9 -0
- wayfinder_paths/core/engine/manifest.py +66 -0
- wayfinder_paths/core/strategies/Strategy.py +0 -71
- wayfinder_paths/core/strategies/__init__.py +10 -1
- wayfinder_paths/core/strategies/opa_loop.py +167 -0
- wayfinder_paths/core/utils/evm_helpers.py +5 -15
- wayfinder_paths/core/utils/test_transaction.py +289 -0
- wayfinder_paths/core/utils/tokens.py +28 -0
- wayfinder_paths/core/utils/transaction.py +57 -8
- wayfinder_paths/core/utils/web3.py +8 -3
- wayfinder_paths/mcp/__init__.py +5 -0
- wayfinder_paths/mcp/preview.py +185 -0
- wayfinder_paths/mcp/scripting.py +84 -0
- wayfinder_paths/mcp/server.py +52 -0
- wayfinder_paths/mcp/state/profile_store.py +195 -0
- wayfinder_paths/mcp/state/store.py +89 -0
- wayfinder_paths/mcp/test_scripting.py +267 -0
- wayfinder_paths/mcp/tools/__init__.py +0 -0
- wayfinder_paths/mcp/tools/balances.py +290 -0
- wayfinder_paths/mcp/tools/discovery.py +158 -0
- wayfinder_paths/mcp/tools/execute.py +770 -0
- wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
- wayfinder_paths/mcp/tools/quotes.py +288 -0
- wayfinder_paths/mcp/tools/run_script.py +286 -0
- wayfinder_paths/mcp/tools/strategies.py +188 -0
- wayfinder_paths/mcp/tools/tokens.py +46 -0
- wayfinder_paths/mcp/tools/wallets.py +354 -0
- wayfinder_paths/mcp/utils.py +129 -0
- wayfinder_paths/policies/enso.py +1 -2
- wayfinder_paths/policies/hyper_evm.py +6 -3
- wayfinder_paths/policies/hyperlend.py +1 -2
- wayfinder_paths/policies/hyperliquid.py +1 -1
- wayfinder_paths/policies/lifi.py +18 -0
- wayfinder_paths/policies/moonwell.py +12 -7
- wayfinder_paths/policies/prjx.py +1 -3
- wayfinder_paths/policies/util.py +8 -2
- wayfinder_paths/run_strategy.py +97 -300
- wayfinder_paths/strategies/basis_trading_strategy/constants.py +3 -1
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +47 -133
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
- wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
- wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
- wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
- wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
- wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
- wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
- wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
- wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
- wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
- wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
- wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
- wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
- wayfinder_paths/{templates/strategy → strategies/boros_hype_strategy}/test_strategy.py +99 -63
- wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
- wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +15 -23
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +27 -62
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +84 -58
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +69 -164
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +43 -76
- wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
- wayfinder_paths/tests/test_test_coverage.py +1 -4
- wayfinder_paths-0.1.24.dist-info/METADATA +378 -0
- wayfinder_paths-0.1.24.dist-info/RECORD +185 -0
- {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.24.dist-info}/WHEEL +1 -1
- wayfinder_paths/core/clients/WalletClient.py +0 -41
- wayfinder_paths/core/engine/StrategyJob.py +0 -110
- wayfinder_paths/core/services/test_local_evm_txn.py +0 -145
- wayfinder_paths/scripts/create_strategy.py +0 -139
- wayfinder_paths/scripts/make_wallets.py +0 -142
- wayfinder_paths/templates/adapter/README.md +0 -150
- wayfinder_paths/templates/adapter/adapter.py +0 -16
- wayfinder_paths/templates/adapter/examples.json +0 -8
- wayfinder_paths/templates/adapter/test_adapter.py +0 -30
- wayfinder_paths/templates/strategy/README.md +0 -186
- wayfinder_paths/templates/strategy/examples.json +0 -11
- wayfinder_paths/templates/strategy/strategy.py +0 -35
- wayfinder_paths/tests/test_smoke_manifest.py +0 -63
- wayfinder_paths-0.1.22.dist-info/METADATA +0 -355
- wayfinder_paths-0.1.22.dist-info/RECORD +0 -129
- /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
- {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.24.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
schema_version: "0.1"
|
|
2
|
+
entrypoint: "adapters.moonwell_adapter.adapter.MoonwellAdapter"
|
|
3
|
+
capabilities:
|
|
4
|
+
- "lending.lend"
|
|
5
|
+
- "lending.unlend"
|
|
6
|
+
- "lending.borrow"
|
|
7
|
+
- "lending.repay"
|
|
8
|
+
- "collateral.set"
|
|
9
|
+
- "collateral.remove"
|
|
10
|
+
- "rewards.claim"
|
|
11
|
+
- "position.read"
|
|
12
|
+
- "market.apy"
|
|
13
|
+
- "market.collateral_factor"
|
|
14
|
+
dependencies: []
|
|
@@ -2,13 +2,22 @@ from contextlib import asynccontextmanager
|
|
|
2
2
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
+
from web3 import Web3
|
|
5
6
|
|
|
6
7
|
from wayfinder_paths.adapters.moonwell_adapter.adapter import (
|
|
7
|
-
|
|
8
|
+
CHAIN_NAME,
|
|
8
9
|
MANTISSA,
|
|
9
|
-
MOONWELL_DEFAULTS,
|
|
10
10
|
MoonwellAdapter,
|
|
11
11
|
)
|
|
12
|
+
from wayfinder_paths.core.constants.contracts import (
|
|
13
|
+
BASE_USDC,
|
|
14
|
+
BASE_WETH,
|
|
15
|
+
MOONWELL_M_USDC,
|
|
16
|
+
MOONWELL_M_WETH,
|
|
17
|
+
MOONWELL_M_WSTETH,
|
|
18
|
+
MOONWELL_REWARD_DISTRIBUTOR,
|
|
19
|
+
MOONWELL_WELL_TOKEN,
|
|
20
|
+
)
|
|
12
21
|
|
|
13
22
|
|
|
14
23
|
class TestMoonwellAdapter:
|
|
@@ -22,61 +31,98 @@ class TestMoonwellAdapter:
|
|
|
22
31
|
def test_adapter_type(self, adapter):
|
|
23
32
|
assert adapter.adapter_type == "MOONWELL"
|
|
24
33
|
|
|
25
|
-
def
|
|
26
|
-
assert
|
|
27
|
-
adapter.comptroller_address.lower()
|
|
28
|
-
== MOONWELL_DEFAULTS["comptroller"].lower()
|
|
29
|
-
)
|
|
30
|
-
assert (
|
|
31
|
-
adapter.reward_distributor_address.lower()
|
|
32
|
-
== MOONWELL_DEFAULTS["reward_distributor"].lower()
|
|
33
|
-
)
|
|
34
|
-
assert adapter.m_usdc.lower() == MOONWELL_DEFAULTS["m_usdc"].lower()
|
|
35
|
-
assert adapter.m_weth.lower() == MOONWELL_DEFAULTS["m_weth"].lower()
|
|
36
|
-
assert adapter.m_wsteth.lower() == MOONWELL_DEFAULTS["m_wsteth"].lower()
|
|
37
|
-
assert adapter.well_token.lower() == MOONWELL_DEFAULTS["well_token"].lower()
|
|
34
|
+
def test_chain_name(self):
|
|
35
|
+
assert CHAIN_NAME == "base"
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
@pytest.mark.asyncio
|
|
38
|
+
async def test_get_full_user_state_basic(self, adapter):
|
|
39
|
+
w3 = Web3()
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
@asynccontextmanager
|
|
42
|
+
async def mock_web3_ctx(_chain_id):
|
|
43
|
+
yield w3
|
|
44
|
+
|
|
45
|
+
m1 = MOONWELL_M_USDC
|
|
46
|
+
m2 = MOONWELL_M_WETH
|
|
47
|
+
|
|
48
|
+
rewards = w3.codec.encode(
|
|
49
|
+
["(address,(address,uint256,uint256,uint256)[])[]"],
|
|
50
|
+
[[(m1, [(MOONWELL_WELL_TOKEN, 123, 0, 0)])]],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
stage1 = [
|
|
54
|
+
w3.codec.encode(["address[]"], [[m1, m2]]),
|
|
55
|
+
w3.codec.encode(["address[]"], [[m1]]),
|
|
56
|
+
w3.codec.encode(["uint256", "uint256", "uint256"], [0, 123, 0]),
|
|
57
|
+
rewards,
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
stage2 = [
|
|
61
|
+
# m1
|
|
62
|
+
w3.codec.encode(["uint256"], [100]),
|
|
63
|
+
w3.codec.encode(["uint256"], [2 * MANTISSA]),
|
|
64
|
+
w3.codec.encode(["uint256"], [50]),
|
|
65
|
+
w3.codec.encode(["address"], [BASE_USDC]),
|
|
66
|
+
w3.codec.encode(["uint8"], [8]),
|
|
67
|
+
w3.codec.encode(["bool", "uint256"], [True, int(0.5 * MANTISSA)]),
|
|
68
|
+
# m2 (all zeros, should be filtered out)
|
|
69
|
+
w3.codec.encode(["uint256"], [0]),
|
|
70
|
+
w3.codec.encode(["uint256"], [0]),
|
|
71
|
+
w3.codec.encode(["uint256"], [0]),
|
|
72
|
+
w3.codec.encode(["address"], [BASE_WETH]),
|
|
73
|
+
w3.codec.encode(["uint8"], [8]),
|
|
74
|
+
w3.codec.encode(["bool", "uint256"], [True, int(0.5 * MANTISSA)]),
|
|
75
|
+
]
|
|
44
76
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
with (
|
|
78
|
+
patch(
|
|
79
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
|
|
80
|
+
mock_web3_ctx,
|
|
81
|
+
),
|
|
82
|
+
patch.object(
|
|
83
|
+
adapter, "_multicall_chunked", new_callable=AsyncMock
|
|
84
|
+
) as mock_multicall,
|
|
85
|
+
):
|
|
86
|
+
mock_multicall.side_effect = [stage1, stage2]
|
|
87
|
+
ok, state = await adapter.get_full_user_state(
|
|
88
|
+
include_rewards=True,
|
|
89
|
+
include_usd=False,
|
|
90
|
+
include_apy=False,
|
|
91
|
+
)
|
|
51
92
|
|
|
52
|
-
@pytest.mark.asyncio
|
|
53
|
-
async def test_connect(self, adapter):
|
|
54
|
-
ok = await adapter.connect()
|
|
55
|
-
assert isinstance(ok, bool)
|
|
56
93
|
assert ok is True
|
|
94
|
+
assert state["protocol"] == "moonwell"
|
|
95
|
+
assert state["chainId"] == adapter.chain_id
|
|
96
|
+
assert state["accountLiquidity"]["liquidity"] == 123
|
|
97
|
+
assert len(state["positions"]) == 1
|
|
98
|
+
assert state["positions"][0]["enteredAsCollateral"] is True
|
|
99
|
+
assert state["positions"][0]["suppliedUnderlying"] == 200
|
|
100
|
+
assert state["rewards"][f"base_{MOONWELL_WELL_TOKEN.lower()}"] == 123
|
|
57
101
|
|
|
58
102
|
@pytest.mark.asyncio
|
|
59
103
|
async def test_lend(self, adapter):
|
|
60
104
|
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
61
105
|
with (
|
|
62
|
-
patch
|
|
63
|
-
adapter
|
|
106
|
+
patch(
|
|
107
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.ensure_allowance",
|
|
108
|
+
new_callable=AsyncMock,
|
|
64
109
|
) as mock_allowance,
|
|
65
|
-
patch
|
|
66
|
-
adapter
|
|
110
|
+
patch(
|
|
111
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
|
|
112
|
+
new_callable=AsyncMock,
|
|
67
113
|
) as mock_encode,
|
|
68
|
-
patch
|
|
114
|
+
patch(
|
|
115
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
|
|
116
|
+
new_callable=AsyncMock,
|
|
117
|
+
) as mock_send,
|
|
69
118
|
):
|
|
70
119
|
mock_allowance.return_value = (True, {})
|
|
71
|
-
mock_encode.return_value = {
|
|
72
|
-
|
|
73
|
-
"to": MOONWELL_DEFAULTS["m_usdc"],
|
|
74
|
-
}
|
|
75
|
-
mock_send.return_value = (True, mock_tx_hash)
|
|
120
|
+
mock_encode.return_value = {"data": "0x1234", "to": MOONWELL_M_USDC}
|
|
121
|
+
mock_send.return_value = mock_tx_hash
|
|
76
122
|
|
|
77
123
|
success, result = await adapter.lend(
|
|
78
|
-
mtoken=
|
|
79
|
-
underlying_token=
|
|
124
|
+
mtoken=MOONWELL_M_USDC,
|
|
125
|
+
underlying_token=BASE_USDC,
|
|
80
126
|
amount=10**6,
|
|
81
127
|
)
|
|
82
128
|
|
|
@@ -86,8 +132,8 @@ class TestMoonwellAdapter:
|
|
|
86
132
|
@pytest.mark.asyncio
|
|
87
133
|
async def test_lend_invalid_amount(self, adapter):
|
|
88
134
|
success, result = await adapter.lend(
|
|
89
|
-
mtoken=
|
|
90
|
-
underlying_token=
|
|
135
|
+
mtoken=MOONWELL_M_USDC,
|
|
136
|
+
underlying_token=BASE_USDC,
|
|
91
137
|
amount=0,
|
|
92
138
|
)
|
|
93
139
|
|
|
@@ -96,32 +142,22 @@ class TestMoonwellAdapter:
|
|
|
96
142
|
|
|
97
143
|
@pytest.mark.asyncio
|
|
98
144
|
async def test_unlend(self, adapter):
|
|
99
|
-
# Mock contract encoding
|
|
100
|
-
mock_contract = MagicMock()
|
|
101
|
-
mock_contract.functions.redeem = MagicMock(
|
|
102
|
-
return_value=MagicMock(
|
|
103
|
-
build_transaction=AsyncMock(return_value={"data": "0x1234"})
|
|
104
|
-
)
|
|
105
|
-
)
|
|
106
|
-
mock_web3 = MagicMock()
|
|
107
|
-
mock_web3.eth.contract = MagicMock(return_value=mock_contract)
|
|
108
|
-
|
|
109
|
-
@asynccontextmanager
|
|
110
|
-
async def mock_web3_ctx(_chain_id):
|
|
111
|
-
yield mock_web3
|
|
112
|
-
|
|
113
145
|
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
114
146
|
|
|
115
147
|
with (
|
|
116
148
|
patch(
|
|
117
|
-
"wayfinder_paths.adapters.moonwell_adapter.adapter.
|
|
118
|
-
|
|
119
|
-
),
|
|
120
|
-
patch
|
|
149
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
|
|
150
|
+
new_callable=AsyncMock,
|
|
151
|
+
) as mock_encode,
|
|
152
|
+
patch(
|
|
153
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
|
|
154
|
+
new_callable=AsyncMock,
|
|
155
|
+
) as mock_send,
|
|
121
156
|
):
|
|
122
|
-
|
|
157
|
+
mock_encode.return_value = {"data": "0x1234", "to": MOONWELL_M_USDC}
|
|
158
|
+
mock_send.return_value = mock_tx_hash
|
|
123
159
|
success, result = await adapter.unlend(
|
|
124
|
-
mtoken=
|
|
160
|
+
mtoken=MOONWELL_M_USDC,
|
|
125
161
|
amount=10**8,
|
|
126
162
|
)
|
|
127
163
|
|
|
@@ -131,7 +167,7 @@ class TestMoonwellAdapter:
|
|
|
131
167
|
@pytest.mark.asyncio
|
|
132
168
|
async def test_unlend_invalid_amount(self, adapter):
|
|
133
169
|
success, result = await adapter.unlend(
|
|
134
|
-
mtoken=
|
|
170
|
+
mtoken=MOONWELL_M_USDC,
|
|
135
171
|
amount=-1,
|
|
136
172
|
)
|
|
137
173
|
|
|
@@ -140,47 +176,22 @@ class TestMoonwellAdapter:
|
|
|
140
176
|
|
|
141
177
|
@pytest.mark.asyncio
|
|
142
178
|
async def test_borrow(self, adapter):
|
|
143
|
-
# Track calls to return different values (0 before, 10**6 after)
|
|
144
|
-
borrow_balance_calls = [0]
|
|
145
|
-
|
|
146
|
-
async def mock_borrow_balance_call(**kwargs):
|
|
147
|
-
result = borrow_balance_calls[0]
|
|
148
|
-
# Next call returns increased balance
|
|
149
|
-
borrow_balance_calls[0] = 10**6
|
|
150
|
-
return result
|
|
151
|
-
|
|
152
|
-
# Mock mtoken contract for pre-check and verification
|
|
153
|
-
mock_mtoken = MagicMock()
|
|
154
|
-
mock_mtoken.functions.borrowBalanceStored = MagicMock(
|
|
155
|
-
return_value=MagicMock(call=mock_borrow_balance_call)
|
|
156
|
-
)
|
|
157
|
-
mock_mtoken.functions.borrow = MagicMock(
|
|
158
|
-
return_value=MagicMock(
|
|
159
|
-
call=AsyncMock(return_value=0),
|
|
160
|
-
_encode_transaction_data=MagicMock(return_value="0x1234"),
|
|
161
|
-
build_transaction=AsyncMock(return_value={"data": "0x1234"}),
|
|
162
|
-
)
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
mock_web3 = MagicMock()
|
|
166
|
-
mock_web3.eth.contract = MagicMock(return_value=mock_mtoken)
|
|
167
|
-
|
|
168
|
-
@asynccontextmanager
|
|
169
|
-
async def mock_web3_ctx(_chain_id):
|
|
170
|
-
yield mock_web3
|
|
171
|
-
|
|
172
179
|
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
173
180
|
|
|
174
181
|
with (
|
|
175
182
|
patch(
|
|
176
|
-
"wayfinder_paths.adapters.moonwell_adapter.adapter.
|
|
177
|
-
|
|
178
|
-
),
|
|
179
|
-
patch
|
|
183
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
|
|
184
|
+
new_callable=AsyncMock,
|
|
185
|
+
) as mock_encode,
|
|
186
|
+
patch(
|
|
187
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
|
|
188
|
+
new_callable=AsyncMock,
|
|
189
|
+
) as mock_send,
|
|
180
190
|
):
|
|
181
|
-
|
|
191
|
+
mock_encode.return_value = {"data": "0x1234", "to": MOONWELL_M_USDC}
|
|
192
|
+
mock_send.return_value = mock_tx_hash
|
|
182
193
|
success, result = await adapter.borrow(
|
|
183
|
-
mtoken=
|
|
194
|
+
mtoken=MOONWELL_M_USDC,
|
|
184
195
|
amount=10**6,
|
|
185
196
|
)
|
|
186
197
|
|
|
@@ -191,24 +202,26 @@ class TestMoonwellAdapter:
|
|
|
191
202
|
async def test_repay(self, adapter):
|
|
192
203
|
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
193
204
|
with (
|
|
194
|
-
patch
|
|
195
|
-
adapter
|
|
205
|
+
patch(
|
|
206
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.ensure_allowance",
|
|
207
|
+
new_callable=AsyncMock,
|
|
196
208
|
) as mock_allowance,
|
|
197
|
-
patch
|
|
198
|
-
adapter
|
|
209
|
+
patch(
|
|
210
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
|
|
211
|
+
new_callable=AsyncMock,
|
|
199
212
|
) as mock_encode,
|
|
200
|
-
patch
|
|
213
|
+
patch(
|
|
214
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
|
|
215
|
+
new_callable=AsyncMock,
|
|
216
|
+
) as mock_send,
|
|
201
217
|
):
|
|
202
218
|
mock_allowance.return_value = (True, {})
|
|
203
|
-
mock_encode.return_value = {
|
|
204
|
-
|
|
205
|
-
"to": MOONWELL_DEFAULTS["m_usdc"],
|
|
206
|
-
}
|
|
207
|
-
mock_send.return_value = (True, mock_tx_hash)
|
|
219
|
+
mock_encode.return_value = {"data": "0x1234", "to": MOONWELL_M_USDC}
|
|
220
|
+
mock_send.return_value = mock_tx_hash
|
|
208
221
|
|
|
209
222
|
success, result = await adapter.repay(
|
|
210
|
-
mtoken=
|
|
211
|
-
underlying_token=
|
|
223
|
+
mtoken=MOONWELL_M_USDC,
|
|
224
|
+
underlying_token=BASE_USDC,
|
|
212
225
|
amount=10**6,
|
|
213
226
|
)
|
|
214
227
|
|
|
@@ -217,17 +230,10 @@ class TestMoonwellAdapter:
|
|
|
217
230
|
|
|
218
231
|
@pytest.mark.asyncio
|
|
219
232
|
async def test_set_collateral(self, adapter):
|
|
220
|
-
# Mock comptroller contract for verification
|
|
221
233
|
mock_comptroller = MagicMock()
|
|
222
234
|
mock_comptroller.functions.checkMembership = MagicMock(
|
|
223
235
|
return_value=MagicMock(call=AsyncMock(return_value=True))
|
|
224
236
|
)
|
|
225
|
-
mock_comptroller.functions.enterMarkets = MagicMock(
|
|
226
|
-
return_value=MagicMock(
|
|
227
|
-
_encode_transaction_data=MagicMock(return_value="0x1234"),
|
|
228
|
-
build_transaction=AsyncMock(return_value={"data": "0x1234"}),
|
|
229
|
-
)
|
|
230
|
-
)
|
|
231
237
|
|
|
232
238
|
mock_web3 = MagicMock()
|
|
233
239
|
mock_web3.eth.contract = MagicMock(return_value=mock_comptroller)
|
|
@@ -239,15 +245,23 @@ class TestMoonwellAdapter:
|
|
|
239
245
|
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
240
246
|
|
|
241
247
|
with (
|
|
248
|
+
patch(
|
|
249
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
|
|
250
|
+
new_callable=AsyncMock,
|
|
251
|
+
) as mock_encode,
|
|
252
|
+
patch(
|
|
253
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
|
|
254
|
+
new_callable=AsyncMock,
|
|
255
|
+
) as mock_send,
|
|
242
256
|
patch(
|
|
243
257
|
"wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
|
|
244
258
|
mock_web3_ctx,
|
|
245
259
|
),
|
|
246
|
-
patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
|
|
247
260
|
):
|
|
248
|
-
|
|
261
|
+
mock_encode.return_value = {"data": "0x1234", "to": MOONWELL_M_WSTETH}
|
|
262
|
+
mock_send.return_value = mock_tx_hash
|
|
249
263
|
success, result = await adapter.set_collateral(
|
|
250
|
-
mtoken=
|
|
264
|
+
mtoken=MOONWELL_M_WSTETH,
|
|
251
265
|
)
|
|
252
266
|
|
|
253
267
|
assert success is True
|
|
@@ -270,7 +284,7 @@ class TestMoonwellAdapter:
|
|
|
270
284
|
)
|
|
271
285
|
|
|
272
286
|
def mock_contract(address, abi):
|
|
273
|
-
if address.lower() ==
|
|
287
|
+
if address.lower() == MOONWELL_REWARD_DISTRIBUTOR.lower():
|
|
274
288
|
return mock_reward_contract
|
|
275
289
|
return mock_comptroller
|
|
276
290
|
|
|
@@ -308,7 +322,7 @@ class TestMoonwellAdapter:
|
|
|
308
322
|
)
|
|
309
323
|
|
|
310
324
|
def mock_contract(address, abi):
|
|
311
|
-
if address.lower() ==
|
|
325
|
+
if address.lower() == MOONWELL_REWARD_DISTRIBUTOR.lower():
|
|
312
326
|
return mock_reward
|
|
313
327
|
return mock_mtoken
|
|
314
328
|
|
|
@@ -323,7 +337,7 @@ class TestMoonwellAdapter:
|
|
|
323
337
|
"wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
|
|
324
338
|
mock_web3_ctx,
|
|
325
339
|
):
|
|
326
|
-
success, result = await adapter.get_pos(mtoken=
|
|
340
|
+
success, result = await adapter.get_pos(mtoken=MOONWELL_M_USDC)
|
|
327
341
|
|
|
328
342
|
assert success
|
|
329
343
|
assert "mtoken_balance" in result
|
|
@@ -336,7 +350,7 @@ class TestMoonwellAdapter:
|
|
|
336
350
|
@pytest.mark.asyncio
|
|
337
351
|
async def test_get_collateral_factor_success(self, adapter):
|
|
338
352
|
# Clear cache to ensure fresh test
|
|
339
|
-
adapter.
|
|
353
|
+
await adapter._cache.clear()
|
|
340
354
|
|
|
341
355
|
# Mock contract calls - returns (isListed, collateralFactorMantissa)
|
|
342
356
|
mock_contract = MagicMock()
|
|
@@ -357,7 +371,7 @@ class TestMoonwellAdapter:
|
|
|
357
371
|
mock_web3_ctx,
|
|
358
372
|
):
|
|
359
373
|
success, result = await adapter.get_collateral_factor(
|
|
360
|
-
mtoken=
|
|
374
|
+
mtoken=MOONWELL_M_WSTETH
|
|
361
375
|
)
|
|
362
376
|
|
|
363
377
|
assert success
|
|
@@ -390,7 +404,7 @@ class TestMoonwellAdapter:
|
|
|
390
404
|
@pytest.mark.asyncio
|
|
391
405
|
async def test_get_collateral_factor_caching(self, adapter):
|
|
392
406
|
# Clear cache to ensure fresh test
|
|
393
|
-
adapter.
|
|
407
|
+
await adapter._cache.clear()
|
|
394
408
|
|
|
395
409
|
call_count = 0
|
|
396
410
|
|
|
@@ -410,7 +424,7 @@ class TestMoonwellAdapter:
|
|
|
410
424
|
async def mock_web3_ctx(_chain_id):
|
|
411
425
|
yield mock_web3
|
|
412
426
|
|
|
413
|
-
mtoken =
|
|
427
|
+
mtoken = MOONWELL_M_WSTETH
|
|
414
428
|
|
|
415
429
|
with patch(
|
|
416
430
|
"wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
|
|
@@ -435,67 +449,11 @@ class TestMoonwellAdapter:
|
|
|
435
449
|
assert call_count == 1
|
|
436
450
|
|
|
437
451
|
success4, result4 = await adapter.get_collateral_factor(
|
|
438
|
-
mtoken=
|
|
452
|
+
mtoken=MOONWELL_M_USDC
|
|
439
453
|
)
|
|
440
454
|
assert success4 is True
|
|
441
455
|
assert call_count == 2
|
|
442
456
|
|
|
443
|
-
@pytest.mark.asyncio
|
|
444
|
-
async def test_get_collateral_factor_cache_expiry(self, adapter):
|
|
445
|
-
import time
|
|
446
|
-
|
|
447
|
-
from wayfinder_paths.adapters.moonwell_adapter import adapter as adapter_module
|
|
448
|
-
|
|
449
|
-
# Clear cache to ensure fresh test
|
|
450
|
-
adapter._cf_cache.clear()
|
|
451
|
-
|
|
452
|
-
original_ttl = adapter_module.CF_CACHE_TTL
|
|
453
|
-
|
|
454
|
-
try:
|
|
455
|
-
adapter_module.CF_CACHE_TTL = 0.1
|
|
456
|
-
|
|
457
|
-
call_count = 0
|
|
458
|
-
|
|
459
|
-
async def mock_markets_call(**kwargs):
|
|
460
|
-
nonlocal call_count
|
|
461
|
-
call_count += 1
|
|
462
|
-
return (True, int(0.75 * MANTISSA))
|
|
463
|
-
|
|
464
|
-
mock_contract = MagicMock()
|
|
465
|
-
mock_contract.functions.markets = MagicMock(
|
|
466
|
-
return_value=MagicMock(call=mock_markets_call)
|
|
467
|
-
)
|
|
468
|
-
mock_web3 = MagicMock()
|
|
469
|
-
mock_web3.eth.contract = MagicMock(return_value=mock_contract)
|
|
470
|
-
|
|
471
|
-
@asynccontextmanager
|
|
472
|
-
async def mock_web3_ctx(_chain_id):
|
|
473
|
-
yield mock_web3
|
|
474
|
-
|
|
475
|
-
mtoken = MOONWELL_DEFAULTS["m_wsteth"]
|
|
476
|
-
|
|
477
|
-
with patch(
|
|
478
|
-
"wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
|
|
479
|
-
mock_web3_ctx,
|
|
480
|
-
):
|
|
481
|
-
# First call
|
|
482
|
-
await adapter.get_collateral_factor(mtoken=mtoken)
|
|
483
|
-
assert call_count == 1
|
|
484
|
-
|
|
485
|
-
# Immediate second call should use cache
|
|
486
|
-
await adapter.get_collateral_factor(mtoken=mtoken)
|
|
487
|
-
assert call_count == 1
|
|
488
|
-
|
|
489
|
-
# Wait for cache to expire
|
|
490
|
-
time.sleep(0.15)
|
|
491
|
-
|
|
492
|
-
await adapter.get_collateral_factor(mtoken=mtoken)
|
|
493
|
-
assert call_count == 2
|
|
494
|
-
|
|
495
|
-
finally:
|
|
496
|
-
# Restore original TTL
|
|
497
|
-
adapter_module.CF_CACHE_TTL = original_ttl
|
|
498
|
-
|
|
499
457
|
@pytest.mark.asyncio
|
|
500
458
|
async def test_get_apy_supply(self, adapter):
|
|
501
459
|
rate_per_second = int(1.5e9)
|
|
@@ -516,7 +474,7 @@ class TestMoonwellAdapter:
|
|
|
516
474
|
)
|
|
517
475
|
|
|
518
476
|
def mock_contract(address, abi):
|
|
519
|
-
if address.lower() ==
|
|
477
|
+
if address.lower() == MOONWELL_REWARD_DISTRIBUTOR.lower():
|
|
520
478
|
return mock_reward
|
|
521
479
|
return mock_mtoken
|
|
522
480
|
|
|
@@ -532,7 +490,7 @@ class TestMoonwellAdapter:
|
|
|
532
490
|
mock_web3_ctx,
|
|
533
491
|
):
|
|
534
492
|
success, result = await adapter.get_apy(
|
|
535
|
-
mtoken=
|
|
493
|
+
mtoken=MOONWELL_M_USDC,
|
|
536
494
|
apy_type="supply",
|
|
537
495
|
include_rewards=False,
|
|
538
496
|
)
|
|
@@ -561,7 +519,7 @@ class TestMoonwellAdapter:
|
|
|
561
519
|
)
|
|
562
520
|
|
|
563
521
|
def mock_contract(address, abi):
|
|
564
|
-
if address.lower() ==
|
|
522
|
+
if address.lower() == MOONWELL_REWARD_DISTRIBUTOR.lower():
|
|
565
523
|
return mock_reward
|
|
566
524
|
return mock_mtoken
|
|
567
525
|
|
|
@@ -577,7 +535,7 @@ class TestMoonwellAdapter:
|
|
|
577
535
|
mock_web3_ctx,
|
|
578
536
|
):
|
|
579
537
|
success, result = await adapter.get_apy(
|
|
580
|
-
mtoken=
|
|
538
|
+
mtoken=MOONWELL_M_USDC,
|
|
581
539
|
apy_type="borrow",
|
|
582
540
|
include_rewards=False,
|
|
583
541
|
)
|
|
@@ -632,63 +590,28 @@ class TestMoonwellAdapter:
|
|
|
632
590
|
|
|
633
591
|
@pytest.mark.asyncio
|
|
634
592
|
async def test_wrap_eth(self, adapter):
|
|
635
|
-
mock_contract = MagicMock()
|
|
636
|
-
mock_contract.functions.deposit = MagicMock(
|
|
637
|
-
return_value=MagicMock(
|
|
638
|
-
build_transaction=AsyncMock(return_value={"data": "0x1234"})
|
|
639
|
-
)
|
|
640
|
-
)
|
|
641
|
-
mock_web3 = MagicMock()
|
|
642
|
-
mock_web3.eth.contract = MagicMock(return_value=mock_contract)
|
|
643
|
-
|
|
644
|
-
@asynccontextmanager
|
|
645
|
-
async def mock_web3_ctx(_chain_id):
|
|
646
|
-
yield mock_web3
|
|
647
|
-
|
|
648
593
|
mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
|
|
649
594
|
|
|
650
595
|
with (
|
|
651
596
|
patch(
|
|
652
|
-
"wayfinder_paths.adapters.moonwell_adapter.adapter.
|
|
653
|
-
|
|
654
|
-
),
|
|
655
|
-
patch
|
|
597
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
|
|
598
|
+
new_callable=AsyncMock,
|
|
599
|
+
) as mock_encode,
|
|
600
|
+
patch(
|
|
601
|
+
"wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
|
|
602
|
+
new_callable=AsyncMock,
|
|
603
|
+
) as mock_send,
|
|
656
604
|
):
|
|
657
|
-
|
|
605
|
+
mock_encode.return_value = {"data": "0x1234", "to": BASE_WETH}
|
|
606
|
+
mock_send.return_value = mock_tx_hash
|
|
658
607
|
success, result = await adapter.wrap_eth(amount=10**18)
|
|
659
608
|
|
|
660
609
|
assert success
|
|
661
610
|
assert result == mock_tx_hash
|
|
662
611
|
|
|
663
612
|
def test_strategy_address_missing(self):
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
with pytest.raises(ValueError, match="strategy_wallet"):
|
|
667
|
-
adapter._strategy_address()
|
|
668
|
-
|
|
669
|
-
def test_checksum_missing_address(self, adapter):
|
|
670
|
-
with pytest.raises(ValueError, match="Missing required"):
|
|
671
|
-
adapter._checksum(None)
|
|
672
|
-
|
|
673
|
-
def test_config_override(self):
|
|
674
|
-
custom_comptroller = "0x1111111111111111111111111111111111111111"
|
|
675
|
-
custom_well = "0x2222222222222222222222222222222222222222"
|
|
676
|
-
config = {
|
|
677
|
-
"strategy_wallet": {
|
|
678
|
-
"address": "0x1234567890123456789012345678901234567890"
|
|
679
|
-
},
|
|
680
|
-
"moonwell_adapter": {
|
|
681
|
-
"comptroller": custom_comptroller,
|
|
682
|
-
"well_token": custom_well,
|
|
683
|
-
"chain_id": 1,
|
|
684
|
-
},
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
adapter = MoonwellAdapter(config=config)
|
|
688
|
-
|
|
689
|
-
assert adapter.comptroller_address.lower() == custom_comptroller.lower()
|
|
690
|
-
assert adapter.well_token.lower() == custom_well.lower()
|
|
691
|
-
assert adapter.chain_id == 1
|
|
613
|
+
with pytest.raises(KeyError):
|
|
614
|
+
MoonwellAdapter(config={})
|
|
692
615
|
|
|
693
616
|
@pytest.mark.asyncio
|
|
694
617
|
async def test_max_withdrawable_mtoken_zero_balance(self, adapter):
|
|
@@ -707,9 +630,7 @@ class TestMoonwellAdapter:
|
|
|
707
630
|
return_value=MagicMock(call=AsyncMock(return_value=8))
|
|
708
631
|
)
|
|
709
632
|
mock_mtoken.functions.underlying = MagicMock(
|
|
710
|
-
return_value=MagicMock(
|
|
711
|
-
call=AsyncMock(return_value=MOONWELL_DEFAULTS["usdc"])
|
|
712
|
-
)
|
|
633
|
+
return_value=MagicMock(call=AsyncMock(return_value=BASE_USDC))
|
|
713
634
|
)
|
|
714
635
|
|
|
715
636
|
mock_web3 = MagicMock()
|
|
@@ -724,7 +645,7 @@ class TestMoonwellAdapter:
|
|
|
724
645
|
mock_web3_ctx,
|
|
725
646
|
):
|
|
726
647
|
success, result = await adapter.max_withdrawable_mtoken(
|
|
727
|
-
mtoken=
|
|
648
|
+
mtoken=MOONWELL_M_USDC
|
|
728
649
|
)
|
|
729
650
|
|
|
730
651
|
assert success
|