diff --git a/README.md b/README.md index 23153d6..29e6c2e 100644 --- a/README.md +++ b/README.md @@ -856,4 +856,504 @@ import lending from "@curvefi/lending-api"; // loss_pct: '5.043158793049750311' // } })() -``` \ No newline at end of file +``` + +### Leverage (createLoan, borrowMore, repay) +```ts +(async () => { + await lending.init('JsonRpc', {}, {}, API_KEY_1INCH); + await lending.oneWayfactory.fetchMarkets(); + + const oneWayMarket = lending.getOneWayMarket('one-way-market-0'); + console.log(oneWayMarket.collateral_token, oneWayMarket.borrowed_token); + // { + // address: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', + // decimals: 18, + // name: 'Wrapped Ether', + // symbol: 'WETH' + // } + // + // { + // address: '0x498bf2b1e120fed3ad3d42ea2165e9b73f99c1e5', + // decimals: 18, + // name: 'Curve.Fi USD Stablecoin', + // symbol: 'crvUSD' + // } + console.log(await oneWayMarket.wallet.balances()); + // { + // collateral: '100.0', + // borrowed: '2000000.0', + // vaultShares: '0.0', + // gauge: '0' + // } + + + // - Create Loan - + + // Creates leveraged position (userCollateral + collateralFromUserBorrowed + leverage_collateral) + // ^ + // | + // userCollateral | debt debt + userBorrowed + // user ---> controller ----> leverage_zap ----> router + // | ^ | ^ ^ | + // | |__________________| | |___________________| + // | leverageCollateral + collateralFromUserBorrowed + // |_____________________________________| + // userBorrowed + + let userCollateral = 1; + let userBorrowed = 1000; + let debt = 2000; + const range = 10; + const slippage = 0.5; // % + await oneWayMarket.leverage.maxLeverage(range); + // 7.4728229145282742179 + await oneWayMarket.leverage.createLoanMaxRecv(userCollateral, userBorrowed, range); + // { + // maxDebt: '26089.494406081862861214', + // maxTotalCollateral: '9.539182089833411347', + // userCollateral: '1', + // collateralFromUserBorrowed: '0.315221168834966496', + // collateralFromMaxDebt: '8.223960920998444851', + // maxLeverage: '7.25291100528992828612', + // avgPrice: '3172.3757757003568790858' + // } + await oneWayMarket.leverage.createLoanExpectedCollateral(userCollateral, userBorrowed, debt); + // { + // totalCollateral: '1.946422996710829', + // userCollateral: '1.0', + // collateralFromUserBorrowed: '0.315474332236942984', + // collateralFromDebt: '0.630948664473886', + // leverage: '1.4796358613861877' + // avgPrice: '3169.8299919022623523421' + // } + await oneWayMarket.leverage.createLoanMaxRange(userCollateral, userBorrowed, debt); + // 50 + await oneWayMarket.leverage.createLoanBands(userCollateral, userBorrowed, debt, range); + // [ 76, 67 ] + await oneWayMarket.leverage.createLoanPrices(userCollateral, userBorrowed, debt, range); + // [ '1027.977701011670136614', '1187.061409925215211173' ] + await oneWayMarket.leverage.createLoanHealth(userCollateral, userBorrowed, debt, range); + // 195.8994783042570637 + await oneWayMarket.leverage.createLoanHealth(userCollateral, userBorrowed, debt, range, false); + // 3.2780908310686365 + await oneWayMarket.leverage.createLoanIsApproved(userCollateral, userBorrowed); + // false + await oneWayMarket.leverage.createLoanApprove(userCollateral, userBorrowed); + // [ + // '0xd5491d9f1e9d8ac84b03867494e35b25efad151c597d2fa4211d7bf5d540c98e', + // '0x93565f37ec5be902a824714a30bddc25cf9cd9ed39b4c0e8de61fab44af5bc8c' + // ] + await oneWayMarket.leverage.createLoanRoute(userBorrowed, debt, slippage); + // [ + // { + // part: 100, + // hops: [ + // [ + // { + // name: 'ARBITRUM_CURVE_STABLE_NG', + // part: 100, + // fromTokenAddress: '0x498bf2b1e120fed3ad3d42ea2165e9b73f99c1e5', + // toTokenAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9' + // } + // ], + // [ + // { + // name: 'ARBITRUM_UNISWAP_V3', + // part: 100, + // fromTokenAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', + // toTokenAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1' + // } + // ] + // ] + // } + // ] + + await oneWayMarket.leverage.createLoan(userCollateral, userBorrowed, debt, range); + // 0xeb1b7a92bcb02598f00dc8bbfe8fa3a554e7a2b1ca764e0ee45e2bf583edf731 + + await oneWayMarket.wallet.balances(); + // { + // collateral: '99.0', + // borrowed: '599000.0', + // vaultShares: '1400000000.0', + // gauge: '0' + // } + await oneWayMarket.userState(); + // { + // collateral: '1.945616160868693648', + // borrowed: '0.0', + // debt: '2000.0', + // N: '10' + // } + await oneWayMarket.userBands(); + // [ 76, 67 ] + await oneWayMarket.userPrices(); + // [ '1027.977718614028011906', '1187.061430251609195098' ] + await oneWayMarket.userHealth(); + // 195.8372633833293605 + await oneWayMarket.userHealth(false); + // 3.2518122092914609 + + + // - Borrow More - + + // Updates leveraged position (dCollateral = userCollateral + collateralFromUserBorrowed + leverageCollateral) + // ^ + // | + // userCollateral | dDebt dDebt + userBorrowed + // user ---> controller ----> leverage_zap ----> router + // | ^ | ^ ^ | + // | |__________________| | |___________________| + // | leverageCollateral + collateralFromUSerBorrowed + // |_____________________________________| + // userBorrowed + + userCollateral = 2; + userBorrowed = 2000; + debt = 10000; + await oneWayMarket.leverage.borrowMoreMaxRecv(userCollateral, userBorrowed); + // { + // maxDebt: '76182.8497941193262889', + // maxTotalCollateral: '26.639775583730298462', + // userCollateral: '2', + // collateralFromUserBorrowed: '1.677318306610359627', + // collateralFromMaxDebt: '22.962457277119938834', + // avgPrice: '3172.55402418338331369083' + // } + await oneWayMarket.leverage.borrowMoreExpectedCollateral(userCollateral, userBorrowed, debt); + // { + // totalCollateral: '5.783452104143246413', + // userCollateral: '2.0', + // collateralFromUserBorrowed: '0.630575350690541071', + // collateralFromDebt: '3.152876753452705342' + // avgPrice: '3171.70659749038129067231' + // } + await oneWayMarket.leverage.borrowMoreBands(userCollateral, userBorrowed, debt); + // [ 47, 38 ] + await oneWayMarket.leverage.borrowMorePrices(userCollateral, userBorrowed, debt); + // [ '1560.282474721398939216', '1801.742501325928269008' ] + await oneWayMarket.leverage.borrowMoreHealth(userCollateral, userBorrowed, debt, true); + // 91.6798951784708552 + await oneWayMarket.leverage.borrowMoreHealth(userCollateral, userBorrowed, debt, false); + // 3.7614279042995641 + await oneWayMarket.leverage.borrowMoreIsApproved(userCollateral, userBorrowed); + // true + await oneWayMarket.leverage.borrowMoreApprove(userCollateral, userBorrowed); + // [] + await oneWayMarket.leverage.borrowMoreRoute(userBorrowed, debt, slippage); + // [ + // { + // part: 50, + // hops: [ + // [ + // { + // name: 'ARBITRUM_CURVE_STABLE_NG', + // part: 100, + // fromTokenAddress: '0x498bf2b1e120fed3ad3d42ea2165e9b73f99c1e5', + // toTokenAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9' + // } + // ], + // [ + // { + // name: 'ARBITRUM_PANCAKESWAP_V3', + // part: 12, + // fromTokenAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', + // toTokenAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1' + // }, + // { + // name: 'ARBITRUM_PANCAKESWAP_V3', + // part: 42, + // fromTokenAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', + // toTokenAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1' + // }, + // { + // name: 'ARBITRUM_UNISWAP_V3', + // part: 46, + // fromTokenAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', + // toTokenAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1' + // } + // ] + // ] + // }, + // { + // part: 50, + // hops: [ + // [ + // { + // name: 'ARBITRUM_CURVE_STABLE_NG', + // part: 100, + // fromTokenAddress: '0x498bf2b1e120fed3ad3d42ea2165e9b73f99c1e5', + // toTokenAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831' + // } + // ], + // [ + // { + // name: 'ARBITRUM_PANCAKESWAP_V3', + // part: 10, + // fromTokenAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', + // toTokenAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1' + // }, + // { + // name: 'ARBITRUM_UNISWAP_V3', + // part: 90, + // fromTokenAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', + // toTokenAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1' + // } + // ] + // ] + // } + // ] + + await oneWayMarket.leverage.borrowMore(userCollateral, userBorrowed, debt, slippage); + // 0x6357dd6ea7250d7adb2344cd9295f8255fd8fbbe85f00120fbcd1ebf139e057c + + await oneWayMarket.wallet.balances(); + // { + // collateral: '97.0', + // borrowed: '597000.0', + // vaultShares: '1400000000.0', + // gauge: '0' + // } + await oneWayMarket.userState(); + // { + // collateral: '7.727839965845165558', + // borrowed: '0.0', + // debt: '12000.000010193901375446', + // N: '10' + // } + await oneWayMarket.userBands(); + // [ 47, 38 ] + await oneWayMarket.userPrices(); + // [ '1560.28248267408177179', '1801.742510509320950242' ] + await oneWayMarket.userHealth(); + // 91.6519475547753288 + await oneWayMarket.userHealth(false); + // 3.7449386373872907 + + + // - Repay - + + + // Deleveraged position (-dDebt = borrowedFromStateCollateral + borrowedFromUSerCollateral + userBorrowed) + // ^ + // | userCollateral + // user ___|__________________________ + // | | + // | | stateCollateral ↓ userCollateral + stateCollateral + // | controller --> leverage_zap --> router + // | ^ | ^ ^ | + // | |______________________| | |___________________| + // | | borrowedFromStateCollateral + // |________________________________| + + // userBorrowed borrowedFromUSerCollateral + + const stateCollateral = 2; + userCollateral = 1; + userBorrowed = 1500; + await oneWayMarket.leverage.repayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed); + // { + // totalBorrowed: '10998.882838599741571472', + // borrowedFromStateCollateral: '6332.588559066494374648', + // borrowedFromUserCollateral: '3166.294279533247196824', + // userBorrowed: '1500' + // avgPrice: '3166.29427953324743125312' + // } + + + await oneWayMarket.leverage.repayIsFull(stateCollateral, userCollateral, userBorrowed); + // false + await oneWayMarket.leverage.repayIsAvailable(stateCollateral, userCollateral, userBorrowed); + // true + await oneWayMarket.leverage.repayBands(stateCollateral, userCollateral, userBorrowed); + // [ 199, 190 ] + await oneWayMarket.leverage.repayPrices(stateCollateral, userCollateral, userBorrowed); + // [ '175.130965754280721633', '202.233191367561902757' ] + await oneWayMarket.leverage.repayHealth(stateCollateral, userCollateral, userBorrowed, true); + // 1699.6097751079226865 + await oneWayMarket.leverage.repayHealth(stateCollateral, userCollateral, userBorrowed, false); + // 3.4560086962806991 + await oneWayMarket.leverage.repayIsApproved(userCollateral, userBorrowed); + // false + await oneWayMarket.leverage.repayApprove(userCollateral, userBorrowed); + // ['0xd8a8d3b3f67395e1a4f4d4f95b041edcaf1c9f7bab5eb8a8a767467678295498'] + await oneWayMarket.leverage.repayRoute(stateCollateral, userCollateral, slippage); + // [ + // { + // part: 10, + // hops: [ + // [ + // { + // name: 'ARBITRUM_CURVE_V2', + // part: 32, + // fromTokenAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', + // toTokenAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9' + // }, + // { + // name: 'ARBITRUM_PANCAKESWAP_V3', + // part: 68, + // fromTokenAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', + // toTokenAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9' + // } + // ], + // [ + // { + // name: 'ARBITRUM_CURVE_STABLE_NG', + // part: 100, + // fromTokenAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', + // toTokenAddress: '0x498bf2b1e120fed3ad3d42ea2165e9b73f99c1e5' + // } + // ] + // ] + // }, + // { + // part: 90, + // hops: [ + // [ + // { + // name: 'ARBITRUM_UNISWAP_V3', + // part: 8, + // fromTokenAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', + // toTokenAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831' + // }, + // { + // name: 'ARBITRUM_INTEGRAL', + // part: 92, + // fromTokenAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', + // toTokenAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831' + // } + // ], + // [ + // { + // name: 'ARBITRUM_CAMELOT_V3', + // part: 100, + // fromTokenAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', + // toTokenAddress: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8' + // } + // ], + // [ + // { + // name: 'ARBITRUM_CURVE_STABLE_NG', + // part: 100, + // fromTokenAddress: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', + // toTokenAddress: '0x498bf2b1e120fed3ad3d42ea2165e9b73f99c1e5' + // } + // ] + // ] + // } + // ] + + + await oneWayMarket.leverage.repay(stateCollateral, userCollateral, userBorrowed, slippage); + // 0xe48a97fef1c54180a2c7d104d210a95ac1a516fdd22109682179f1582da23a82 + + await oneWayMarket.wallet.balances(); + // { + // collateral: '96.0', + // borrowed: '595500.0', + // vaultShares: '1400000000.0', + // gauge: '0' + // } + await oneWayMarket.userState(); + // { + // collateral: '5.727839965845165558', + // borrowed: '0.0', + // debt: '992.083214663467727334', + // N: '10' + // } + await oneWayMarket.userBands(); + // [ 199, 190 ] + await oneWayMarket.userPrices(); + // [ '175.13096689602455189', '202.233192685995210783' ] + await oneWayMarket.userHealth(); + // 1716.0249924305707883 + await oneWayMarket.userHealth(false); + // 3.6389352509210336 +})() +``` + +### Leverage createLoan all ranges methods +```ts + await lending.init('JsonRpc', {}, {}, API_KEY_1INCH); + await lending.oneWayfactory.fetchMarkets(); + + const oneWayMarket = lending.getOneWayMarket('one-way-market-0'); + + const userCollateral = 1; + const userBorrowed = 1000; + const debt = 2000; + await oneWayMarket.leverage.createLoanMaxRecvAllRanges(userCollateral, userBorrowed); + // { + // '4': { + // maxDebt: '37916.338071504823875251', + // maxTotalCollateral: '13.286983617364703479', + // userCollateral: '1', + // collateralFromUserBorrowed: '0.315728154966395280', + // collateralFromMaxDebt: '11.971255462398308199', + // maxLeverage: '10.09857816541446843865', + // avgPrice: '3167.28167656266072703689' + // }, + // '5': { + // maxDebt: '35363.440522143354729759', + // maxTotalCollateral: '12.480961984286574804', + // userCollateral: '1', + // collateralFromUserBorrowed: '0.315728154966395280', + // collateralFromMaxDebt: '11.165233829320179524', + // maxLeverage: '9.48597317551918486951', + // avgPrice: '3167.28167656266072703689' + // }, + // '6': { + // maxDebt: '33122.824118147617102062', + // maxTotalCollateral: '11.773536301065561222', + // userCollateral: '1', + // collateralFromUserBorrowed: '0.315728154966395280', + // collateralFromMaxDebt: '10.457808146099165942', + // maxLeverage: '8.94830459971897955699', + // avgPrice: '3167.28167656266072703689' + // }, + // '7': { + // maxDebt: '31140.555201395785060968', + // maxTotalCollateral: '11.147678193332270290', + // userCollateral: '1', + // collateralFromUserBorrowed: '0.315728154966395280', + // collateralFromMaxDebt: '9.831950038365875010', + // maxLeverage: '8.47263027035929823721', + // avgPrice: '3167.28167656266072703689' + // }, + // + // ... + // + // '50': { + // maxDebt: '8122.705063645852013929', + // maxTotalCollateral: '3.880294838047496482', + // userCollateral: '1', + // collateralFromUserBorrowed: '0.315728154966395280', + // collateralFromMaxDebt: '2.564566683081101202', + // maxLeverage: '2.94916151440614435181', + // avgPrice: '3167.28167656266072703689' + // } + + await oneWayMarket.leverage.createLoanBandsAllRanges(userCollateral, userBorrowed, debt); + // { + // '4': [ 73, 70 ], + // '5': [ 73, 69 ], + // '6': [ 74, 69 ], + // '7': [ 74, 68 ], + // + // ... + // + // '50': [ 97, 48 ] + // } + + await oneWayMarket.leverage.createLoanPricesAllRanges(userCollateral, userBorrowed, debt); + // { + // '4': [ '1073.323292757532604807', '1136.910693647788699808' ], + // '5': [ '1073.323292757532604807', '1153.387660222394333133' ], + // '6': [ '1057.990102860996424743', '1153.387660222394333133' ], + // '7': [ '1057.990102860996424743', '1170.103423414023236507' ], + // + // ... + // + // '50': [ '759.898822708156242647', '1560.282492846180089068' ] + // } +``` diff --git a/package.json b/package.json index 9bd821c..e28dd0a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@curvefi/lending-api", - "version": "1.6.9", + "version": "2.0.0-alpha", "description": "JavaScript library for Curve Lending", "main": "lib/index.js", "author": "Macket", diff --git a/src/constants/abis/Controller.json b/src/constants/abis/Controller.json index 7efc4cf..2cb6398 100644 --- a/src/constants/abis/Controller.json +++ b/src/constants/abis/Controller.json @@ -401,6 +401,10 @@ { "name": "callback_args", "type": "uint256[]" + }, + { + "name": "callback_bytes", + "type": "bytes" } ], "name": "create_loan_extended", @@ -452,6 +456,46 @@ "stateMutability": "payable", "type": "function" }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "borrow_more_extended", + "inputs": [ + { + "name": "collateral", + "type": "uint256" + }, + { + "name": "debt", + "type": "uint256" + }, + { + "name": "callbacker", + "type": "address" + }, + { + "name": "callback_args", + "type": "uint256[]" + }, + { + "name": "callback_bytes", + "type": "bytes" + } + ], + "outputs": [] + }, + { + "stateMutability": "nonpayable", + "type": "function", + "name": "repay", + "inputs": [ + { + "name": "_d_debt", + "type": "uint256" + } + ], + "outputs": [] + }, { "inputs": [ { @@ -481,6 +525,10 @@ { "name": "callback_args", "type": "uint256[]" + }, + { + "name": "callback_bytes", + "type": "bytes" } ], "name": "repay_extended", diff --git a/src/constants/abis/LeverageZap.json b/src/constants/abis/LeverageZap.json index 1bc8715..5f695ea 100644 --- a/src/constants/abis/LeverageZap.json +++ b/src/constants/abis/LeverageZap.json @@ -1,21 +1,4 @@ [ - { - "stateMutability": "view", - "type": "function", - "name": "max_p_base", - "inputs": [ - { - "name": "controller", - "type": "address" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256" - } - ] - }, { "stateMutability": "view", "type": "function", diff --git a/src/constants/aliases.ts b/src/constants/aliases.ts index be420f6..f4d873b 100644 --- a/src/constants/aliases.ts +++ b/src/constants/aliases.ts @@ -36,10 +36,11 @@ export const ALIASES_AVALANCHE = lowerCaseValues({ export const ALIASES_ARBITRUM = lowerCaseValues({ "crv": "0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978", - "one_way_factory": "0xcaEC110C784c9DF37240a8Ce096D352A75922DeA", + // "one_way_factory": "0xcaEC110C784c9DF37240a8Ce096D352A75922DeA", // REAL + "one_way_factory": "0xb3adda9b28f0f117FD5b6eFE7f0a0cd662Dba5D6", // TEST "gauge_controller": "0x2F50D538606Fa9EDD2B11E2446BEb18C9D5846bB", "gauge_factory": "0xabC000d88f23Bb45525E447528DBF656A9D55bf5", - "leverage_zap": "0x0000000000000000000000000000000000000000", + "leverage_zap": "0xA233e77AE47bf839Fd1c1d30f9EAa0584737376a", // TEST }); export const ALIASES_OPTIMISM = lowerCaseValues({ diff --git a/src/external-api.ts b/src/external-api.ts index 8f33eb2..17ad43a 100644 --- a/src/external-api.ts +++ b/src/external-api.ts @@ -1,7 +1,7 @@ import axios from "axios"; import memoize from "memoizee"; import { lending } from "./lending.js"; -import { IExtendedPoolDataFromApi, INetworkName, IPoolFactory } from "./interfaces"; +import { IExtendedPoolDataFromApi, INetworkName, IPoolFactory, I1inchRoute } from "./interfaces"; export const _getPoolsFromApi = memoize( @@ -44,8 +44,7 @@ export const _getUserCollateral = memoize( export const _getQuote1inch = memoize( async (fromToken: string, toToken: string, _amount: bigint): Promise => { if (_amount === BigInt(0)) return "0.0"; - const url = `https://api.1inch.dev/swap/v5.2/1/quote?src=${fromToken}&dst=${toToken}&amount=${_amount}& - protocols=${lending.constants.PROTOCOLS_1INCH}&includeTokensInfo=true&includeProtocols=true`; + const url = `https://api.1inch.dev/swap/v6.0/${lending.chainId}/quote?src=${fromToken}&dst=${toToken}&amount=${_amount}&protocols=${lending.constants.PROTOCOLS_1INCH}&includeTokensInfo=true&includeProtocols=true`; const response = await axios.get( url, { @@ -55,7 +54,7 @@ export const _getQuote1inch = memoize( if (response.status !== 200) { throw Error(`1inch error: ${response.status} ${response.statusText}`); } - return response.data.toAmount; + return response.data.dstAmount; }, { @@ -63,3 +62,35 @@ export const _getQuote1inch = memoize( maxAge: 5 * 1000, // 5s } ) + +const _getSwapData1inch = memoize( + async (fromToken: string, toToken: string, _amount: bigint, slippage: number): Promise<{ tx: { data: string }, protocols: I1inchRoute[] }> => { + if (_amount === BigInt(0)) throw Error("Amount must be > 0"); + const url = `https://api.1inch.dev/swap/v6.0/${lending.chainId}/swap?src=${fromToken}&dst=${toToken}&amount=${_amount}&from=${lending.constants.ALIASES.leverage_zap}&slippage=${slippage}&protocols=${lending.constants.PROTOCOLS_1INCH}&includeTokensInfo=true&includeProtocols=true&disableEstimate=true`; + const response = await axios.get( + url, + { + headers: {"accept": "application/json", "Authorization": `Bearer ${lending.apiKey1inch}`}, + validateStatus: () => true, + }); + if (response.status !== 200) { + throw Error(`1inch error: ${response.status} ${response.statusText}`); + } + return response.data; + + }, + { + promise: true, + maxAge: 5 * 1000, // 5s + } +) + +export const _getCalldata1inch = async (fromToken: string, toToken: string, _amount: bigint, slippage: number): Promise => { + const data = await _getSwapData1inch(fromToken, toToken, _amount, slippage); + return data.tx.data; +} + +export const _getRoute1inch = async (fromToken: string, toToken: string, _amount: bigint, slippage: number): Promise => { + const data = await _getSwapData1inch(fromToken, toToken, _amount, slippage); + return data.protocols; +} diff --git a/src/interfaces.ts b/src/interfaces.ts index 46fb0ec..1fbe965 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -121,4 +121,16 @@ export interface IReward { tokenAddress: string, symbol: string, apy: number -} \ No newline at end of file +} + +export type T1inchRouteStep = { + name: string, + part: number, + fromTokenAddress: string, + toTokenAddress: string, +}[] + +export interface I1inchRoute { + part: number, + hops: T1inchRouteStep[], +} diff --git a/src/lending.ts b/src/lending.ts index 8cda4ff..8df6344 100644 --- a/src/lending.ts +++ b/src/lending.ts @@ -53,6 +53,7 @@ export const NETWORK_CONSTANTS: { [index: number]: any } = { NAME: 'ethereum', ALIASES: ALIASES_ETHEREUM, COINS: COINS_ETHEREUM, + PROTOCOLS_1INCH: "UNISWAP_V1,UNISWAP_V2,SUSHI,MOONISWAP,BALANCER,COMPOUND,CURVE,CURVE_V2_SPELL_2_ASSET,CURVE_V2_SGT_2_ASSET,CURVE_V2_THRESHOLDNETWORK_2_ASSET,CHAI,OASIS,KYBER,AAVE,IEARN,BANCOR,SWERVE,BLACKHOLESWAP,DODO,DODO_V2,VALUELIQUID,SHELL,DEFISWAP,SAKESWAP,LUASWAP,MINISWAP,MSTABLE,SYNTHETIX,AAVE_V2,ST_ETH,ONE_INCH_LP,ONE_INCH_LP_1_1,LINKSWAP,S_FINANCE,PSM,POWERINDEX,XSIGMA,SMOOTHY_FINANCE,SADDLE,KYBER_DMM,BALANCER_V2,UNISWAP_V3,SETH_WRAPPER,CURVE_V2,CURVE_V2_EURS_2_ASSET,CURVE_V2_ETH_CRV,CURVE_V2_ETH_CVX,CONVERGENCE_X,ONE_INCH_LIMIT_ORDER,ONE_INCH_LIMIT_ORDER_V2,ONE_INCH_LIMIT_ORDER_V3,ONE_INCH_LIMIT_ORDER_V4,DFX_FINANCE,FIXED_FEE_SWAP,DXSWAP,SHIBASWAP,UNIFI,PSM_PAX,WSTETH,DEFI_PLAZA,FIXED_FEE_SWAP_V3,SYNTHETIX_WRAPPER,SYNAPSE,CURVE_V2_YFI_2_ASSET,CURVE_V2_ETH_PAL,POOLTOGETHER,ETH_BANCOR_V3,ELASTICSWAP,BALANCER_V2_WRAPPER,FRAXSWAP,RADIOSHACK,KYBERSWAP_ELASTIC,CURVE_V2_TWO_CRYPTO,STABLE_PLAZA,ZEROX_LIMIT_ORDER,CURVE_3CRV,KYBER_DMM_STATIC,ANGLE,ROCKET_POOL,ETHEREUM_ELK,ETHEREUM_PANCAKESWAP_V2,SYNTHETIX_ATOMIC_SIP288,PSM_GUSD,INTEGRAL,MAINNET_SOLIDLY,NOMISWAP_STABLE,CURVE_V2_TWOCRYPTO_META,MAVERICK_V1,VERSE,DFX_FINANCE_V3,ZK_BOB,PANCAKESWAP_V3,NOMISWAPEPCS,XFAI,CURVE_V2_TRICRYPTO_NG,SUSHISWAP_V3,SFRX_ETH,SDAI,ETHEREUM_WOMBATSWAP,CARBON,COMPOUND_V3,DODO_V3,SMARDEX,TRADERJOE_V2_1,PMM15,SOLIDLY_V3,RAFT_PSM,CLAYSTACK,CURVE_STABLE_NG,LIF3,BLUEPRINT,AAVE_V3,ORIGIN,BGD_AAVE_STATIC", }, 10: { NAME: 'optimism', @@ -103,6 +104,7 @@ export const NETWORK_CONSTANTS: { [index: number]: any } = { NAME: 'arbitrum', ALIASES: ALIASES_ARBITRUM, COINS: COINS_ARBITRUM, + PROTOCOLS_1INCH: "ARBITRUM_BALANCER_V2,ARBITRUM_ONE_INCH_LIMIT_ORDER,ARBITRUM_ONE_INCH_LIMIT_ORDER_V2,ARBITRUM_ONE_INCH_LIMIT_ORDER_V3,ARBITRUM_ONE_INCH_LIMIT_ORDER_V4,ARBITRUM_DODO,ARBITRUM_DODO_V2,ARBITRUM_SUSHISWAP,ARBITRUM_DXSWAP,ARBITRUM_UNISWAP_V3,ARBITRUM_CURVE,ARBITRUM_CURVE_V2,ARBITRUM_GMX,ARBITRUM_SYNAPSE,ARBITRUM_SADDLE,ARBITRUM_KYBERSWAP_ELASTIC,ARBITRUM_KYBER_DMM_STATIC,ARBITRUM_AAVE_V3,ARBITRUM_ELK,ARBITRUM_WOOFI_V2,ARBITRUM_CAMELOT,ARBITRUM_TRADERJOE,ARBITRUM_TRADERJOE_V2,ARBITRUM_SWAPFISH,ARBITRUM_ZYBER,ARBITRUM_ZYBER_STABLE,ARBITRUM_SOLIDLIZARD,ARBITRUM_ZYBER_V3,ARBITRUM_MYCELIUM,ARBITRUM_TRIDENT,ARBITRUM_SHELL_OCEAN,ARBITRUM_RAMSES,ARBITRUM_TRADERJOE_V2_1,ARBITRUM_NOMISWAPEPCS,ARBITRUM_CAMELOT_V3,ARBITRUM_WOMBATSWAP,ARBITRUM_CHRONOS,ARBITRUM_LIGHTER,ARBITRUM_ARBIDEX,ARBITRUM_ARBIDEX_V3,ARBSWAP,ARBSWAP_STABLE,ARBITRUM_SUSHISWAP_V3,ARBITRUM_RAMSES_V2,ARBITRUM_LEVEL_FINANCE,ARBITRUM_CHRONOS_V3,ARBITRUM_PANCAKESWAP_V3,ARBITRUM_PMM11,ARBITRUM_DODO_V3,ARBITRUM_SMARDEX,ARBITRUM_INTEGRAL,ARBITRUM_DFX_FINANCE_V3,ARBITRUM_CURVE_STABLE_NG", }, 42220: { NAME: 'celo', @@ -165,19 +167,7 @@ class Lending implements ILending { NETWORK_NAME: 'ethereum', ALIASES: {}, ZERO_ADDRESS: ethers.ZeroAddress, - PROTOCOLS_1INCH: "UNISWAP_V1,UNISWAP_V2,SUSHI,MOONISWAP,BALANCER,COMPOUND,CURVE,CURVE_V2_SPELL_2_ASSET," + - "CURVE_V2_SGT_2_ASSET,CURVE_V2_THRESHOLDNETWORK_2_ASSET,CHAI,OASIS,KYBER,AAVE,IEARN,BANCOR,SWERVE," + - "BLACKHOLESWAP,DODO,DODO_V2,VALUELIQUID,SHELL,DEFISWAP,SAKESWAP,LUASWAP,MINISWAP,MSTABLE,PMM2," + - "SYNTHETIX,AAVE_V2,ST_ETH,ONE_INCH_LP,ONE_INCH_LP_1_1,LINKSWAP,S_FINANCE,PSM,POWERINDEX,XSIGMA," + - "SMOOTHY_FINANCE,SADDLE,KYBER_DMM,BALANCER_V2,UNISWAP_V3,SETH_WRAPPER,CURVE_V2,CURVE_V2_EURS_2_ASSET," + - "CURVE_V2_ETH_CRV,CURVE_V2_ETH_CVX,CONVERGENCE_X,ONE_INCH_LIMIT_ORDER,ONE_INCH_LIMIT_ORDER_V2," + - "ONE_INCH_LIMIT_ORDER_V3,DFX_FINANCE,FIXED_FEE_SWAP,DXSWAP,SHIBASWAP,UNIFI,PSM_PAX,WSTETH,DEFI_PLAZA," + - "FIXED_FEE_SWAP_V3,SYNTHETIX_WRAPPER,SYNAPSE,CURVE_V2_YFI_2_ASSET,CURVE_V2_ETH_PAL,POOLTOGETHER," + - "ETH_BANCOR_V3,ELASTICSWAP,BALANCER_V2_WRAPPER,FRAXSWAP,RADIOSHACK,KYBERSWAP_ELASTIC,CURVE_V2_TWO_CRYPTO," + - "STABLE_PLAZA,ZEROX_LIMIT_ORDER,CURVE_3CRV,KYBER_DMM_STATIC,ANGLE,ROCKET_POOL,ETHEREUM_ELK,ETHEREUM_PANCAKESWAP_V2," + - "SYNTHETIX_ATOMIC_SIP288,PSM_GUSD,INTEGRAL,MAINNET_SOLIDLY,NOMISWAP_STABLE,CURVE_V2_TWOCRYPTO_META,MAVERICK_V1,VERSE," + - "DFX_FINANCE_V2,ZK_BOB,PANCAKESWAP_V3,NOMISWAPEPCS,XFAI,CURVE_V2_TRICRYPTO_NG,PMM8_2,SUSHISWAP_V3,SFRX_ETH,SDAI," + - "ETHEREUM_WOMBATSWAP,PMM12,CARBON,COMPOUND_V3,DODO_V3,SMARDEX,TRADERJOE_V2_1", + PROTOCOLS_1INCH: "", }; } @@ -207,19 +197,7 @@ class Lending implements ILending { NETWORK_NAME: 'ethereum', ALIASES: {}, ZERO_ADDRESS: ethers.ZeroAddress, - PROTOCOLS_1INCH: "UNISWAP_V1,UNISWAP_V2,SUSHI,MOONISWAP,BALANCER,COMPOUND,CURVE,CURVE_V2_SPELL_2_ASSET," + - "CURVE_V2_SGT_2_ASSET,CURVE_V2_THRESHOLDNETWORK_2_ASSET,CHAI,OASIS,KYBER,AAVE,IEARN,BANCOR,SWERVE," + - "BLACKHOLESWAP,DODO,DODO_V2,VALUELIQUID,SHELL,DEFISWAP,SAKESWAP,LUASWAP,MINISWAP,MSTABLE,PMM2," + - "SYNTHETIX,AAVE_V2,ST_ETH,ONE_INCH_LP,ONE_INCH_LP_1_1,LINKSWAP,S_FINANCE,PSM,POWERINDEX,XSIGMA," + - "SMOOTHY_FINANCE,SADDLE,KYBER_DMM,BALANCER_V2,UNISWAP_V3,SETH_WRAPPER,CURVE_V2,CURVE_V2_EURS_2_ASSET," + - "CURVE_V2_ETH_CRV,CURVE_V2_ETH_CVX,CONVERGENCE_X,ONE_INCH_LIMIT_ORDER,ONE_INCH_LIMIT_ORDER_V2," + - "ONE_INCH_LIMIT_ORDER_V3,DFX_FINANCE,FIXED_FEE_SWAP,DXSWAP,SHIBASWAP,UNIFI,PSM_PAX,WSTETH,DEFI_PLAZA," + - "FIXED_FEE_SWAP_V3,SYNTHETIX_WRAPPER,SYNAPSE,CURVE_V2_YFI_2_ASSET,CURVE_V2_ETH_PAL,POOLTOGETHER," + - "ETH_BANCOR_V3,ELASTICSWAP,BALANCER_V2_WRAPPER,FRAXSWAP,RADIOSHACK,KYBERSWAP_ELASTIC,CURVE_V2_TWO_CRYPTO," + - "STABLE_PLAZA,ZEROX_LIMIT_ORDER,CURVE_3CRV,KYBER_DMM_STATIC,ANGLE,ROCKET_POOL,ETHEREUM_ELK,ETHEREUM_PANCAKESWAP_V2," + - "SYNTHETIX_ATOMIC_SIP288,PSM_GUSD,INTEGRAL,MAINNET_SOLIDLY,NOMISWAP_STABLE,CURVE_V2_TWOCRYPTO_META,MAVERICK_V1,VERSE," + - "DFX_FINANCE_V2,ZK_BOB,PANCAKESWAP_V3,NOMISWAPEPCS,XFAI,CURVE_V2_TRICRYPTO_NG,PMM8_2,SUSHISWAP_V3,SFRX_ETH,SDAI," + - "ETHEREUM_WOMBATSWAP,PMM12,CARBON,COMPOUND_V3,DODO_V3,SMARDEX,TRADERJOE_V2_1", + PROTOCOLS_1INCH: "", }; // JsonRpc provider @@ -274,6 +252,7 @@ class Lending implements ILending { this.constants.NETWORK_NAME = NETWORK_CONSTANTS[this.chainId].NAME; this.constants.ALIASES = NETWORK_CONSTANTS[this.chainId].ALIASES; this.constants.COINS = NETWORK_CONSTANTS[this.chainId].COINS; + this.constants.PROTOCOLS_1INCH = NETWORK_CONSTANTS[this.chainId].PROTOCOLS_1INCH; this.setContract(this.constants.ALIASES.crv, ERC20ABI); this.multicallProvider = new MulticallProvider(this.chainId, this.provider); diff --git a/src/markets/OneWayMarketTemplate.ts b/src/markets/OneWayMarketTemplate.ts index 41cee1f..5954fdd 100644 --- a/src/markets/OneWayMarketTemplate.ts +++ b/src/markets/OneWayMarketTemplate.ts @@ -22,8 +22,8 @@ import { DIGas, smartNumber, } from "../utils.js"; -import { IDict, TGas, TAmount, IReward } from "../interfaces.js"; -import { _getQuote1inch } from "../external-api.js"; +import { IDict, TGas, TAmount, IReward, I1inchRoute } from "../interfaces.js"; +import { _getQuote1inch, _getCalldata1inch, _getRoute1inch } from "../external-api.js"; import ERC20Abi from '../constants/abis/ERC20.json' assert { type: 'json' }; @@ -148,6 +148,84 @@ export class OneWayMarketTemplate { claimRewards: () => Promise, } }; + leverage: { + maxLeverage: (N: number) => Promise, + + createLoanMaxRecv: (userCollateral: TAmount, userBorrowed: TAmount, range: number) => + Promise<{ + maxDebt: string, + maxTotalCollateral: string, + userCollateral: string, + collateralFromUserBorrowed: string, + collateralFromMaxDebt: string, + maxLeverage: string, + avgPrice: string, + }>, + createLoanMaxRecvAllRanges: (userCollateral: TAmount, userBorrowed: TAmount) => + Promise>, + createLoanExpectedCollateral: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount) => + Promise<{ totalCollateral: string, userCollateral: string, collateralFromUserBorrowed: string, collateralFromDebt: string, leverage: string, avgPrice: string }>, + createLoanMaxRange: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount) => Promise, + createLoanBands: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number) => Promise<[number, number]>, + createLoanBandsAllRanges: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount) => Promise>, + createLoanPrices: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number) => Promise, + createLoanPricesAllRanges: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount) => Promise>, + createLoanHealth: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, full?: boolean) => Promise, + createLoanIsApproved: (userCollateral: TAmount, userBorrowed: TAmount) => Promise, + createLoanApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise, + createLoanRoute: (userBorrowed: TAmount, debt: TAmount, slippage?: number) => Promise, + createLoan: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, slippage?: number) => Promise, + + borrowMoreMaxRecv: (userCollateral: TAmount, userBorrowed: TAmount, address?: string) => + Promise<{ + maxDebt: string, + maxTotalCollateral: string, + userCollateral: string, + collateralFromUserBorrowed: string, + collateralFromMaxDebt: string, + avgPrice: string, + }>, + borrowMoreExpectedCollateral: (userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, address?: string) => + Promise<{ totalCollateral: string, userCollateral: string, collateralFromUserBorrowed: string, collateralFromDebt: string, avgPrice: string }>, + borrowMoreBands: (userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, address?: string) => Promise<[number, number]>, + borrowMorePrices: (userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, address?: string) => Promise, + borrowMoreHealth: (userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, full?: boolean, address?: string) => Promise, + borrowMoreIsApproved: (userCollateral: TAmount, userBorrowed: TAmount) => Promise, + borrowMoreApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise, + borrowMoreRoute: (userBorrowed: TAmount, debt: TAmount, slippage?: number) => Promise, + borrowMore: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, slippage?: number) => Promise, + + repayExpectedBorrowed: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount) => + Promise<{ totalBorrowed: string, borrowedFromStateCollateral: string, borrowedFromUserCollateral: string, userBorrowed: string, avgPrice: string }>, + repayIsFull: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address?: string) => Promise, + repayIsAvailable: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address?: string) => Promise, + repayBands: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address?: string) => Promise<[number, number]>, + repayPrices: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address?: string) => Promise, + repayHealth: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, full?: boolean, address?: string) => Promise, + repayIsApproved: (userCollateral: TAmount, userBorrowed: TAmount) => Promise, + repayApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise, + repayRoute: (stateCollateral: TAmount, userCollateral: TAmount, slippage?: number) => Promise, + repay: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, slippage?: number) => Promise, + + estimateGas: { + createLoanApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise, + createLoan: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, slippage?: number) => Promise, + + borrowMoreApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise, + borrowMore: (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, slippage?: number) => Promise, + + repayApprove: (userCollateral: TAmount, userBorrowed: TAmount) => Promise, + repay: (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, slippage?: number) => Promise, + } + }; constructor(id: string) { this.id = id; @@ -241,6 +319,55 @@ export class OneWayMarketTemplate { claimRewards: this.vaultClaimRewardsEstimateGas.bind(this), }, } + this.leverage = { + maxLeverage: this.maxLeverage.bind(this), + + createLoanMaxRecv: this.leverageCreateLoanMaxRecv.bind(this), + createLoanMaxRecvAllRanges: this.leverageCreateLoanMaxRecvAllRanges.bind(this), + createLoanExpectedCollateral: this.leverageCreateLoanExpectedCollateral.bind(this), + createLoanMaxRange: this.leverageCreateLoanMaxRange.bind(this), + createLoanBands: this.leverageCreateLoanBands.bind(this), + createLoanBandsAllRanges: this.leverageCreateLoanBandsAllRanges.bind(this), + createLoanPrices: this.leverageCreateLoanPrices.bind(this), + createLoanPricesAllRanges: this.leverageCreateLoanPricesAllRanges.bind(this), + createLoanHealth: this.leverageCreateLoanHealth.bind(this), + createLoanIsApproved: this.leverageCreateLoanIsApproved.bind(this), + createLoanApprove: this.leverageCreateLoanApprove.bind(this), + createLoanRoute: this.leverageCreateLoanRoute.bind(this), + createLoan: this.leverageCreateLoan.bind(this), + + borrowMoreMaxRecv: this.leverageBorrowMoreMaxRecv.bind(this), + borrowMoreExpectedCollateral: this.leverageBorrowMoreExpectedCollateral.bind(this), + borrowMoreBands: this.leverageBorrowMoreBands.bind(this), + borrowMorePrices: this.leverageBorrowMorePrices.bind(this), + borrowMoreHealth: this.leverageBorrowMoreHealth.bind(this), + borrowMoreIsApproved: this.leverageCreateLoanIsApproved.bind(this), + borrowMoreApprove: this.leverageCreateLoanApprove.bind(this), + borrowMoreRoute: this.leverageBorrowMoreRoute.bind(this), + borrowMore: this.leverageBorrowMore.bind(this), + + repayExpectedBorrowed: this.leverageRepayExpectedBorrowed.bind(this), + repayIsFull: this.leverageRepayIsFull.bind(this), + repayIsAvailable: this.leverageRepayIsAvailable.bind(this), + repayBands: this.leverageRepayBands.bind(this), + repayPrices: this.leverageRepayPrices.bind(this), + repayHealth: this.leverageRepayHealth.bind(this), + repayIsApproved: this.leverageRepayIsApproved.bind(this), + repayApprove: this.leverageRepayApprove.bind(this), + repayRoute: this.leverageRepayRoute.bind(this), + repay: this.leverageRepay.bind(this), + + estimateGas: { + createLoanApprove: this.leverageCreateLoanApproveEstimateGas.bind(this), + createLoan: this.leverageCreateLoanEstimateGas.bind(this), + + borrowMoreApprove: this.leverageCreateLoanApproveEstimateGas.bind(this), + borrowMore: this.leverageBorrowMoreEstimateGas.bind(this), + + repayApprove: this.leverageRepayApproveEstimateGas.bind(this), + repay: this.leverageRepayEstimateGas.bind(this), + }, + } } @@ -1021,13 +1148,17 @@ export class OneWayMarketTemplate { return await lending.contracts[this.addresses.controller].contract.loan_exists(address, lending.constantOptions); } - public async _userState(address = ""): Promise<{ _collateral: bigint, _borrowed: bigint, _debt: bigint, _N: bigint }> { + public _userState = memoize(async (address = ""): Promise<{ _collateral: bigint, _borrowed: bigint, _debt: bigint, _N: bigint }> => { address = _getAddress(address); const contract = lending.contracts[this.addresses.controller].contract; const [_collateral, _borrowed, _debt, _N] = await contract.user_state(address, lending.constantOptions) as bigint[]; return { _collateral, _borrowed, _debt, _N } - } + }, + { + promise: true, + maxAge: 3 * 1000, // 3s + }); public async userState(address = ""): Promise<{ collateral: string, borrowed: string, debt: string, N: string }> { const { _collateral, _borrowed, _debt, _N } = await this._userState(address); @@ -1809,10 +1940,10 @@ export class OneWayMarketTemplate { return await this._liquidate(lending.signerAddress, slippage, false) as string; } - // ---------------- LEVERAGE ---------------- + // ---------------- LEVERAGE CREATE LOAN ---------------- private _checkLeverageZap(): void { - if (lending.constants.ALIASES.leverage_zap === "0x0000000000000000000000000000000000000000") { + if (lending.constants.ALIASES.leverage_zap === lending.constants.ZERO_ADDRESS) { throw Error(`There is no leverage contract on this network. ID: ${lending.chainId}`); } } @@ -1833,32 +1964,42 @@ export class OneWayMarketTemplate { return d_k_effective_BN.times(S); } - public async maxLeverage(N: number): Promise { + private async maxLeverage(N: number): Promise { // max_leverage = 1 / (k_effective - 1) const k_effective_BN = await this._get_k_effective_BN(N); return BN(1).div(BN(1).minus(k_effective_BN)).toString() } - private async leverageCreateLoanMaxRecv(userCollateral: TAmount, range: number): - Promise<{ maxBorrowable: string, maxTotalCollateral: string, maxLeverage: string, collateralAvgPrice: string }> { + private async leverageCreateLoanMaxRecv(userCollateral: TAmount, userBorrowed: TAmount, range: number): + Promise<{ + maxDebt: string, + maxTotalCollateral: string, + userCollateral: string, + collateralFromUserBorrowed: string, + collateralFromMaxDebt: string, + maxLeverage: string, + avgPrice: string, + }> { // max_borrowable = userCollateral / (1 / (k_effective * max_p_base) - 1 / p_avg) this._checkLeverageZap(); - this._checkRange(range); + if (range > 0) this._checkRange(range); const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals); - const contract = lending.contracts[lending.constants.ALIASES.leverage_zap].contract; + const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals); const oraclePriceBand = await this.oraclePriceBand(); let pAvgBN = BN(await this.calcTickPrice(oraclePriceBand)); // upper tick of oracle price band - let maxLeverageCollateralBN = BN(0); - let _maxLeverageCollateral = BigInt(0); let maxBorrowablePrevBN = BN(0); let maxBorrowableBN = BN(0); - let _maxBorrowable = BigInt(0); + let _userEffectiveCollateral = BigInt(0); + let _maxLeverageCollateral = BigInt(0); + const contract = lending.contracts[lending.constants.ALIASES.leverage_zap].contract; for (let i = 0; i < 5; i++) { maxBorrowablePrevBN = maxBorrowableBN; - _maxBorrowable = await contract.max_borrowable(this.addresses.controller, _userCollateral, _maxLeverageCollateral, range, fromBN(pAvgBN)); + _userEffectiveCollateral = _userCollateral + fromBN(BN(userBorrowed).div(pAvgBN), this.collateral_token.decimals); + const _maxBorrowable = await contract.max_borrowable(this.addresses.controller, _userEffectiveCollateral, _maxLeverageCollateral, range, fromBN(pAvgBN)); + if (_maxBorrowable === BigInt(0)) break; maxBorrowableBN = toBN(_maxBorrowable, this.borrowed_token.decimals); if (maxBorrowableBN.minus(maxBorrowablePrevBN).abs().div(maxBorrowablePrevBN).lt(0.0005)) { @@ -1866,21 +2007,36 @@ export class OneWayMarketTemplate { break; } - _maxLeverageCollateral = BigInt(await _getQuote1inch(this.addresses.borrowed_token, this.addresses.collateral_token, _maxBorrowable)); - maxLeverageCollateralBN = toBN(_maxLeverageCollateral, this.collateral_token.decimals); - pAvgBN = maxBorrowableBN.div(maxLeverageCollateralBN) + // additionalCollateral = (userBorrowed / p) + leverageCollateral + const _maxAdditionalCollateral = BigInt(await _getQuote1inch(this.addresses.borrowed_token, this.addresses.collateral_token, _maxBorrowable + _userBorrowed)); + pAvgBN = maxBorrowableBN.plus(userBorrowed).div(toBN(_maxAdditionalCollateral, this.collateral_token.decimals)); + _maxLeverageCollateral = _maxAdditionalCollateral - fromBN(BN(userBorrowed).div(pAvgBN), this.collateral_token.decimals); } + const userEffectiveCollateralBN = maxBorrowableBN.gt(0) ? toBN(_userEffectiveCollateral, this.collateral_token.decimals) : BN(0); + const maxLeverageCollateralBN = toBN(_maxLeverageCollateral, this.collateral_token.decimals); + return { - maxBorrowable: maxBorrowableBN.toString(), - maxTotalCollateral: maxLeverageCollateralBN.plus(userCollateral).toString(), - maxLeverage: maxLeverageCollateralBN.plus(userCollateral).div(userCollateral).toString(), - collateralAvgPrice: pAvgBN.toString(), + maxDebt: formatNumber(maxBorrowableBN.toString(), this.borrowed_token.decimals), + maxTotalCollateral: formatNumber(maxLeverageCollateralBN.plus(userEffectiveCollateralBN).toString(), this.collateral_token.decimals), + userCollateral: formatNumber(userCollateral, this.collateral_token.decimals), + collateralFromUserBorrowed: formatNumber(BN(userBorrowed).div(pAvgBN).toString(), this.collateral_token.decimals), + collateralFromMaxDebt: formatNumber(maxLeverageCollateralBN.toString(), this.collateral_token.decimals), + maxLeverage: maxLeverageCollateralBN.plus(userEffectiveCollateralBN).div(userEffectiveCollateralBN).toString(), + avgPrice: pAvgBN.toString(), }; } - private leverageCreateLoanMaxRecvAllRanges = memoize(async (userCollateral: TAmount): - Promise> => { + private leverageCreateLoanMaxRecvAllRanges = memoize(async (userCollateral: TAmount, userBorrowed: TAmount): + Promise> => { this._checkLeverageZap(); const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals); const contract = lending.contracts[lending.constants.ALIASES.leverage_zap].multicallContract; @@ -1895,13 +2051,14 @@ export class OneWayMarketTemplate { let maxBorrowableBN: BigNumber[] = new Array(arrLength).fill(BN(0)); let _maxBorrowable: bigint[] = new Array(arrLength).fill(BigInt(0)); - for (let i = 0; i < 5; i++) { + const pBN = pAvgBN ?? pAvgApproxBN; maxBorrowablePrevBN = maxBorrowableBN; + const _userEffectiveCollateral: bigint = _userCollateral + fromBN(BN(userBorrowed).div(pBN), this.collateral_token.decimals); const calls = []; for (let N = this.minBands; N <= this.maxBands; N++) { const j = N - this.minBands; - calls.push(contract.max_borrowable(this.addresses.controller, _userCollateral, _maxLeverageCollateral[j], N, fromBN(pAvgBN ?? pAvgApproxBN))); + calls.push(contract.max_borrowable(this.addresses.controller, _userEffectiveCollateral, _maxLeverageCollateral[j], N, fromBN(pBN))); } _maxBorrowable = await lending.multicallProvider.all(calls); maxBorrowableBN = _maxBorrowable.map((_mb) => toBN(_mb, this.borrowed_token.decimals)); @@ -1922,14 +2079,27 @@ export class OneWayMarketTemplate { _maxLeverageCollateral = maxLeverageCollateralBN.map((mlc) => fromBN(mlc, this.collateral_token.decimals)); } - const res: IDict<{ maxBorrowable: string, maxTotalCollateral: string, maxLeverage: string, collateralAvgPrice: string }> = {}; + const userEffectiveCollateralBN = BN(userCollateral).plus(BN(userBorrowed).div(pAvgBN as BigNumber)); + + const res: IDict<{ + maxDebt: string, + maxTotalCollateral: string, + userCollateral: string, + collateralFromUserBorrowed: string, + collateralFromMaxDebt: string, + maxLeverage: string, + avgPrice: string, + }> = {}; for (let N = this.minBands; N <= this.maxBands; N++) { const j = N - this.minBands; res[N] = { - maxBorrowable: maxBorrowableBN[j].toString(), - maxTotalCollateral: maxLeverageCollateralBN[j].plus(userCollateral).toString(), - maxLeverage: maxLeverageCollateralBN[j].plus(userCollateral).div(userCollateral).toString(), - collateralAvgPrice: (pAvgBN as BigNumber).toString(), + maxDebt: formatNumber(maxBorrowableBN[j].toString(), this.borrowed_token.decimals), + maxTotalCollateral: formatNumber(maxLeverageCollateralBN[j].plus(userEffectiveCollateralBN).toString(), this.collateral_token.decimals), + userCollateral: formatNumber(userCollateral, this.collateral_token.decimals), + collateralFromUserBorrowed: formatNumber(BN(userBorrowed).div(pAvgBN as BigNumber).toString(), this.collateral_token.decimals), + collateralFromMaxDebt: formatNumber(maxLeverageCollateralBN[j].toString(), this.collateral_token.decimals), + maxLeverage: maxLeverageCollateralBN[j].plus(userEffectiveCollateralBN).div(userEffectiveCollateralBN).toString(), + avgPrice: (pAvgBN as BigNumber).toString(), }; } @@ -1940,54 +2110,103 @@ export class OneWayMarketTemplate { maxAge: 60 * 1000, // 1m }); - private leverageCreateLoanTotalCollateral = memoize(async (userCollateral: TAmount, debt: TAmount): Promise => { + private _leverageExpectedCollateral = memoize(async (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, user?: string): + Promise<{ _futureStateCollateral: bigint, _totalCollateral: bigint, _userCollateral: bigint, + _collateralFromUserBorrowed: bigint, _collateralFromDebt: bigint, avgPrice: string }> => { const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals); const _debt = parseUnits(debt, this.borrowed_token.decimals); - const _leverageCollateral = BigInt(await _getQuote1inch(this.addresses.borrowed_token, this.addresses.collateral_token, _debt)); + const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals); + // additionalCollateral = (userBorrowed / p) + leverageCollateral + const _additionalCollateral = BigInt(await _getQuote1inch(this.addresses.borrowed_token, this.addresses.collateral_token, _debt + _userBorrowed)); + const _collateralFromDebt = _debt * BigInt(10**18) / (_debt + _userBorrowed) * _additionalCollateral / BigInt(10**18); + const _collateralFromUserBorrowed = _additionalCollateral - _collateralFromDebt; + let _stateCollateral = BigInt(0); + if (user) { + const { _collateral, _borrowed } = await this._userState(user); + if (_borrowed > BigInt(0)) throw Error(`User ${user} is already in liquidation mode`); + _stateCollateral = _collateral; + } + const _totalCollateral = _userCollateral + _additionalCollateral; + const _futureStateCollateral = _stateCollateral + _totalCollateral; + const avgPrice = toBN(_debt + _userBorrowed, this.borrowed_token.decimals).div(toBN(_additionalCollateral, this.collateral_token.decimals)).toString(); - return formatUnits(_userCollateral + _leverageCollateral, this.collateral_token.decimals); + return { _futureStateCollateral, _totalCollateral, _userCollateral, _collateralFromUserBorrowed, _collateralFromDebt, avgPrice }; }, { promise: true, maxAge: 60 * 1000, // 1m }); - private async leverageGetMaxRange(userCollateral: number | string, debt: number | string): Promise { - const maxRecv = await this.leverageCreateLoanMaxRecvAllRanges(userCollateral); + private async leverageCreateLoanExpectedCollateral(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount): + Promise<{ totalCollateral: string, userCollateral: string, collateralFromUserBorrowed: string, collateralFromDebt: string, leverage: string, avgPrice: string }> { + const { _totalCollateral, _userCollateral, _collateralFromUserBorrowed, _collateralFromDebt, avgPrice } = + await this._leverageExpectedCollateral(userCollateral, userBorrowed, debt); + return { + totalCollateral: formatUnits(_totalCollateral, this.collateral_token.decimals), + userCollateral: formatUnits(_userCollateral, this.collateral_token.decimals), + collateralFromUserBorrowed: formatUnits(_collateralFromUserBorrowed, this.collateral_token.decimals), + collateralFromDebt: formatUnits(_collateralFromDebt, this.collateral_token.decimals), + leverage: toBN(_collateralFromDebt + _userCollateral + _collateralFromUserBorrowed, this.collateral_token.decimals) + .div(toBN(_userCollateral + _collateralFromUserBorrowed, this.collateral_token.decimals)).toString(), + avgPrice, + } + } + + private async leverageCreateLoanMaxRange(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount): Promise { + const maxRecv = await this.leverageCreateLoanMaxRecvAllRanges(userCollateral, userBorrowed); for (let N = this.minBands; N <= this.maxBands; N++) { - if (BN(debt).gt(maxRecv[N].maxBorrowable)) return N - 1; + if (BN(debt).gt(maxRecv[N].maxDebt)) return N - 1; } return this.maxBands; } - private async _leverageCalcN1(userCollateral: TAmount, debt: TAmount, range: number): Promise { - this._checkRange(range); - const _totalCollateral = parseUnits(await this.leverageCreateLoanTotalCollateral(userCollateral, debt), this.collateral_token.decimals); - const _debt = parseUnits(debt, this.borrowed_token.decimals); - return await lending.contracts[this.addresses.controller].contract.calculate_debt_n1(_totalCollateral, _debt, range, lending.constantOptions); - } + private _leverageCalcN1 = memoize(async (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, user?: string): Promise => { + if (range > 0) this._checkRange(range); + let _stateDebt = BigInt(0); + if (user) { + const { _debt, _borrowed, _N } = await this._userState(user); + if (_borrowed > BigInt(0)) throw Error(`User ${user} is already in liquidation mode`); + _stateDebt = _debt; + if (range < 0) range = Number(lending.formatUnits(_N, 0)); + } + const { _futureStateCollateral } = await this._leverageExpectedCollateral(userCollateral, userBorrowed, debt, user); + const _debt = _stateDebt + parseUnits(debt, this.borrowed_token.decimals); + return await lending.contracts[this.addresses.controller].contract.calculate_debt_n1(_futureStateCollateral, _debt, range, lending.constantOptions); + }, + { + promise: true, + maxAge: 60 * 1000, // 1m + }); - private async _leverageCalcN1AllRanges(userCollateral: number | string, debt: number | string, maxN: number): Promise { - const _totalCollateral = parseUnits(await this.leverageCreateLoanTotalCollateral(userCollateral, debt), this.collateral_token.decimals); + private _leverageCalcN1AllRanges = memoize(async (userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, maxN: number): Promise => { + const { _futureStateCollateral } = await this._leverageExpectedCollateral(userCollateral, userBorrowed, debt); const _debt = parseUnits(debt, this.borrowed_token.decimals); const calls = []; for (let N = this.minBands; N <= maxN; N++) { - calls.push(lending.contracts[this.addresses.controller].multicallContract.calculate_debt_n1(_totalCollateral, _debt, N)); + calls.push(lending.contracts[this.addresses.controller].multicallContract.calculate_debt_n1(_futureStateCollateral, _debt, N)); } return await lending.multicallProvider.all(calls) as bigint[]; - } + }, + { + promise: true, + maxAge: 60 * 1000, // 1m + }); - private async _leverageCreateLoanBands(collateral: number | string, debt: number | string, range: number): Promise<[bigint, bigint]> { - const _n1 = await this._leverageCalcN1(collateral, debt, range); + private async _leverageBands(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, user?: string): Promise<[bigint, bigint]> { + const _n1 = await this._leverageCalcN1(userCollateral, userBorrowed, debt, range, user); + if (range < 0) { + const { N } = await this.userState(user); + range = Number(N); + } const _n2 = _n1 + BigInt(range - 1); return [_n2, _n1]; } - private async _leverageCreateLoanBandsAllRanges(collateral: number | string, debt: number | string): Promise> { - const maxN = await this.leverageGetMaxRange(collateral, debt); - const _n1_arr = await this._leverageCalcN1AllRanges(collateral, debt, maxN); + private async _leverageCreateLoanBandsAllRanges(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount): Promise> { + const maxN = await this.leverageCreateLoanMaxRange(userCollateral, userBorrowed, debt); + const _n1_arr = await this._leverageCalcN1AllRanges(userCollateral, userBorrowed, debt, maxN); const _n2_arr: bigint[] = []; for (let N = this.minBands; N <= maxN; N++) { _n2_arr.push(_n1_arr[N - this.minBands] + BigInt(N - 1)); @@ -2001,16 +2220,16 @@ export class OneWayMarketTemplate { return _bands; } - private async leverageCreateLoanBands(collateral: number | string, debt: number | string, range: number): Promise<[number, number]> { + private async leverageCreateLoanBands(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number): Promise<[number, number]> { this._checkLeverageZap(); - const [_n2, _n1] = await this._leverageCreateLoanBands(collateral, debt, range); + const [_n2, _n1] = await this._leverageBands(userCollateral, userBorrowed, debt, range); return [Number(_n2), Number(_n1)]; } - private async leverageCreateLoanBandsAllRanges(collateral: number | string, debt: number | string): Promise> { + private async leverageCreateLoanBandsAllRanges(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount): Promise> { this._checkLeverageZap(); - const _bands = await this._leverageCreateLoanBandsAllRanges(collateral, debt); + const _bands = await this._leverageCreateLoanBandsAllRanges(userCollateral, userBorrowed, debt); const bands: { [index: number]: [number, number] | null } = {}; for (let N = this.minBands; N <= this.maxBands; N++) { @@ -2024,16 +2243,16 @@ export class OneWayMarketTemplate { return bands; } - private async leverageCreateLoanPrices(collateral: number | string, debt: number | string, range: number): Promise { + private async leverageCreateLoanPrices(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number): Promise { this._checkLeverageZap(); - const [_n2, _n1] = await this._leverageCreateLoanBands(collateral, debt, range); + const [_n2, _n1] = await this._leverageBands(userCollateral, userBorrowed, debt, range); return await this._getPrices(_n2, _n1); } - private async leverageCreateLoanPricesAllRanges(collateral: number | string, debt: number | string): Promise> { + private async leverageCreateLoanPricesAllRanges(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount): Promise> { this._checkLeverageZap(); - const _bands = await this._leverageCreateLoanBandsAllRanges(collateral, debt); + const _bands = await this._leverageCreateLoanBandsAllRanges(userCollateral, userBorrowed, debt); const prices: { [index: number]: [string, string] | null } = {}; for (let N = this.minBands; N <= this.maxBands; N++) { @@ -2047,17 +2266,470 @@ export class OneWayMarketTemplate { return prices; } - private async leverageCreateLoanHealth(collateral: number | string, debt: number | string, range: number, full = true): Promise { + private async _leverageHealth( + userCollateral: TAmount, + userBorrowed: TAmount, + dDebt: TAmount, + range: number, + full: boolean, + user = lending.constants.ZERO_ADDRESS + ): Promise { this._checkLeverageZap(); - const address = "0x0000000000000000000000000000000000000000"; - const totalCollateral = await this.leverageCreateLoanTotalCollateral(collateral, debt); - const _totalCollateral = parseUnits(totalCollateral, this.collateral_token.decimals); - const _debt = parseUnits(debt); + if (range > 0) this._checkRange(range); + const { _totalCollateral } = await this._leverageExpectedCollateral(userCollateral, userBorrowed, dDebt, user); + const { _borrowed, _N } = await this._userState(user); + if (_borrowed > BigInt(0)) throw Error(`User ${user} is already in liquidation mode`); + if (range < 0) range = Number(lending.formatUnits(_N, 0)); + const _dDebt = parseUnits(dDebt, this.collateral_token.decimals); const contract = lending.contracts[this.addresses.controller].contract; - let _health = await contract.health_calculator(address, _totalCollateral, _debt, full, range, lending.constantOptions) as bigint; + let _health = await contract.health_calculator(user, _totalCollateral, _dDebt, full, range, lending.constantOptions) as bigint; _health = _health * BigInt(100); return formatUnits(_health); } + + private async leverageCreateLoanHealth(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, full = true): Promise { + return await this._leverageHealth(userCollateral, userBorrowed, debt, range, full); + } + + private async leverageCreateLoanIsApproved(userCollateral: TAmount, userBorrowed: TAmount): Promise { + const collateralAllowance = await hasAllowance( + [this.collateral_token.address], [userCollateral], lending.signerAddress, this.addresses.controller); + const borrowedAllowance = await hasAllowance( + [this.borrowed_token.address], [userBorrowed], lending.signerAddress, lending.constants.ALIASES.leverage_zap); + + return collateralAllowance && borrowedAllowance + } + + private async leverageCreateLoanApproveEstimateGas (userCollateral: TAmount, userBorrowed: TAmount): Promise { + const collateralGas = await ensureAllowanceEstimateGas( + [this.collateral_token.address], [userCollateral], this.addresses.controller); + const borrowedGas = await ensureAllowanceEstimateGas( + [this.borrowed_token.address], [userBorrowed], lending.constants.ALIASES.leverage_zap); + + if(Array.isArray(collateralGas) && Array.isArray(borrowedGas)) { + return [collateralGas[0] + borrowedGas[0], collateralGas[1] + borrowedGas[1]] + } else { + return (collateralGas as number) + (borrowedGas as number) + } + } + + private async leverageCreateLoanApprove(userCollateral: TAmount, userBorrowed: TAmount): Promise { + const collateralApproveTx = await ensureAllowance( + [this.collateral_token.address], [userCollateral], this.addresses.controller); + const borrowedApproveTx = await ensureAllowance( + [this.borrowed_token.address], [userBorrowed], lending.constants.ALIASES.leverage_zap); + + return [...collateralApproveTx, ...borrowedApproveTx] + } + + private async leverageCreateLoanRoute(userBorrowed: TAmount, debt: TAmount, slippage = 0.1): Promise { + const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals); + const _debt = parseUnits(debt, this.borrowed_token.decimals); + + return await _getRoute1inch(this.addresses.borrowed_token, this.addresses.collateral_token, _debt + _userBorrowed, slippage); + } + + private async _leverageCreateLoan( + userCollateral: TAmount, + userBorrowed: TAmount, + debt: TAmount, + range: number, + slippage: number, + estimateGas: boolean + ): Promise { + if (await this.userLoanExists()) throw Error("Loan already created"); + this._checkRange(range); + + const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals); + const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals); + const _debt = parseUnits(debt, this.borrowed_token.decimals); + const calldata = await _getCalldata1inch(this.addresses.borrowed_token, this.addresses.collateral_token, _debt + _userBorrowed, slippage); + const contract = lending.contracts[this.addresses.controller].contract; + const gas = await contract.create_loan_extended.estimateGas( + _userCollateral, + _debt, + range, + lending.constants.ALIASES.leverage_zap, + [0, parseUnits(this.id.split("-").slice(-1)[0], 0), _userBorrowed], + calldata, + { ...lending.constantOptions } + ); + if (estimateGas) return smartNumber(gas); + + await lending.updateFeeData(); + const gasLimit = _mulBy1_3(DIGas(gas)); + return (await contract.create_loan_extended( + _userCollateral, + _debt, + range, + lending.constants.ALIASES.leverage_zap, + [0, parseUnits(this.id.split("-").slice(-1)[0], 0), _userBorrowed], + calldata, + { ...lending.options, gasLimit } + )).hash + } + + private async leverageCreateLoanEstimateGas(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, slippage = 0.1): Promise { + this._checkLeverageZap(); + if (!(await this.leverageCreateLoanIsApproved(userCollateral, userBorrowed))) throw Error("Approval is needed for gas estimation"); + return await this._leverageCreateLoan(userCollateral, userBorrowed, debt, range, slippage, true) as number; + } + + private async leverageCreateLoan(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, range: number, slippage = 0.1): Promise { + this._checkLeverageZap(); + await this.leverageCreateLoanApprove(userCollateral, userBorrowed); + return await this._leverageCreateLoan(userCollateral, userBorrowed, debt, range, slippage, false) as string; + } + + // ---------------- LEVERAGE BORROW MORE ---------------- + + private async leverageBorrowMoreMaxRecv(userCollateral: TAmount, userBorrowed: TAmount, address = ""): + Promise<{ + maxDebt: string, + maxTotalCollateral: string, + userCollateral: string, + collateralFromUserBorrowed: string, + collateralFromMaxDebt: string, + avgPrice: string, + }> { + // max_borrowable = userCollateral / (1 / (k_effective * max_p_base) - 1 / p_avg) + this._checkLeverageZap(); + address = _getAddress(address); + const { _collateral: _stateCollateral, _borrowed: _stateBorrowed, _debt: _stateDebt, _N } = await this._userState(address); + if (_stateBorrowed > BigInt(0)) throw Error(`User ${address} is already in liquidation mode`); + const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals); + const controllerContract = lending.contracts[this.addresses.controller].contract; + const _borrowedFromStateCollateral = await controllerContract.max_borrowable(_stateCollateral, _N, _stateDebt, lending.constantOptions) - _stateDebt; + const _userBorrowed = _borrowedFromStateCollateral + parseUnits(userBorrowed, this.borrowed_token.decimals); + userBorrowed = formatUnits(_userBorrowed, this.borrowed_token.decimals); + + const oraclePriceBand = await this.oraclePriceBand(); + let pAvgBN = BN(await this.calcTickPrice(oraclePriceBand)); // upper tick of oracle price band + let maxBorrowablePrevBN = BN(0); + let maxBorrowableBN = BN(0); + let _userEffectiveCollateral = BigInt(0); + let _maxLeverageCollateral = BigInt(0); + + const contract = lending.contracts[lending.constants.ALIASES.leverage_zap].contract; + for (let i = 0; i < 5; i++) { + maxBorrowablePrevBN = maxBorrowableBN; + _userEffectiveCollateral = _userCollateral + fromBN(BN(userBorrowed).div(pAvgBN), this.collateral_token.decimals); + let _maxBorrowable = await contract.max_borrowable(this.addresses.controller, _userEffectiveCollateral, _maxLeverageCollateral, _N, fromBN(pAvgBN)); + _maxBorrowable = _maxBorrowable * BigInt(999) / BigInt(1000); // Revert happens if I don't do this and try to borrow max + if (_maxBorrowable === BigInt(0)) break; + maxBorrowableBN = toBN(_maxBorrowable, this.borrowed_token.decimals); + + if (maxBorrowableBN.minus(maxBorrowablePrevBN).abs().div(maxBorrowablePrevBN).lt(0.0005)) { + maxBorrowableBN = maxBorrowablePrevBN; + break; + } + + // additionalCollateral = (userBorrowed / p) + leverageCollateral + const _maxAdditionalCollateral = BigInt(await _getQuote1inch(this.addresses.borrowed_token, this.addresses.collateral_token, _maxBorrowable + _userBorrowed)); + pAvgBN = maxBorrowableBN.plus(userBorrowed).div(toBN(_maxAdditionalCollateral, this.collateral_token.decimals)); + _maxLeverageCollateral = _maxAdditionalCollateral - fromBN(BN(userBorrowed).div(pAvgBN), this.collateral_token.decimals); + } + + if (maxBorrowableBN.eq(0)) _userEffectiveCollateral = BigInt(0); + const _maxTotalCollateral = _userEffectiveCollateral + _maxLeverageCollateral + const _maxBorrowable = await controllerContract.max_borrowable(_stateCollateral + _maxTotalCollateral, _N, _stateDebt, lending.constantOptions) - _stateDebt; + + return { + maxDebt: formatUnits(_maxBorrowable, this.borrowed_token.decimals), + maxTotalCollateral: formatUnits(_maxTotalCollateral, this.collateral_token.decimals), + userCollateral: formatNumber(userCollateral, this.collateral_token.decimals), + collateralFromUserBorrowed: formatNumber(BN(userBorrowed).div(pAvgBN).toString(), this.collateral_token.decimals), + collateralFromMaxDebt: formatUnits(_maxLeverageCollateral, this.collateral_token.decimals), + avgPrice: pAvgBN.toString(), + }; + } + + private async leverageBorrowMoreExpectedCollateral(userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, address = ""): + Promise<{ totalCollateral: string, userCollateral: string, collateralFromUserBorrowed: string, collateralFromDebt: string, avgPrice: string }> { + address = _getAddress(address); + const { _totalCollateral, _userCollateral, _collateralFromUserBorrowed, _collateralFromDebt, avgPrice } = + await this._leverageExpectedCollateral(userCollateral, userBorrowed, dDebt, address); + return { + totalCollateral: formatUnits(_totalCollateral, this.collateral_token.decimals), + userCollateral: formatUnits(_userCollateral, this.collateral_token.decimals), + collateralFromUserBorrowed: formatUnits(_collateralFromUserBorrowed, this.collateral_token.decimals), + collateralFromDebt: formatUnits(_collateralFromDebt, this.collateral_token.decimals), + avgPrice, + } + } + + private async leverageBorrowMoreBands(userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, address = ""): Promise<[number, number]> { + address = _getAddress(address); + this._checkLeverageZap(); + const [_n2, _n1] = await this._leverageBands(userCollateral, userBorrowed, dDebt, -1, address); + + return [Number(_n2), Number(_n1)]; + } + + private async leverageBorrowMorePrices(userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, address = ""): Promise { + address = _getAddress(address); + this._checkLeverageZap(); + const [_n2, _n1] = await this._leverageBands(userCollateral, userBorrowed, dDebt, -1, address); + + return await this._getPrices(_n2, _n1); + } + + private async leverageBorrowMoreHealth(userCollateral: TAmount, userBorrowed: TAmount, dDebt: TAmount, full = true, address = ""): Promise { + address = _getAddress(address); + return await this._leverageHealth(userCollateral, userBorrowed, dDebt, -1, full, address); + } + + private async leverageBorrowMoreRoute(userBorrowed: TAmount, debt: TAmount, slippage = 0.1): Promise { + const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals); + const _debt = parseUnits(debt, this.borrowed_token.decimals); + + return await _getRoute1inch(this.addresses.borrowed_token, this.addresses.collateral_token, _debt + _userBorrowed, slippage); + } + + private async _leverageBorrowMore( + userCollateral: TAmount, + userBorrowed: TAmount, + debt: TAmount, + slippage: number, + estimateGas: boolean + ): Promise { + if (!(await this.userLoanExists())) throw Error("Loan does not exist"); + const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals); + const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals); + const _debt = parseUnits(debt, this.borrowed_token.decimals); + const calldata = await _getCalldata1inch(this.addresses.borrowed_token, this.addresses.collateral_token, _debt + _userBorrowed, slippage); + const contract = lending.contracts[this.addresses.controller].contract; + const gas = await contract.borrow_more_extended.estimateGas( + _userCollateral, + _debt, + lending.constants.ALIASES.leverage_zap, + [0, parseUnits(this.id.split("-").slice(-1)[0], 0), _userBorrowed], + calldata, + { ...lending.constantOptions } + ); + if (estimateGas) return smartNumber(gas); + + await lending.updateFeeData(); + const gasLimit = _mulBy1_3(DIGas(gas)); + + return (await contract.borrow_more_extended( + _userCollateral, + _debt, + lending.constants.ALIASES.leverage_zap, + [0, parseUnits(this.id.split("-").slice(-1)[0], 0), _userBorrowed], + calldata, + { ...lending.options, gasLimit } + )).hash + } + + private async leverageBorrowMoreEstimateGas(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, slippage = 0.1): Promise { + this._checkLeverageZap(); + if (!(await this.leverageCreateLoanIsApproved(userCollateral, userBorrowed))) throw Error("Approval is needed for gas estimation"); + return await this._leverageBorrowMore(userCollateral, userBorrowed, debt, slippage, true) as number; + } + + private async leverageBorrowMore(userCollateral: TAmount, userBorrowed: TAmount, debt: TAmount, slippage = 0.1): Promise { + this._checkLeverageZap(); + await this.leverageCreateLoanApprove(userCollateral, userBorrowed); + return await this._leverageBorrowMore(userCollateral, userBorrowed, debt, slippage, false) as string; + } + + // ---------------- LEVERAGE REPAY ---------------- + + private leverageRepayExpectedBorrowed = memoize( async (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount): + Promise<{ totalBorrowed: string, borrowedFromStateCollateral: string, borrowedFromUserCollateral: string, userBorrowed: string, avgPrice: string }> => { + this._checkLeverageZap(); + const _stateCollateral = parseUnits(stateCollateral, this.collateral_token.decimals); + const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals); + let _borrowedExpected = BigInt(0); + let _borrowedFromStateCollateral = BigInt(0); + let _borrowedFromUserCollateral = BigInt(0); + if (_stateCollateral + _userCollateral > BigInt(0)) { + _borrowedExpected = BigInt(await _getQuote1inch(this.addresses.collateral_token, this.addresses.borrowed_token, _stateCollateral + _userCollateral)); + _borrowedFromStateCollateral = _stateCollateral * BigInt(10 ** 18) / (_stateCollateral + _userCollateral) * _borrowedExpected / BigInt(10 ** 18); + _borrowedFromUserCollateral = _borrowedExpected - _borrowedFromStateCollateral; + } + const _totalBorrowed = _borrowedExpected + parseUnits(userBorrowed, this.borrowed_token.decimals); + const avgPrice = toBN(_borrowedExpected, this.borrowed_token.decimals).div(toBN(_stateCollateral + _userCollateral, this.collateral_token.decimals)).toString(); + + return { + totalBorrowed: formatUnits(_totalBorrowed, this.borrowed_token.decimals), + borrowedFromStateCollateral: formatUnits(_borrowedFromStateCollateral, this.borrowed_token.decimals), + borrowedFromUserCollateral: formatUnits(_borrowedFromUserCollateral, this.borrowed_token.decimals), + userBorrowed: formatNumber(userBorrowed, this.borrowed_token.decimals), + avgPrice, + } + }, + { + promise: true, + maxAge: 5 * 60 * 1000, // 5m + }); + + private async leverageRepayIsFull(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address = ""): Promise { + this._checkLeverageZap(); + address = _getAddress(address); + const { borrowed: stateBorrowed, debt } = await this.userState(address); + const { totalBorrowed } = await this.leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed); + + return BN(stateBorrowed).plus(totalBorrowed).gt(debt); + } + + private async leverageRepayIsAvailable(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address = ""): Promise { + // 0. const { collateral, stablecoin, debt } = await this.userState(address); + // 1. maxCollateral for deleverage is collateral from line above. + // 2. If user is underwater (stablecoin > 0), only full repayment is available: + // await this.deleverageRepayStablecoins(deleverageCollateral) + stablecoin > debt + this._checkLeverageZap(); + address = _getAddress(address); + const { collateral, borrowed, debt } = await this.userState(address); + // Loan does not exist + if (BN(debt).eq(0)) return false; + // Can't spend more than user has + if (BN(stateCollateral).gt(collateral)) return false; + // Only full repayment and closing the position is available if user is underwater+ + if (BN(borrowed).gt(0)) return await this.leverageRepayIsFull(stateCollateral, userCollateral, userBorrowed, address); + + return true; + } + + private _leverageRepayBands = memoize( async (stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address: string): Promise<[bigint, bigint]> => { + address = _getAddress(address); + if (!(await this.leverageRepayIsAvailable(stateCollateral, userCollateral, userBorrowed, address))) return [parseUnits(0, 0), parseUnits(0, 0)]; + + const _stateRepayCollateral = parseUnits(stateCollateral, this.collateral_token.decimals); + const { _collateral: _stateCollateral, _debt: _stateDebt, _N } = await this._userState(address); + if (_stateDebt == BigInt(0)) throw Error(`Loan for ${address} does not exist`); + if (_stateCollateral < _stateRepayCollateral) throw Error(`Can't use more collateral than user's position has (${_stateRepayCollateral}) > ${_stateCollateral})`); + + let _n1 = parseUnits(0, 0); + let _n2 = parseUnits(0, 0); + const { totalBorrowed } = await this.leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed); + const _repayExpected = parseUnits(totalBorrowed, this.borrowed_token.decimals); + try { + _n1 = await lending.contracts[this.addresses.controller].contract.calculate_debt_n1(_stateCollateral - _stateRepayCollateral, _stateDebt - _repayExpected, _N); + _n2 = _n1 + (_N - BigInt(1)); + } catch (e) { + console.log("Full repayment"); + } + + return [_n2, _n1]; + }, + { + promise: true, + maxAge: 5 * 60 * 1000, // 5m + }); + + private async leverageRepayBands(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address = ""): Promise<[number, number]> { + this._checkLeverageZap(); + const [_n2, _n1] = await this._leverageRepayBands(stateCollateral, userCollateral, userBorrowed, address); + + return [Number(_n2), Number(_n1)]; + } + + private async leverageRepayPrices(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, address = ""): Promise { + this._checkLeverageZap(); + const [_n2, _n1] = await this._leverageRepayBands(stateCollateral, userCollateral, userBorrowed, address); + + return await this._getPrices(_n2, _n1); + } + + private async leverageRepayHealth(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, full = true, address = ""): Promise { + this._checkLeverageZap(); + address = _getAddress(address); + const { _borrowed: _stateBorrowed, _debt, _N } = await this._userState(address); + if (_stateBorrowed > BigInt(0)) return "0.0"; + if (!(await this.leverageRepayIsAvailable(stateCollateral, userCollateral, userBorrowed, address))) return "0.0"; + + const { totalBorrowed } = await this.leverageRepayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed); + const _dCollateral = parseUnits(stateCollateral, this.collateral_token.decimals) * BigInt(-1); + const _dDebt = parseUnits(totalBorrowed, this.borrowed_token.decimals) * BigInt(-1); + + if (_debt + _dDebt <= BigInt(0)) return "0.0"; + const contract = lending.contracts[this.addresses.controller].contract; + let _health = await contract.health_calculator(address, _dCollateral, _dDebt, full, _N, lending.constantOptions) as bigint; + _health = _health * BigInt(100); + + return lending.formatUnits(_health); + } + + private async leverageRepayIsApproved(userCollateral: TAmount, userBorrowed: TAmount): Promise { + return await hasAllowance( + [this.collateral_token.address, this.borrowed_token.address], + [userCollateral, userBorrowed], + lending.signerAddress, + lending.constants.ALIASES.leverage_zap + ); + } + + private async leverageRepayApproveEstimateGas (userCollateral: TAmount, userBorrowed: TAmount): Promise { + return await ensureAllowanceEstimateGas( + [this.collateral_token.address, this.borrowed_token.address], + [userCollateral, userBorrowed], + lending.constants.ALIASES.leverage_zap + ); + } + + private async leverageRepayApprove(userCollateral: TAmount, userBorrowed: TAmount): Promise { + return await ensureAllowance( + [this.collateral_token.address, this.borrowed_token.address], + [userCollateral, userBorrowed], + lending.constants.ALIASES.leverage_zap + ); + } + + private async leverageRepayRoute(stateCollateral: TAmount, userCollateral: TAmount, slippage = 0.1): Promise { + const _stateCollateral = parseUnits(stateCollateral, this.collateral_token.decimals); + const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals); + + return await _getRoute1inch(this.addresses.collateral_token, this.addresses.borrowed_token, _stateCollateral + _userCollateral, slippage); + } + + private async _leverageRepay( + stateCollateral: TAmount, + userCollateral: TAmount, + userBorrowed: TAmount, + slippage: number, + estimateGas: boolean + ): Promise { + if (!(await this.userLoanExists())) throw Error("Loan does not exist"); + const _stateCollateral = parseUnits(stateCollateral, this.collateral_token.decimals); + const _userCollateral = parseUnits(userCollateral, this.collateral_token.decimals); + const _userBorrowed = parseUnits(userBorrowed, this.borrowed_token.decimals); + let calldata = "0x"; + if (_stateCollateral + _userCollateral > BigInt(0)) { + calldata = await _getCalldata1inch(this.addresses.collateral_token, this.addresses.borrowed_token, _stateCollateral + _userCollateral, slippage); + } + const contract = lending.contracts[this.addresses.controller].contract; + const gas = await contract.repay_extended.estimateGas( + lending.constants.ALIASES.leverage_zap, + [0, parseUnits(this.id.split("-").slice(-1)[0], 0), _userCollateral, _userBorrowed], + calldata, + { ...lending.constantOptions } + ); + if (estimateGas) return smartNumber(gas); + + await lending.updateFeeData(); + const gasLimit = _mulBy1_3(DIGas(gas)); + + return (await contract.repay_extended( + lending.constants.ALIASES.leverage_zap, + [0, parseUnits(this.id.split("-").slice(-1)[0], 0), _userCollateral, _userBorrowed], + calldata, + { ...lending.options, gasLimit } + )).hash + } + + private async leverageRepayEstimateGas(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, slippage = 0.1): Promise { + this._checkLeverageZap(); + if (!(await this.leverageRepayIsApproved(userCollateral, userBorrowed))) throw Error("Approval is needed for gas estimation"); + return await this._leverageRepay(stateCollateral, userCollateral, userBorrowed, slippage, true) as number; + } + + private async leverageRepay(stateCollateral: TAmount, userCollateral: TAmount, userBorrowed: TAmount, slippage = 0.1): Promise { + this._checkLeverageZap(); + await this.leverageRepayApprove(userCollateral, userBorrowed); + return await this._leverageRepay(stateCollateral, userCollateral, userBorrowed, slippage, false) as string; + } } diff --git a/test/leverageBorrowMore.test.ts b/test/leverageBorrowMore.test.ts new file mode 100644 index 0000000..88e2531 --- /dev/null +++ b/test/leverageBorrowMore.test.ts @@ -0,0 +1,242 @@ +import { assert } from "chai"; +import lending from "../src/index.js"; +import { API_KEY_1INCH } from "./rpcUrls.test.js"; +import { getOneWayMarket, OneWayMarketTemplate } from "../src/markets/index.js"; + + +const ONE_WAY_MARKETS = ['one-way-market-0']; + +const generalTest = (id: string) => { + describe(`${id} leverage borrowMore test`, function () { + let oneWayMarket: OneWayMarketTemplate; + + before(async function () { + oneWayMarket = getOneWayMarket(id); + if (Number(await oneWayMarket.vault.totalLiquidity()) === 0) { + const maxDeposit = Number(await oneWayMarket.vault.maxDeposit()) * 0.7; + await oneWayMarket.vault.deposit(maxDeposit); + } + if (!(await oneWayMarket.userLoanExists())) { + const collateralAmount = 0.5; + const borrowedAmount = 1000; + const N = 10; + const { maxDebt } = await oneWayMarket.leverage.createLoanMaxRecv(collateralAmount, borrowedAmount, N); + const debtAmount = (Number(maxDebt) / 2).toFixed(oneWayMarket.borrowed_token.decimals); + + await oneWayMarket.leverage.createLoan(collateralAmount, borrowedAmount, debtAmount, N, 1); + } + }); + + it('Leverage borrowMore collateral only, debt too high', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0.5; + const borrowedAmount = 0; + const { maxDebt } = await oneWayMarket.leverage.borrowMoreMaxRecv(collateralAmount, borrowedAmount); + const debtAmount = (Number(maxDebt) * 1.004).toFixed(oneWayMarket.borrowed_token.decimals); + + try { + await oneWayMarket.leverage.borrowMore(collateralAmount, borrowedAmount, debtAmount,1); + throw Error("Did not revert"); + } catch (e) { + // @ts-ignore + assert.notEqual(e.message, "Did not revert"); + // @ts-ignore + assert.isTrue(e.message.startsWith('execution reverted: "Debt too high"')); + } + }); + + it('Leverage borrowMore borrowed only, debt too high', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0; + const borrowedAmount = 1000; + const { maxDebt } = await oneWayMarket.leverage.borrowMoreMaxRecv(collateralAmount, borrowedAmount); + const debtAmount = (Number(maxDebt) * 1.004).toFixed(oneWayMarket.borrowed_token.decimals); + + try { + await oneWayMarket.leverage.borrowMore(collateralAmount, borrowedAmount, debtAmount,1); + throw Error("Did not revert"); + } catch (e) { + // @ts-ignore + assert.notEqual(e.message, "Did not revert"); + // @ts-ignore + assert.isTrue(e.message.startsWith('execution reverted: "Debt too high"')); + } + }); + + it('Leverage borrowMore, debt too high', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0.5; + const borrowedAmount = 1000; + const { maxDebt } = await oneWayMarket.leverage.borrowMoreMaxRecv(collateralAmount, borrowedAmount); + const debtAmount = (Number(maxDebt) * 1.004).toFixed(oneWayMarket.borrowed_token.decimals); + + try { + await oneWayMarket.leverage.borrowMore(collateralAmount, borrowedAmount, debtAmount,1); + throw Error("Did not revert"); + } catch (e) { + // @ts-ignore + assert.notEqual(e.message, "Did not revert"); + // @ts-ignore + assert.isTrue(e.message.startsWith('execution reverted: "Debt too high"')); + } + }); + + it('Leverage borrowMore collateral only', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const initialState = await oneWayMarket.userState(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0.5; + const borrowedAmount = 0; + const { maxDebt } = await oneWayMarket.leverage.borrowMoreMaxRecv(collateralAmount, borrowedAmount); + const debtAmount = (Number(maxDebt) / 2).toFixed(oneWayMarket.borrowed_token.decimals); + const borrowMoreBands = await oneWayMarket.leverage.borrowMoreBands(collateralAmount, borrowedAmount, debtAmount); + const borrowMorePrices = await oneWayMarket.leverage.borrowMorePrices(collateralAmount, borrowedAmount, debtAmount); + const borrowMoreFullHealth = await oneWayMarket.leverage.borrowMoreHealth(collateralAmount, borrowedAmount, debtAmount); + const borrowMoreHealth = await oneWayMarket.leverage.borrowMoreHealth(collateralAmount, borrowedAmount, debtAmount, false); + const { totalCollateral } = await oneWayMarket.leverage.borrowMoreExpectedCollateral(collateralAmount, borrowedAmount, debtAmount); + + await oneWayMarket.leverage.borrowMore(collateralAmount, borrowedAmount, debtAmount,1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + const userBands = await oneWayMarket.userBands(); + const userPrices = await oneWayMarket.userPrices(); + const fullHealth = await oneWayMarket.userHealth(); + const health = await oneWayMarket.userHealth(false); + + assert.equal(Number(borrowMoreBands[0]), Number(userBands[0]), 'band 0'); + assert.equal(Number(borrowMoreBands[1]), Number(userBands[1]), 'band 1'); + assert.approximately(Number(borrowMorePrices[0]), Number(userPrices[0]), 0.01, 'price 0'); + assert.approximately(Number(borrowMorePrices[1]), Number(userPrices[1]), 0.01, 'price 1'); + assert.approximately(Number(borrowMoreFullHealth), Number(fullHealth), 0.3, 'full health'); + assert.approximately(Number(borrowMoreHealth), Number(health), 0.3, 'health'); + assert.equal(Number(balances.collateral), Number(initialBalances.collateral) - Number(collateralAmount), 'wallet collateral'); + assert.equal(Number(balances.borrowed), Number(initialBalances.borrowed) - borrowedAmount, 'wallet borrowed'); + const collateralDiff = Number(state.collateral) - Number(initialState.collateral); + assert.isAtMost(Math.abs(collateralDiff - Number(totalCollateral)) / Number(totalCollateral), 0.01, 'state collateral'); + const debtDiff = Number(state.debt) - Number(initialState.debt); + assert.isAtMost(Math.abs(debtDiff - Number(debtAmount)) / Number(debtAmount), 1e-7, 'state debt'); + }); + + it('Leverage borrowMore borrowed only', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const initialState = await oneWayMarket.userState(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0; + const borrowedAmount = 1000; + const { maxDebt } = await oneWayMarket.leverage.borrowMoreMaxRecv(collateralAmount, borrowedAmount); + const debtAmount = (Number(maxDebt) / 2).toFixed(oneWayMarket.borrowed_token.decimals); + const borrowMoreBands = await oneWayMarket.leverage.borrowMoreBands(collateralAmount, borrowedAmount, debtAmount); + const borrowMorePrices = await oneWayMarket.leverage.borrowMorePrices(collateralAmount, borrowedAmount, debtAmount); + const borrowMoreFullHealth = await oneWayMarket.leverage.borrowMoreHealth(collateralAmount, borrowedAmount, debtAmount); + const borrowMoreHealth = await oneWayMarket.leverage.borrowMoreHealth(collateralAmount, borrowedAmount, debtAmount, false); + const { totalCollateral } = await oneWayMarket.leverage.borrowMoreExpectedCollateral(collateralAmount, borrowedAmount, debtAmount); + + await oneWayMarket.leverage.borrowMore(collateralAmount, borrowedAmount, debtAmount,1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + const userBands = await oneWayMarket.userBands(); + const userPrices = await oneWayMarket.userPrices(); + const fullHealth = await oneWayMarket.userHealth(); + const health = await oneWayMarket.userHealth(false); + + assert.equal(Number(borrowMoreBands[0]), Number(userBands[0]), 'band 0'); + assert.equal(Number(borrowMoreBands[1]), Number(userBands[1]), 'band 1'); + assert.approximately(Number(borrowMorePrices[0]), Number(userPrices[0]), 0.01, 'price 0'); + assert.approximately(Number(borrowMorePrices[1]), Number(userPrices[1]), 0.01, 'price 1'); + assert.approximately(Number(borrowMoreFullHealth), Number(fullHealth), 0.3, 'full health'); + assert.approximately(Number(borrowMoreHealth), Number(health), 0.3, 'health'); + assert.equal(Number(balances.collateral), Number(initialBalances.collateral) - Number(collateralAmount), 'wallet collateral'); + assert.equal(Number(balances.borrowed), Number(initialBalances.borrowed) - borrowedAmount, 'wallet borrowed'); + const collateralDiff = Number(state.collateral) - Number(initialState.collateral); + assert.isAtMost(Math.abs(collateralDiff - Number(totalCollateral)) / Number(totalCollateral), 3e-3, 'state collateral'); + const debtDiff = Number(state.debt) - Number(initialState.debt); + assert.isAtMost(Math.abs(debtDiff - Number(debtAmount)) / Number(debtAmount), 1e-7, 'state debt'); + }); + + it('Leverage borrowMore', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const initialState = await oneWayMarket.userState(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0.5; + const borrowedAmount = 1000; + const { maxDebt } = await oneWayMarket.leverage.borrowMoreMaxRecv(collateralAmount, borrowedAmount); + // const debtAmount = (Number(maxDebt) * 0.999).toFixed(oneWayMarket.borrowed_token.decimals); + const debtAmount = maxDebt; + const borrowMoreBands = await oneWayMarket.leverage.borrowMoreBands(collateralAmount, borrowedAmount, debtAmount); + const borrowMorePrices = await oneWayMarket.leverage.borrowMorePrices(collateralAmount, borrowedAmount, debtAmount); + const borrowMoreFullHealth = await oneWayMarket.leverage.borrowMoreHealth(collateralAmount, borrowedAmount, debtAmount); + const borrowMoreHealth = await oneWayMarket.leverage.borrowMoreHealth(collateralAmount, borrowedAmount, debtAmount, false); + const { totalCollateral } = await oneWayMarket.leverage.borrowMoreExpectedCollateral(collateralAmount, borrowedAmount, debtAmount); + + await oneWayMarket.leverage.borrowMore(collateralAmount, borrowedAmount, debtAmount,1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + const userBands = await oneWayMarket.userBands(); + const userPrices = await oneWayMarket.userPrices(); + const fullHealth = await oneWayMarket.userHealth(); + const health = await oneWayMarket.userHealth(false); + + assert.equal(Number(borrowMoreBands[0]), Number(userBands[0]), 'band 0'); + assert.equal(Number(borrowMoreBands[1]), Number(userBands[1]), 'band 1'); + assert.approximately(Number(borrowMorePrices[0]), Number(userPrices[0]), 0.01, 'price 0'); + assert.approximately(Number(borrowMorePrices[1]), Number(userPrices[1]), 0.01, 'price 1'); + assert.approximately(Number(borrowMoreFullHealth), Number(fullHealth), 0.3, 'full health'); + assert.approximately(Number(borrowMoreHealth), Number(health), 0.3, 'health'); + assert.equal(Number(balances.collateral), Number(initialBalances.collateral) - Number(collateralAmount), 'wallet collateral'); + assert.equal(Number(balances.borrowed), Number(initialBalances.borrowed) - borrowedAmount, 'wallet borrowed'); + const collateralDiff = Number(state.collateral) - Number(initialState.collateral); + assert.isAtMost(Math.abs(collateralDiff - Number(totalCollateral)) / Number(totalCollateral), 0.01, 'state collateral'); + const debtDiff = Number(state.debt) - Number(initialState.debt); + assert.isAtMost(Math.abs(debtDiff - Number(debtAmount)) / Number(debtAmount), 1e-7, 'state debt'); + }); + }) +} + +describe('Leverage borrowMore test', async function () { + this.timeout(180000); + + before(async function () { + await lending.init('JsonRpc', {},{ gasPrice: 0 }, API_KEY_1INCH); + await lending.oneWayfactory.fetchMarkets(); + }); + + for (const oneWayMarketId of ONE_WAY_MARKETS) { + generalTest(oneWayMarketId); + } +}) diff --git a/test/leverageCreateLoan.test.ts b/test/leverageCreateLoan.test.ts new file mode 100644 index 0000000..56928c6 --- /dev/null +++ b/test/leverageCreateLoan.test.ts @@ -0,0 +1,232 @@ +import { assert } from "chai"; +import lending from "../src/index.js"; +import { API_KEY_1INCH } from "./rpcUrls.test.js"; +import { getOneWayMarket, OneWayMarketTemplate } from "../src/markets/index.js"; + + +const ONE_WAY_MARKETS = ['one-way-market-0']; + +const generalTest = (id: string) => { + describe(`${id} leverage createLoan test`, function () { + let oneWayMarket: OneWayMarketTemplate; + + before(async function () { + oneWayMarket = getOneWayMarket(id); + if (Number(await oneWayMarket.vault.totalLiquidity()) === 0) { + const maxDeposit = Number(await oneWayMarket.vault.maxDeposit()) * 0.7; + await oneWayMarket.vault.deposit(maxDeposit); + } + }); + + it('Leverage createLoan collateral only, debt too high', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isFalse(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0.5; + const borrowedAmount = 0; + const N = 10; + const { maxDebt } = await oneWayMarket.leverage.createLoanMaxRecv(collateralAmount, borrowedAmount, N); + const debtAmount = (Number(maxDebt) * 1.004).toFixed(oneWayMarket.borrowed_token.decimals); + + try { + await oneWayMarket.leverage.createLoan(collateralAmount, borrowedAmount, debtAmount, N, 1); + throw Error("Did not revert"); + } catch (e) { + // @ts-ignore + assert.notEqual(e.message, "Did not revert"); + // @ts-ignore + assert.isTrue(e.message.startsWith('execution reverted: "Debt too high"')); + } + }); + + it('Leverage createLoan borrowed only, debt too high', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isFalse(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0; + const borrowedAmount = 1000; + const N = 10; + const { maxDebt } = await oneWayMarket.leverage.createLoanMaxRecv(collateralAmount, borrowedAmount, N); + const debtAmount = (Number(maxDebt) * 1.004).toFixed(oneWayMarket.borrowed_token.decimals); + + try { + await oneWayMarket.leverage.createLoan(collateralAmount, borrowedAmount, debtAmount, N, 1); + throw Error("Did not revert"); + } catch (e) { + // @ts-ignore + assert.notEqual(e.message, "Did not revert"); + // @ts-ignore + assert.isTrue(e.message.startsWith('execution reverted: "Debt too high"')); + } + }); + + it('Leverage createLoan, debt too high', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isFalse(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0.5; + const borrowedAmount = 1000; + const N = 10; + const { maxDebt } = await oneWayMarket.leverage.createLoanMaxRecv(collateralAmount, borrowedAmount, N); + const debtAmount = (Number(maxDebt) * 1.004).toFixed(oneWayMarket.borrowed_token.decimals); + + try { + await oneWayMarket.leverage.createLoan(collateralAmount, borrowedAmount, debtAmount, N, 1); + throw Error("Did not revert"); + } catch (e) { + // @ts-ignore + assert.notEqual(e.message, "Did not revert"); + // @ts-ignore + assert.isTrue(e.message.startsWith('execution reverted: "Debt too high"')); + } + }); + + it('Leverage createLoan collateral only', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isFalse(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0.5; + const borrowedAmount = 0; + const N = 10; + const { maxDebt } = await oneWayMarket.leverage.createLoanMaxRecv(collateralAmount, borrowedAmount, N); + const debtAmount = (Number(maxDebt) / 2).toFixed(oneWayMarket.borrowed_token.decimals); + const createLoanBands = await oneWayMarket.leverage.createLoanBands(collateralAmount, borrowedAmount, debtAmount, N); + const createLoanPrices = await oneWayMarket.leverage.createLoanPrices(collateralAmount, borrowedAmount, debtAmount, N); + const createLoanFullHealth = await oneWayMarket.leverage.createLoanHealth(collateralAmount, borrowedAmount, debtAmount, N); + const createLoanHealth = await oneWayMarket.leverage.createLoanHealth(collateralAmount, borrowedAmount, debtAmount, N, false); + const { totalCollateral } = await oneWayMarket.leverage.createLoanExpectedCollateral(collateralAmount, borrowedAmount, debtAmount); + + await oneWayMarket.leverage.createLoan(collateralAmount, borrowedAmount, debtAmount, N, 1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + const userBands = await oneWayMarket.userBands(); + const userPrices = await oneWayMarket.userPrices(); + const fullHealth = await oneWayMarket.userHealth(); + const health = await oneWayMarket.userHealth(false); + + assert.equal(Number(createLoanBands[0]), Number(userBands[0]), 'band 0'); + assert.equal(Number(createLoanBands[1]), Number(userBands[1]), 'band 1'); + assert.approximately(Number(createLoanPrices[0]), Number(userPrices[0]), 0.01, 'price 0'); + assert.approximately(Number(createLoanPrices[1]), Number(userPrices[1]), 0.01, 'price 1'); + assert.approximately(Number(createLoanFullHealth), Number(fullHealth), 0.3, 'full health'); + assert.approximately(Number(createLoanHealth), Number(health), 0.3, 'health'); + assert.equal(Number(balances.collateral), Number(initialBalances.collateral) - Number(collateralAmount), 'wallet collateral'); + assert.equal(Number(balances.borrowed), Number(initialBalances.borrowed) - borrowedAmount, 'wallet borrowed'); + assert.isAtMost(Math.abs(Number(state.collateral) - Number(totalCollateral)) / Number(totalCollateral), 0.01, 'state collateral'); + assert.equal(Number(state.debt), Number(debtAmount), 'state debt'); + + await oneWayMarket.fullRepay(); + }); + + it('Leverage createLoan borrowed only', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isFalse(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0; + const borrowedAmount = 1000; + const N = 10; + const { maxDebt } = await oneWayMarket.leverage.createLoanMaxRecv(collateralAmount, borrowedAmount, N); + const debtAmount = (Number(maxDebt) / 2).toFixed(oneWayMarket.borrowed_token.decimals); + const createLoanBands = await oneWayMarket.leverage.createLoanBands(collateralAmount, borrowedAmount, debtAmount, N); + const createLoanPrices = await oneWayMarket.leverage.createLoanPrices(collateralAmount, borrowedAmount, debtAmount, N); + const createLoanFullHealth = await oneWayMarket.leverage.createLoanHealth(collateralAmount, borrowedAmount, debtAmount, N); + const createLoanHealth = await oneWayMarket.leverage.createLoanHealth(collateralAmount, borrowedAmount, debtAmount, N, false); + const { totalCollateral } = await oneWayMarket.leverage.createLoanExpectedCollateral(collateralAmount, borrowedAmount, debtAmount); + + await oneWayMarket.leverage.createLoan(collateralAmount, borrowedAmount, debtAmount, N, 1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + const userBands = await oneWayMarket.userBands(); + const userPrices = await oneWayMarket.userPrices(); + const fullHealth = await oneWayMarket.userHealth(); + const health = await oneWayMarket.userHealth(false); + + assert.equal(Number(createLoanBands[0]), Number(userBands[0]), 'band 0'); + assert.equal(Number(createLoanBands[1]), Number(userBands[1]), 'band 1'); + assert.approximately(Number(createLoanPrices[0]), Number(userPrices[0]), 0.01, 'price 0'); + assert.approximately(Number(createLoanPrices[1]), Number(userPrices[1]), 0.01, 'price 1'); + assert.approximately(Number(createLoanFullHealth), Number(fullHealth), 0.3, 'full health'); + assert.approximately(Number(createLoanHealth), Number(health), 0.3, 'health'); + assert.equal(Number(balances.collateral), Number(initialBalances.collateral) - Number(collateralAmount), 'wallet collateral'); + assert.equal(Number(balances.borrowed), Number(initialBalances.borrowed) - borrowedAmount, 'wallet borrowed'); + assert.isAtMost(Math.abs(Number(state.collateral) - Number(totalCollateral)) / Number(totalCollateral), 0.01, 'state collateral'); + assert.equal(Number(state.debt), Number(debtAmount), 'state debt'); + + await oneWayMarket.fullRepay(); + }); + + it('Leverage createLoan', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isFalse(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const collateralAmount = 0.5; + const borrowedAmount = 1000; + const N = 10; + const { maxDebt: debtAmount } = await oneWayMarket.leverage.createLoanMaxRecv(collateralAmount, borrowedAmount, N); + const createLoanBands = await oneWayMarket.leverage.createLoanBands(collateralAmount, borrowedAmount, debtAmount, N); + const createLoanPrices = await oneWayMarket.leverage.createLoanPrices(collateralAmount, borrowedAmount, debtAmount, N); + const createLoanFullHealth = await oneWayMarket.leverage.createLoanHealth(collateralAmount, borrowedAmount, debtAmount, N); + const createLoanHealth = await oneWayMarket.leverage.createLoanHealth(collateralAmount, borrowedAmount, debtAmount, N, false); + const { totalCollateral } = await oneWayMarket.leverage.createLoanExpectedCollateral(collateralAmount, borrowedAmount, debtAmount); + + await oneWayMarket.leverage.createLoan(collateralAmount, borrowedAmount, debtAmount, N, 1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + const userBands = await oneWayMarket.userBands(); + const userPrices = await oneWayMarket.userPrices(); + const fullHealth = await oneWayMarket.userHealth(); + const health = await oneWayMarket.userHealth(false); + + assert.equal(Number(createLoanBands[0]), Number(userBands[0]), 'band 0'); + assert.equal(Number(createLoanBands[1]), Number(userBands[1]), 'band 1'); + assert.approximately(Number(createLoanPrices[0]), Number(userPrices[0]), 0.01, 'price 0'); + assert.approximately(Number(createLoanPrices[1]), Number(userPrices[1]), 0.01, 'price 1'); + assert.approximately(Number(createLoanFullHealth), Number(fullHealth), 0.3, 'full health'); + assert.approximately(Number(createLoanHealth), Number(health), 0.3, 'health'); + assert.equal(Number(balances.collateral), Number(initialBalances.collateral) - Number(collateralAmount), 'wallet collateral'); + assert.equal(Number(balances.borrowed), Number(initialBalances.borrowed) - borrowedAmount, 'wallet borrowed'); + assert.isAtMost(Math.abs(Number(state.collateral) - Number(totalCollateral)) / Number(totalCollateral), 0.01, 'state collateral'); + assert.equal(Number(state.debt), Number(debtAmount), 'state debt'); + }); + }) +} + +describe('Leverage createLoan test', async function () { + this.timeout(180000); + + before(async function () { + await lending.init('JsonRpc', {},{ gasPrice: 0 }, API_KEY_1INCH); + await lending.oneWayfactory.fetchMarkets(); + }); + + for (const oneWayMarketId of ONE_WAY_MARKETS) { + generalTest(oneWayMarketId); + } +}) diff --git a/test/leverageRepay.test.ts b/test/leverageRepay.test.ts new file mode 100644 index 0000000..67616c3 --- /dev/null +++ b/test/leverageRepay.test.ts @@ -0,0 +1,235 @@ +import { assert } from "chai"; +import lending from "../src/index.js"; +import { API_KEY_1INCH } from "./rpcUrls.test.js"; +import { getOneWayMarket, OneWayMarketTemplate } from "../src/markets/index.js"; +import { BN } from "../src/utils.js"; + + +const ONE_WAY_MARKETS = ['one-way-market-0']; + +const generalTest = (id: string) => { + describe(`${id} leverage repay test`, function () { + let oneWayMarket: OneWayMarketTemplate; + + before(async function () { + oneWayMarket = getOneWayMarket(id); + if (Number(await oneWayMarket.vault.totalLiquidity()) === 0) { + const maxDeposit = Number(await oneWayMarket.vault.maxDeposit()) * 0.7; + await oneWayMarket.vault.deposit(maxDeposit); + } + if (!(await oneWayMarket.userLoanExists())) { + const collateralAmount = 0.5; + const borrowedAmount = 1000; + const N = 10; + const { maxDebt } = await oneWayMarket.leverage.createLoanMaxRecv(collateralAmount, borrowedAmount, N); + const debtAmount = (Number(maxDebt) / 2).toFixed(oneWayMarket.borrowed_token.decimals); + + await oneWayMarket.leverage.createLoan(collateralAmount, borrowedAmount, debtAmount, N, 1); + } + }); + + it('Leverage repay state collateral only', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const initialState = await oneWayMarket.userState(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const stateCollateralAmount = 0.2; + const collateralAmount = 0; + const borrowedAmount = 0; + const repayBands = await oneWayMarket.leverage.repayBands(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayPrices = await oneWayMarket.leverage.repayPrices(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayFullHealth = await oneWayMarket.leverage.repayHealth(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayHealth = await oneWayMarket.leverage.repayHealth(stateCollateralAmount, collateralAmount, borrowedAmount, false); + const { totalBorrowed } = await oneWayMarket.leverage.repayExpectedBorrowed(stateCollateralAmount, collateralAmount, borrowedAmount); + + await oneWayMarket.leverage.repay(stateCollateralAmount, collateralAmount, borrowedAmount, 1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + const userBands = await oneWayMarket.userBands(); + const userPrices = await oneWayMarket.userPrices(); + const fullHealth = await oneWayMarket.userHealth(); + const health = await oneWayMarket.userHealth(false); + + assert.equal(Number(repayBands[0]), Number(userBands[0]), 'band 0'); + assert.equal(Number(repayBands[1]), Number(userBands[1]), 'band 1'); + assert.approximately(Number(repayPrices[0]), Number(userPrices[0]), 1e-2, 'price 0'); + assert.approximately(Number(repayPrices[1]), Number(userPrices[1]), 1e-2, 'price 1'); + assert.approximately(Number(repayFullHealth), Number(fullHealth), 0.1, 'full health'); + assert.approximately(Number(repayHealth), Number(health), 0.1, 'health'); + assert.equal(Number(balances.collateral), Number(initialBalances.collateral) - Number(collateralAmount), 'wallet collateral'); + assert.equal(Number(balances.borrowed), Number(initialBalances.borrowed) - borrowedAmount, 'wallet borrowed'); + assert.deepStrictEqual(BN(state.collateral), BN(initialState.collateral).minus(stateCollateralAmount),'state collateral'); + const debtDiff = Number(initialState.debt) - Number(state.debt); + assert.isAtMost(Math.abs(debtDiff - Number(totalBorrowed)) / Number(totalBorrowed), 0.01, 'state debt'); + }); + + it('Leverage repay user collateral only', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const initialState = await oneWayMarket.userState(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const stateCollateralAmount = 0; + const collateralAmount = 0.2; + const borrowedAmount = 0; + const repayBands = await oneWayMarket.leverage.repayBands(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayPrices = await oneWayMarket.leverage.repayPrices(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayFullHealth = await oneWayMarket.leverage.repayHealth(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayHealth = await oneWayMarket.leverage.repayHealth(stateCollateralAmount, collateralAmount, borrowedAmount, false); + const { totalBorrowed } = await oneWayMarket.leverage.repayExpectedBorrowed(stateCollateralAmount, collateralAmount, borrowedAmount); + + await oneWayMarket.leverage.repay(stateCollateralAmount, collateralAmount, borrowedAmount, 1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + const userBands = await oneWayMarket.userBands(); + const userPrices = await oneWayMarket.userPrices(); + const fullHealth = await oneWayMarket.userHealth(); + const health = await oneWayMarket.userHealth(false); + + assert.equal(Number(repayBands[0]), Number(userBands[0]), 'band 0'); + assert.equal(Number(repayBands[1]), Number(userBands[1]), 'band 1'); + assert.approximately(Number(repayPrices[0]), Number(userPrices[0]), 1e-2, 'price 0'); + assert.approximately(Number(repayPrices[1]), Number(userPrices[1]), 1e-2, 'price 1'); + assert.approximately(Number(repayFullHealth), Number(fullHealth), 0.1, 'full health'); + assert.approximately(Number(repayHealth), Number(health), 0.1, 'health'); + assert.deepStrictEqual(BN(balances.collateral), BN(initialBalances.collateral).minus(collateralAmount), 'wallet collateral'); + assert.equal(Number(balances.borrowed), Number(initialBalances.borrowed) - borrowedAmount, 'wallet borrowed'); + assert.equal(Number(state.collateral), Number(initialState.collateral) - Number(stateCollateralAmount), 'state collateral'); + const debtDiff = Number(initialState.debt) - Number(state.debt); + assert.isAtMost(Math.abs(debtDiff - Number(totalBorrowed)) / Number(totalBorrowed), 0.01, 'state debt'); + }); + + it('Leverage repay user borrowed only', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const initialState = await oneWayMarket.userState(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const stateCollateralAmount = 0; + const collateralAmount = 0; + const borrowedAmount = 500; + const repayBands = await oneWayMarket.leverage.repayBands(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayPrices = await oneWayMarket.leverage.repayPrices(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayFullHealth = await oneWayMarket.leverage.repayHealth(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayHealth = await oneWayMarket.leverage.repayHealth(stateCollateralAmount, collateralAmount, borrowedAmount, false); + const { totalBorrowed } = await oneWayMarket.leverage.repayExpectedBorrowed(stateCollateralAmount, collateralAmount, borrowedAmount); + + await oneWayMarket.leverage.repay(stateCollateralAmount, collateralAmount, borrowedAmount, 1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + const userBands = await oneWayMarket.userBands(); + const userPrices = await oneWayMarket.userPrices(); + const fullHealth = await oneWayMarket.userHealth(); + const health = await oneWayMarket.userHealth(false); + + assert.equal(Number(repayBands[0]), Number(userBands[0]), 'band 0'); + assert.equal(Number(repayBands[1]), Number(userBands[1]), 'band 1'); + assert.approximately(Number(repayPrices[0]), Number(userPrices[0]), 1e-2, 'price 0'); + assert.approximately(Number(repayPrices[1]), Number(userPrices[1]), 1e-2, 'price 1'); + assert.approximately(Number(repayFullHealth), Number(fullHealth), 0.1, 'full health'); + assert.approximately(Number(repayHealth), Number(health), 0.1, 'health'); + assert.deepStrictEqual(BN(balances.collateral), BN(initialBalances.collateral).minus(collateralAmount), 'wallet collateral'); + assert.equal(Number(balances.borrowed), Number(initialBalances.borrowed) - borrowedAmount, 'wallet borrowed'); + assert.equal(Number(state.collateral), Number(initialState.collateral) - Number(stateCollateralAmount), 'state collateral'); + assert.equal(borrowedAmount, Number(totalBorrowed), 'borrowed amount'); + const debtDiff = Number(initialState.debt) - Number(state.debt); + assert.isAtMost(Math.abs(debtDiff - Number(totalBorrowed)) / Number(totalBorrowed), 1e-7, 'state debt'); + }); + + it('Leverage repay', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const initialState = await oneWayMarket.userState(); + const loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loanExists"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const stateCollateralAmount = 0.3; + const collateralAmount = 0.2; + const borrowedAmount = 500; + const repayBands = await oneWayMarket.leverage.repayBands(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayPrices = await oneWayMarket.leverage.repayPrices(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayFullHealth = await oneWayMarket.leverage.repayHealth(stateCollateralAmount, collateralAmount, borrowedAmount); + const repayHealth = await oneWayMarket.leverage.repayHealth(stateCollateralAmount, collateralAmount, borrowedAmount, false); + const { totalBorrowed } = await oneWayMarket.leverage.repayExpectedBorrowed(stateCollateralAmount, collateralAmount, borrowedAmount); + + await oneWayMarket.leverage.repay(stateCollateralAmount, collateralAmount, borrowedAmount, 1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + const userBands = await oneWayMarket.userBands(); + const userPrices = await oneWayMarket.userPrices(); + const fullHealth = await oneWayMarket.userHealth(); + const health = await oneWayMarket.userHealth(false); + + assert.equal(Number(repayBands[0]), Number(userBands[0]), 'band 0'); + assert.equal(Number(repayBands[1]), Number(userBands[1]), 'band 1'); + assert.approximately(Number(repayPrices[0]), Number(userPrices[0]), 1e-2, 'price 0'); + assert.approximately(Number(repayPrices[1]), Number(userPrices[1]), 1e-2, 'price 1'); + assert.approximately(Number(repayFullHealth), Number(fullHealth), 0.1, 'full health'); + assert.approximately(Number(repayHealth), Number(health), 0.1, 'health'); + assert.equal(Number(balances.collateral), Number(initialBalances.collateral) - Number(collateralAmount), 'wallet collateral'); + assert.equal(Number(balances.borrowed), Number(initialBalances.borrowed) - borrowedAmount, 'wallet borrowed'); + assert.deepStrictEqual(BN(state.collateral), BN(initialState.collateral).minus(stateCollateralAmount), 'state collateral'); + const debtDiff = Number(initialState.debt) - Number(state.debt); + assert.isAtMost(Math.abs(debtDiff - Number(totalBorrowed)) / Number(totalBorrowed), 0.01, 'state debt'); + }); + + it('Leverage full repay', async function () { + const initialBalances = await oneWayMarket.wallet.balances(); + const initialState = await oneWayMarket.userState(); + let loanExists = await oneWayMarket.userLoanExists(); + + assert.isTrue(loanExists, "loan does not exist"); + assert.isAbove(Number(initialBalances.collateral), 0, "collateral > 0"); + assert.isAbove(Number(initialBalances.borrowed), 0, "borrowed > 0"); + + const stateCollateralAmount = (Number(initialState.collateral) / 3).toFixed(oneWayMarket.collateral_token.decimals); + const collateralAmount = (Number(initialState.collateral) / 3).toFixed(oneWayMarket.collateral_token.decimals); + const borrowedAmount = (Number(initialState.debt) / 3).toFixed(oneWayMarket.borrowed_token.decimals); + const { totalBorrowed } = await oneWayMarket.leverage.repayExpectedBorrowed(stateCollateralAmount, collateralAmount, borrowedAmount); + + await oneWayMarket.leverage.repay(stateCollateralAmount, collateralAmount, borrowedAmount, 1); + + const balances = await oneWayMarket.wallet.balances(); + const state = await oneWayMarket.userState(); + loanExists = await oneWayMarket.userLoanExists(); + + assert.isFalse(loanExists, "loan still exists"); + assert.approximately(Number(balances.collateral), Number(initialBalances.collateral) + (Number(initialState.collateral) / 3), 1e-6, 'wallet collateral'); + const borrowedDiff = (Number(balances.borrowed) + Number(initialState.debt)) - (Number(initialBalances.borrowed) - Number(borrowedAmount)); + assert.isTrue(borrowedDiff > 0); // Same sign + assert.isAtMost(Math.abs(borrowedDiff - Number(totalBorrowed)) / Math.abs(borrowedDiff), 0.01, 'wallet borrowed'); + assert.equal(Number(state.collateral), 0, 'state collateral'); + assert.equal(Number(state.borrowed), 0, 'state borrowed'); + assert.equal(Number(state.debt), 0, 'state debt'); + }); + }) +} + +describe('Leverage repay test', async function () { + this.timeout(180000); + + before(async function () { + await lending.init('JsonRpc', {},{ gasPrice: 0 }, API_KEY_1INCH); + await lending.oneWayfactory.fetchMarkets(); + }); + + for (const oneWayMarketId of ONE_WAY_MARKETS) { + generalTest(oneWayMarketId); + } +}) diff --git a/test/readme.test.ts b/test/readme.test.ts index 2fae21a..66925e6 100644 --- a/test/readme.test.ts +++ b/test/readme.test.ts @@ -1,3 +1,4 @@ +import { API_KEY_1INCH } from "./rpcUrls.test.js"; import lending from "../src/index.js"; const generalMethodsTest = async () => { @@ -332,6 +333,133 @@ const selfLiquidationTest = async () => { console.log(await oneWayMarket.userState()); } +const leverageTest = async () => { + await lending.init('JsonRpc', {}, {}, API_KEY_1INCH); + await lending.oneWayfactory.fetchMarkets(); + + const oneWayMarket = lending.getOneWayMarket('one-way-market-0'); + console.log(oneWayMarket.collateral_token, oneWayMarket.borrowed_token); + console.log(await oneWayMarket.wallet.balances()); + + if (Number(await oneWayMarket.vault.totalLiquidity()) === 0) { + const maxDeposit = Number(await oneWayMarket.vault.maxDeposit()) * 0.7; + await oneWayMarket.vault.deposit(maxDeposit); + } + + console.log("\n- Create Loan -\n") + + let userCollateral = 1; + let userBorrowed = 1000; + let debt = 2000; + const range = 10; + const slippage = 0.5; // % + console.log(await oneWayMarket.leverage.maxLeverage(range)); + console.log(await oneWayMarket.leverage.createLoanMaxRecv(userCollateral, userBorrowed, range)); + console.log(await oneWayMarket.leverage.createLoanExpectedCollateral(userCollateral, userBorrowed, debt)); + console.log(await oneWayMarket.leverage.createLoanMaxRange(userCollateral, userBorrowed, debt)); + console.log(await oneWayMarket.leverage.createLoanBands(userCollateral, userBorrowed, debt, range)); + console.log(await oneWayMarket.leverage.createLoanPrices(userCollateral, userBorrowed, debt, range)); + console.log(await oneWayMarket.leverage.createLoanHealth(userCollateral, userBorrowed, debt, range)); + console.log(await oneWayMarket.leverage.createLoanHealth(userCollateral, userBorrowed, debt, range, false)); + console.log(await oneWayMarket.leverage.createLoanIsApproved(userCollateral, userBorrowed)); + console.log(await oneWayMarket.leverage.createLoanApprove(userCollateral, userBorrowed)); + let route = await oneWayMarket.leverage.createLoanRoute(userBorrowed, debt, slippage); + console.log(route); + for (const r of route) { + console.log(r); + console.log(r.part); + console.log(r.hops); + } + + console.log(await oneWayMarket.leverage.createLoan(userCollateral, userBorrowed, debt, range)); + + console.log(await oneWayMarket.wallet.balances()); + console.log(await oneWayMarket.userState()); + console.log(await oneWayMarket.userBands()); + console.log(await oneWayMarket.userPrices()); + console.log(await oneWayMarket.userHealth()); + console.log(await oneWayMarket.userHealth(false)); + + console.log("\n- Borrow More -\n") + + userCollateral = 2; + userBorrowed = 2000; + debt = 10000; + console.log(await oneWayMarket.leverage.borrowMoreMaxRecv(userCollateral, userBorrowed)); + console.log(await oneWayMarket.leverage.borrowMoreExpectedCollateral(userCollateral, userBorrowed, debt)); + console.log(await oneWayMarket.leverage.borrowMoreBands(userCollateral, userBorrowed, debt)); + console.log(await oneWayMarket.leverage.borrowMorePrices(userCollateral, userBorrowed, debt)); + console.log(await oneWayMarket.leverage.borrowMoreHealth(userCollateral, userBorrowed, debt, true)); + console.log(await oneWayMarket.leverage.borrowMoreHealth(userCollateral, userBorrowed, debt, false)); + console.log(await oneWayMarket.leverage.borrowMoreIsApproved(userCollateral, userBorrowed)); + console.log(await oneWayMarket.leverage.borrowMoreApprove(userCollateral, userBorrowed)); + route = await oneWayMarket.leverage.borrowMoreRoute(userBorrowed, debt, slippage); + console.log(route); + for (const r of route) { + console.log(r); + console.log(r.part); + console.log(r.hops); + } + console.log(await oneWayMarket.leverage.borrowMore(userCollateral, userBorrowed, debt, slippage)); + + console.log(await oneWayMarket.wallet.balances()); + console.log(await oneWayMarket.userState()); + console.log(await oneWayMarket.userBands()); + console.log(await oneWayMarket.userPrices()); + console.log(await oneWayMarket.userHealth()); + console.log(await oneWayMarket.userHealth(false)); + + console.log("\n- Repay -\n") + + const stateCollateral = 2; + userCollateral = 1; + userBorrowed = 1500; + console.log(await oneWayMarket.leverage.repayExpectedBorrowed(stateCollateral, userCollateral, userBorrowed)); + console.log(await oneWayMarket.leverage.repayIsFull(stateCollateral, userCollateral, userBorrowed)); + console.log(await oneWayMarket.leverage.repayIsAvailable(stateCollateral, userCollateral, userBorrowed)); + console.log(await oneWayMarket.leverage.repayBands(stateCollateral, userCollateral, userBorrowed)); + console.log(await oneWayMarket.leverage.repayPrices(stateCollateral, userCollateral, userBorrowed)); + console.log(await oneWayMarket.leverage.repayHealth(stateCollateral, userCollateral, userBorrowed, true)); + console.log(await oneWayMarket.leverage.repayHealth(stateCollateral, userCollateral, userBorrowed, false)); + console.log(await oneWayMarket.leverage.repayIsApproved(userCollateral, userBorrowed)); + console.log(await oneWayMarket.leverage.repayApprove(userCollateral, userBorrowed)); + route = await oneWayMarket.leverage.repayRoute(stateCollateral, userCollateral, slippage); + console.log(route); + for (const r of route) { + console.log(r); + console.log(r.part); + console.log(r.hops); + } + + console.log(await oneWayMarket.leverage.repay(stateCollateral, userCollateral, userBorrowed, slippage)); + + console.log(await oneWayMarket.wallet.balances()); + console.log(await oneWayMarket.userState()); + console.log(await oneWayMarket.userBands()); + console.log(await oneWayMarket.userPrices()); + console.log(await oneWayMarket.userHealth()); + console.log(await oneWayMarket.userHealth(false)); +} + +const leverageAllRangesTest = async () => { + await lending.init('JsonRpc', {}, {}, API_KEY_1INCH); + await lending.oneWayfactory.fetchMarkets(); + + const oneWayMarket = lending.getOneWayMarket('one-way-market-0'); + + if (Number(await oneWayMarket.vault.totalLiquidity()) === 0) { + const maxDeposit = Number(await oneWayMarket.vault.maxDeposit()) * 0.7; + await oneWayMarket.vault.deposit(maxDeposit); + } + + const userCollateral = 1; + const userBorrowed = 1000; + const debt = 2000; + console.log(await oneWayMarket.leverage.createLoanMaxRecvAllRanges(userCollateral, userBorrowed)); + console.log(await oneWayMarket.leverage.createLoanBandsAllRanges(userCollateral, userBorrowed, debt)); + console.log(await oneWayMarket.leverage.createLoanPricesAllRanges(userCollateral, userBorrowed, debt)); +} + (async () => { console.log("\n--- generalMethodsTest ---\n") await generalMethodsTest(); @@ -351,4 +479,8 @@ const selfLiquidationTest = async () => { await swapTest(); console.log("\n--- selfLiquidationTest ---\n") await selfLiquidationTest(); + console.log("\n--- leverageTest ---\n") + await leverageTest(); + console.log("\n--- leverageAllRangesTest ---\n") + await leverageAllRangesTest(); })()