From ab65fbb86960d4e3f59e3fb85ef65f23c5dc078d Mon Sep 17 00:00:00 2001 From: Oleg <40476427+amfet42@users.noreply.github.com> Date: Mon, 3 Jun 2024 20:07:58 +0300 Subject: [PATCH] add tests for leverage --- tests_leverage/test_v2/conftest.py | 121 +++++++++++++++--- tests_leverage/test_v2/constants.py | 31 +---- tests_leverage/test_v2/settings.py | 4 +- tests_leverage/test_v2/tests/test_leverage.py | 119 +++++++++++++++-- tests_leverage/test_v2/utils.py | 34 ++++- 5 files changed, 246 insertions(+), 63 deletions(-) diff --git a/tests_leverage/test_v2/conftest.py b/tests_leverage/test_v2/conftest.py index dc83d8e3..3b1b6878 100644 --- a/tests_leverage/test_v2/conftest.py +++ b/tests_leverage/test_v2/conftest.py @@ -1,45 +1,48 @@ import boa import pytest -from .constants import COLLATERALS, CONTROLLERS, CRVUSD, FACTORIES, ROUTER -from .constants import frxETH as frxETH_address -from .constants import stETH as stETH_address +from .constants import ( + COLLATERALS, + CRVUSD, + FACTORIES, + ONE_WAY_LENDING_FACTORY, + ORACLE_POOLS, + ROUTER, +) from .settings import WEB3_PROVIDER_URL from .utils import Router1inch, get_contract_from_explorer def pytest_generate_tests(metafunc): if "collateral_info" in metafunc.fixturenames: - collaterals = [{"address": v, "controller": CONTROLLERS[k], "symbol": k} for k, v in COLLATERALS.items()] + collaterals = [ + {"address": v, "symbol": k, "oracle": ORACLE_POOLS[k]} + for k, v in COLLATERALS.items() + ] metafunc.parametrize( "collateral_info", collaterals, - ids=[f"(ControllerInfo={k['address']})" for k in collaterals], + ids=[f"(Collateral={k['address']})" for k in collaterals], ) @pytest.fixture(autouse=True) def forked_chain(): - assert WEB3_PROVIDER_URL is not None, "Provider url is not set, add WEB3_PROVIDER_URL param to env" + assert ( + WEB3_PROVIDER_URL is not None + ), "Provider url is not set, add WEB3_PROVIDER_URL param to env" boa.env.fork(url=WEB3_PROVIDER_URL) @pytest.fixture() -def alice(collateral_info): - user = boa.env.generate_address() - boa.env.set_balance(user, 200 * 10**18) # 200 eth +def admin(): + return "0xbabe61887f1de2713c6f97e567623453d3C79f67" - with boa.env.prank(user): - if collateral_info["symbol"] == "sfrxETH": - swap = get_contract_from_explorer("0xa1F8A6807c402E4A15ef4EBa36528A3FED24E577") - swap.exchange(0, 1, 2 * 10**18, 1 * 10**18, value=2 * 10**18) - sfrxETH = get_contract_from_explorer(collateral_info["address"]) - frxETH = get_contract_from_explorer(frxETH_address) - frxETH.approve(sfrxETH.address, 10**18) - sfrxETH.deposit(10**18, user) - return user +@pytest.fixture(scope="session") +def amm_interface(): + return boa.load_partial("contracts/AMM.vy") @pytest.fixture(scope="session") @@ -47,6 +50,11 @@ def controller_interface(): return boa.load_partial("contracts/Controller.vy") +@pytest.fixture(scope="session") +def vault_impl(): + return boa.load_partial("contracts/lending/Vault.vy") + + @pytest.fixture() def stablecoin(): return boa.load_partial("contracts/Stablecoin.vy").at(CRVUSD) @@ -57,9 +65,82 @@ def collateral(collateral_info): return get_contract_from_explorer(collateral_info["address"]) +@pytest.fixture(scope="session") +def factory(): + return boa.load_partial("contracts/lending/OneWayLendingFactory.vy").at( + ONE_WAY_LENDING_FACTORY + ) + + +@pytest.fixture() +def oracle_pool(collateral_info): + return collateral_info["oracle"] + + +@pytest.fixture() +def vault(admin, factory, collateral, stablecoin, vault_impl, oracle_pool): + boa.env.set_balance(admin, 200 * 10**18) # 200 eth + + A = 70 + fee = int(0.002 * 1e18) + borrowing_discount = int(0.07 * 1e18) + liquidation_discount = int(0.04 * 1e18) + min_borrow_rate = 5 * 10**15 // (365 * 86400) # 0.5% + max_borrow_rate = 80 * 10**16 // (365 * 86400) # 80% + + with boa.env.prank(admin): + vault = vault_impl.at( + factory.create_from_pool( + stablecoin, + collateral, + A, + fee, + borrowing_discount, + liquidation_discount, + oracle_pool, + "test", + min_borrow_rate, + max_borrow_rate, + ) + ) + + swap = get_contract_from_explorer("0x4eBdF703948ddCEA3B11f675B4D1Fba9d2414A14") + + weth = get_contract_from_explorer("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2") + weth.deposit(value=20 * 10**18) + weth.approve(swap.address, 20 * 10**18) + swap.exchange(1, 0, 20 * 10**18, 0) + + stablecoin.approve(vault.address, 50 * 1000 * 10**18) + vault.deposit(50 * 1000 * 10**18) + + return vault + + @pytest.fixture() -def controller(collateral_info, controller_interface): - return controller_interface.at(collateral_info["controller"]) +def controller(vault, controller_interface): + return controller_interface.at(vault.controller()) + + +@pytest.fixture() +def user_collateral_amount(collateral): + return 1 * 10 ** collateral.decimals() + + +@pytest.fixture() +def alice(collateral_info, user_collateral_amount, controller): + user = boa.env.generate_address() + boa.env.set_balance(user, 200 * 10**18) # 200 eth + + with boa.env.prank(user): + if collateral_info["symbol"] == "WETH": + weth = get_contract_from_explorer( + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ) + weth.deposit(value=user_collateral_amount) + weth.approve(controller.AMM(), user_collateral_amount) + + return user @pytest.fixture() diff --git a/tests_leverage/test_v2/constants.py b/tests_leverage/test_v2/constants.py index 2bff4fdb..1c26a246 100644 --- a/tests_leverage/test_v2/constants.py +++ b/tests_leverage/test_v2/constants.py @@ -1,34 +1,15 @@ CRVUSD = "0xf939e0a03fb07f59a73314e73794be0e57ac1b4e" -FACTORIES = [ - "0xC9332fdCB1C491Dcc683bAe86Fe3cb70360738BC", # crvUSD -] +ONE_WAY_LENDING_FACTORY = "0xeA6876DDE9e3467564acBeE1Ed5bac88783205E0" -COLLATERALS = { - "sfrxETH": "0xac3E018457B222d93114458476f3E3416Abbe38F", - # "wstETH": "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", - # "WBTC": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", - # "WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - # "sfrxETH2": "0xac3E018457B222d93114458476f3E3416Abbe38F", - # "tBTC": "0x18084fba666a33d37592fa2633fd49a74dd93a88", -} +FACTORIES = [ONE_WAY_LENDING_FACTORY] -CONTROLLERS = { - "sfrxETH": "0x8472A9A7632b173c8Cf3a86D3afec50c35548e76", - "wstETH": "0x100dAa78fC509Db39Ef7D04DE0c1ABD299f4C6CE", - "WBTC": "0x4e59541306910aD6dC1daC0AC9dFB29bD9F15c67", - "WETH": "0xA920De414eA4Ab66b97dA1bFE9e6EcA7d4219635", - "sfrxETH2": "0xEC0820EfafC41D8943EE8dE495fC9Ba8495B15cf", - "tBTC": "0x1C91da0223c763d2e0173243eAdaA0A2ea47E704", +COLLATERALS = { + "WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", } -LLAMMAS = { - "sfrxETH": "0x136e783846ef68c8bd00a3369f787df8d683a696", - "wstETH": "0x37417b2238aa52d0dd2d6252d989e728e8f706e4", - "WBTC": "0xe0438eb3703bf871e31ce639bd351109c88666ea", - "WETH": "0x1681195c176239ac5e72d9aebacf5b2492e0c4ee", - "sfrxETH2": "0xfa96ad0a9e64261db86950e2da362f5572c5c6fd", - "tBTC": "0xf9bd9da2427a50908c4c6d1599d8e62837c2bcb0", +ORACLE_POOLS = { + "WETH": "0x4eBdF703948ddCEA3B11f675B4D1Fba9d2414A14", } ROUTER = "0x111111125421cA6dc452d289314280a0f8842A65" # 1inch v6 diff --git a/tests_leverage/test_v2/settings.py b/tests_leverage/test_v2/settings.py index b463389b..c40cf1fb 100644 --- a/tests_leverage/test_v2/settings.py +++ b/tests_leverage/test_v2/settings.py @@ -1,6 +1,6 @@ import os -ROUTER_1INCH_TOKEN = os.getenv("ROUTER_TOKEN") +ROUTER_1INCH_TOKEN = os.getenv("ROUTER_TOKEN", "Aa9VksVszqXDod6Smp2TQGsJ6OkHHnzJ") WEB3_PROVIDER_URL = os.getenv("WEB3_PROVIDER_URL", "http://127.0.0.1:8545") EXPLORER_URL = os.getenv("EXPLORER_URL", "https://api.etherscan.io/api") -EXPLORER_TOKEN = os.getenv("EXPLORER_TOKEN") +EXPLORER_TOKEN = os.getenv("EXPLORER_TOKEN", "SSY47SFCQFICC6WWHPJP6JR56SKWXTSR9B") diff --git a/tests_leverage/test_v2/tests/test_leverage.py b/tests_leverage/test_v2/tests/test_leverage.py index 4d0d160d..c115b1ef 100644 --- a/tests_leverage/test_v2/tests/test_leverage.py +++ b/tests_leverage/test_v2/tests/test_leverage.py @@ -1,16 +1,111 @@ +import pytest + + class TestLeverage: - class TestCreateLoan: - def test_max_borrowable(self, alice, collateral, controller, leverage_zap_1inch, router_api_1inch): - N = 4 - max_collateral = 0 - collateral_decimals = 10 ** collateral.decimals() - p_avg = 10**18 * collateral_decimals // router_api_1inch.get_rate(collateral.address, 1 * 10**18) - balance = 1 * 10**18 if collateral.symbol() == "WETH" else collateral.balanceOf(alice) - max_borrowable = leverage_zap_1inch.max_borrowable(controller.address, balance, max_collateral, N, p_avg) + def get_max_borrowable( + self, + collateral, + stablecoin, + controller, + user_collateral_amount, + leverage_zap_1inch, + router_api_1inch, + N, + ): + collateral_decimals = 10 ** collateral.decimals() + stablecoin_decimals = 10 ** stablecoin.decimals() + + router_rate = router_api_1inch.get_rate_to_crvusd( + collateral.address, user_collateral_amount * 10 + ) + avg_rate_initial = ( + router_rate + * collateral_decimals + / (user_collateral_amount * 10 * stablecoin_decimals) + ) + p_avg_initial = int(avg_rate_initial * 10**18) + lev_collateral_amount = 0 + max_b = leverage_zap_1inch.max_borrowable( + controller.address, + user_collateral_amount, + lev_collateral_amount, + N, + p_avg_initial, + ) + + col_from_max_b = router_api_1inch.get_rate_from_crvusd( + collateral.address, max_b + ) + avg_rate = max_b * collateral_decimals / (col_from_max_b * stablecoin_decimals) + p_avg = int(avg_rate * 10**18) + lev_collateral_amount = int(max_b / avg_rate) + max_b = leverage_zap_1inch.max_borrowable( + controller.address, user_collateral_amount, lev_collateral_amount, N, p_avg + ) + max_lev = max_b / avg_rate / collateral_decimals + + return max_b, lev_collateral_amount, max_lev + + @pytest.mark.parametrize('N', (4, 15, 50)) + def test_max_borrowable( + self, + collateral, + stablecoin, + controller, + user_collateral_amount, + leverage_zap_1inch, + router_api_1inch, + N + ): + max_b, max_c, max_lev = self.get_max_borrowable( + collateral, + stablecoin, + controller, + user_collateral_amount, + leverage_zap_1inch, + router_api_1inch, + N, + ) + + assert max_b > 0 + assert max_c > 0 + + if N == 4: + assert max_lev > 5 + elif N == 15: + assert max_lev > 3 + elif N == 50: + assert max_lev > 1.5 - max_collateral = router_api_1inch.get_rate(collateral.address, max_borrowable) - p_avg = max_borrowable * collateral_decimals // max_collateral - max_borrowable = leverage_zap_1inch.max_borrowable(controller.address, balance, max_collateral, N, p_avg) + def test_max_borrowable_create_loan( + self, + collateral, + stablecoin, + controller, + user_collateral_amount, + leverage_zap_1inch, + router_api_1inch, + ): + N = 4 + max_b, max_c, max_lev = self.get_max_borrowable( + collateral, + stablecoin, + controller, + user_collateral_amount, + leverage_zap_1inch, + router_api_1inch, + N, + ) + calldata = router_api_1inch.get_calldata( + collateral.address, stablecoin.address, max_b, leverage_zap_1inch.address + ) - assert max_borrowable > 0 + controller.create_loan_extended( + user_collateral_amount, + max_b, + N, + leverage_zap_1inch.address, + [0, user_collateral_amount, max_b, 0, 0, 0], + bytes.fromhex(calldata[2:]), + ) diff --git a/tests_leverage/test_v2/utils.py b/tests_leverage/test_v2/utils.py index ed2f7bdc..9d707475 100644 --- a/tests_leverage/test_v2/utils.py +++ b/tests_leverage/test_v2/utils.py @@ -1,3 +1,5 @@ +import time + import boa import requests @@ -6,17 +8,41 @@ def get_contract_from_explorer(address: str): - return boa.from_etherscan(address=address, name=address, uri=EXPLORER_URL, api_key=EXPLORER_TOKEN) + return boa.from_etherscan( + address=address, name=address, uri=EXPLORER_URL, api_key=EXPLORER_TOKEN + ) class Router1inch: def __init__(self, chain_id: int): self._chain_id = chain_id self._headers = {"Authorization": f"Bearer {ROUTER_1INCH_TOKEN}"} - self._from = CRVUSD + self._crvusd = CRVUSD - def get_rate(self, _to: str, amount: int): + def get_rate(self, _from: str, _to: str, amount: int): url = "https://api.1inch.dev/swap/v6.0/1/quote" - params = {"src": self._from, "dst": _to, "amount": amount} + params = {"src": _from, "dst": _to, "amount": amount} resp = requests.get(url, headers=self._headers, params=params) + time.sleep(1) return int(resp.json()["dstAmount"]) + + def get_rate_to_crvusd(self, _from: str, amount: int): + return self.get_rate(_from, CRVUSD, amount) + + def get_rate_from_crvusd(self, _to: str, amount: int): + return self.get_rate(CRVUSD, _to, amount) + + def get_calldata(self, _from: str, _to: str, amount: int, user: str) -> str: + url = "https://api.1inch.dev/swap/v6.0/1/swap" + params = { + "src": _from, + "dst": _to, + "amount": amount, + "from": user, + "slippage": 0.1, + "disableEstimate": True, + } + resp = requests.get(url, headers=self._headers, params=params) + + time.sleep(1) + return resp.json()["tx"]["data"]