diff --git a/core/package-lock.json b/core/package-lock.json index 30969c1bb..d31ea000c 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -11,6 +11,7 @@ "@nomiclabs/hardhat-ethers": "^2.1.0", "@uniswap/sdk": "^3.0.3", "@uniswap/smart-order-router": "^2.10.0", + "async-await-queue": "^2.1.3", "bignumber.js": "^9.0.1", "date-fns": "^2.28.0", "deep-equal-in-any-order": "^2.0.0", @@ -3098,6 +3099,11 @@ "lodash": "^4.17.14" } }, + "node_modules/async-await-queue": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/async-await-queue/-/async-await-queue-2.1.3.tgz", + "integrity": "sha512-CW1klMSpp8ch6nNwdoux4s8YDc46l8VEvHd8F1Ouai3Jew1TnCcTXarn/kUr8tZUYvPun2GkGV6wzMU659kztw==" + }, "node_modules/async-eventemitter": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", @@ -22253,6 +22259,11 @@ "lodash": "^4.17.14" } }, + "async-await-queue": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/async-await-queue/-/async-await-queue-2.1.3.tgz", + "integrity": "sha512-CW1klMSpp8ch6nNwdoux4s8YDc46l8VEvHd8F1Ouai3Jew1TnCcTXarn/kUr8tZUYvPun2GkGV6wzMU659kztw==" + }, "async-eventemitter": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/async-eventemitter/-/async-eventemitter-0.2.4.tgz", diff --git a/core/package.json b/core/package.json index 199802486..7efdcf251 100644 --- a/core/package.json +++ b/core/package.json @@ -32,6 +32,7 @@ "@nomiclabs/hardhat-ethers": "^2.1.0", "@uniswap/sdk": "^3.0.3", "@uniswap/smart-order-router": "^2.10.0", + "async-await-queue": "^2.1.3", "bignumber.js": "^9.0.1", "date-fns": "^2.28.0", "deep-equal-in-any-order": "^2.0.0", diff --git a/core/src/auctions.ts b/core/src/auctions.ts index 08d5510b4..5966e3944 100644 --- a/core/src/auctions.ts +++ b/core/src/auctions.ts @@ -6,6 +6,7 @@ import type { TakeEvent, MarketData, ExchangeFees, + GetCalleeDataParams, } from './types'; import BigNumber from './bignumber'; import fetchAuctionsByCollateralType, { @@ -337,6 +338,22 @@ export const bidWithDai = async function ( return executeTransaction(network, contractName, 'take', contractParameters, { notifier }); }; +const buildGetCalleeDataParams = (marketData?: MarketData): GetCalleeDataParams | undefined => { + const preloadedPools = marketData && 'pools' in marketData ? marketData.pools : undefined; + const oneInchData = marketData && 'oneInch' in marketData ? marketData.oneInch : undefined; + if (preloadedPools && oneInchData) { + throw new Error('Cannot use both preloaded pools and oneInch data as params to get callee data'); + } + if (preloadedPools) { + return { + pools: preloadedPools, + }; + } + if (oneInchData) { + return { oneInchParams: { txData: oneInchData.calleeData, to: oneInchData.to } }; + } + return undefined; +}; export const bidWithCallee = async function ( network: string, auction: Auction, @@ -346,8 +363,8 @@ export const bidWithCallee = async function ( ): Promise { const calleeAddress = getCalleeAddressByCollateralType(network, auction.collateralType, marketId); const marketData = auction.marketDataRecords?.[marketId]; - const preloadedPools = marketData && 'pools' in marketData ? marketData.pools : undefined; - const calleeData = await getCalleeData(network, auction.collateralType, marketId, profitAddress, preloadedPools); + const params = buildGetCalleeDataParams(marketData); + const calleeData = await getCalleeData(network, auction.collateralType, marketId, profitAddress, params); const contractName = getClipperNameByCollateralType(auction.collateralType); const contractParameters = [ convertNumberTo32Bytes(auction.index), diff --git a/core/src/calleeFunctions/CurveLpTokenUniv3Callee.ts b/core/src/calleeFunctions/CurveLpTokenUniv3Callee.ts index dc0b3c5b7..56478169b 100644 --- a/core/src/calleeFunctions/CurveLpTokenUniv3Callee.ts +++ b/core/src/calleeFunctions/CurveLpTokenUniv3Callee.ts @@ -1,4 +1,4 @@ -import type { CalleeFunctions, CollateralConfig, Pool } from '../types'; +import type { CalleeFunctions, CollateralConfig, GetCalleeDataParams, Pool } from '../types'; import { ethers } from 'ethers'; import BigNumber from '../bignumber'; import { getContractAddressByName, getJoinNameByCollateralType } from '../contracts'; @@ -13,12 +13,13 @@ const getCalleeData = async function ( collateral: CollateralConfig, marketId: string, profitAddress: string, - preloadedPools?: Pool[] + params?: GetCalleeDataParams ): Promise { const marketData = collateral.exchanges[marketId]; if (marketData?.callee !== 'CurveLpTokenUniv3Callee') { throw new Error(`Can not encode route for the "${collateral.ilk}"`); } + const preloadedPools = !!params && 'pools' in params ? params.pools : undefined; if (!preloadedPools) { throw new Error(`Can not encode route for the "${collateral.ilk}" without preloaded pools`); } diff --git a/core/src/calleeFunctions/OneInchCallee.ts b/core/src/calleeFunctions/OneInchCallee.ts new file mode 100644 index 000000000..0aca3cde7 --- /dev/null +++ b/core/src/calleeFunctions/OneInchCallee.ts @@ -0,0 +1,63 @@ +import type { CalleeFunctions, CollateralConfig, GetCalleeDataParams } from '../types'; +import { ethers } from 'ethers'; +import BigNumber from '../bignumber'; +import { getContractAddressByName, getJoinNameByCollateralType } from '../contracts'; +import { getOneinchSwapParameters } from './helpers/oneInch'; +import { DAI_NUMBER_OF_DIGITS } from '../constants/UNITS'; + +const getCalleeData = async function ( + network: string, + collateral: CollateralConfig, + marketId: string, + profitAddress: string, + params?: GetCalleeDataParams +): Promise { + const marketData = collateral.exchanges[marketId]; + if (marketData?.callee !== 'OneInchCallee') { + throw new Error(`getCalleeData called with invalid collateral type "${collateral.ilk}"`); + } + const oneInchParams = !!params && 'oneInchParams' in params ? params.oneInchParams : undefined; + if (!oneInchParams) { + throw new Error(`getCalleeData called with invalid txData`); + } + const joinAdapterAddress = await getContractAddressByName(network, getJoinNameByCollateralType(collateral.ilk)); + const minProfit = 1; + const typesArray = ['address', 'address', 'uint256', 'address', 'address', 'bytes']; + return ethers.utils.defaultAbiCoder.encode(typesArray, [ + profitAddress, + joinAdapterAddress, + minProfit, + ethers.constants.AddressZero, + oneInchParams.to, + ethers.utils.hexDataSlice(oneInchParams.txData, 4), + ]); +}; + +const getMarketPrice = async function ( + network: string, + collateral: CollateralConfig, + marketId: string, + collateralAmount: BigNumber +): Promise<{ price: BigNumber; pools: undefined }> { + // convert collateral into DAI + const collateralIntegerAmount = collateralAmount.shiftedBy(collateral.decimals).toFixed(0); + const { toTokenAmount } = await getOneinchSwapParameters( + network, + collateral.symbol, + collateralIntegerAmount, + marketId + ); + + // return price per unit + return { + price: new BigNumber(toTokenAmount).shiftedBy(-DAI_NUMBER_OF_DIGITS).dividedBy(collateralAmount), + pools: undefined, + }; +}; + +const UniswapV2CalleeDai: CalleeFunctions = { + getCalleeData, + getMarketPrice, +}; + +export default UniswapV2CalleeDai; diff --git a/core/src/calleeFunctions/UniswapV3Callee.ts b/core/src/calleeFunctions/UniswapV3Callee.ts index 6cfe0384d..9d292873c 100644 --- a/core/src/calleeFunctions/UniswapV3Callee.ts +++ b/core/src/calleeFunctions/UniswapV3Callee.ts @@ -1,4 +1,4 @@ -import type { CalleeFunctions, CollateralConfig, Pool } from '../types'; +import type { CalleeFunctions, CollateralConfig, GetCalleeDataParams, Pool } from '../types'; import { ethers } from 'ethers'; import BigNumber from '../bignumber'; import { getContractAddressByName, getJoinNameByCollateralType } from '../contracts'; @@ -12,12 +12,13 @@ const getCalleeData = async function ( collateral: CollateralConfig, marketId: string, profitAddress: string, - preloadedPools?: Pool[] + params?: GetCalleeDataParams ): Promise { const marketData = collateral.exchanges[marketId]; if (marketData?.callee !== 'UniswapV3Callee') { throw new Error(`getCalleeData called with invalid collateral type "${collateral.ilk}"`); } + const preloadedPools = !!params && 'pools' in params ? params.pools : undefined; const pools = preloadedPools || (await getPools(network, collateral, marketId)); if (!pools) { throw new Error(`getCalleeData called with invalid pools`); diff --git a/core/src/calleeFunctions/helpers/oneInch.ts b/core/src/calleeFunctions/helpers/oneInch.ts new file mode 100644 index 000000000..d3761d0e4 --- /dev/null +++ b/core/src/calleeFunctions/helpers/oneInch.ts @@ -0,0 +1,174 @@ +import { ethers } from 'ethers'; +import { getCalleeAddressByCollateralType } from '../../constants/CALLEES'; +import { getCollateralConfigBySymbol } from '../../constants/COLLATERALS'; +import { getErc20SymbolByAddress } from '../../contracts'; +import { getDecimalChainIdByNetworkType, getNetworkConfigByType } from '../../network'; +import { CollateralConfig } from '../../types'; +import BigNumber from '../../bignumber'; +import { getTokenAddressByNetworkAndSymbol } from '../../tokens'; +import { Queue } from 'async-await-queue'; +import memoizee from 'memoizee'; +import { convertETHtoDAI } from '../../fees'; + +const MAX_DELAY_BETWEEN_REQUESTS_MS = 600; +const REQUEST_QUEUE = new Queue(1, MAX_DELAY_BETWEEN_REQUESTS_MS); +const EXPECTED_SIGNATURE = '0x12aa3caf'; // see https://www.4byte.directory/signatures/?bytes4_signature=0x12aa3caf +const SUPPORTED_1INCH_NETWORK_IDS = [1, 56, 137, 10, 42161, 100, 43114]; // see https://help.1inch.io/en/articles/5528619-how-to-use-different-networks-on-1inch +const ONE_DAY_MS = 24 * 60 * 60 * 1000; + +export const getOneInchUrl = (chainId: number) => { + return `https://api.1inch.io/v5.0/${chainId}`; +}; + +interface Protocol { + id: string; + title: string; + img: string; + img_color: string; +} + +interface OneInchToken { + symbol: string; + name: string; + address: string; + decimals: 0; + logoURI: string; +} +interface OneInchSwapRepsonse { + fromToken: OneInchToken; + toToken: OneInchToken; + toTokenAmount: string; + fromTokenAmount: string; + protocols: OneInchSwapRoute[]; + tx: { + from: string; + to: string; + data: string; + value: string; + gasPrice: string; + gas: string; + }; +} +interface LiquiditySourcesResponse { + protocols: Protocol[]; +} +type OneInchSwapRoute = { name: string; part: number; fromTokenAddress: string; toTokenAddress: string }[][]; + +const executeRequestInQueue = async (url: string) => { + const apiRequestSymbol = Symbol(); + await REQUEST_QUEUE.wait(apiRequestSymbol); + const response = await fetch(url).then(res => res.json()); + REQUEST_QUEUE.end(apiRequestSymbol); + return response; +}; + +export const executeOneInchApiRequest = async ( + chainId: number, + endpoint: '/swap' | '/liquidity-sources', + params?: Record +) => { + const oneInchUrl = getOneInchUrl(chainId); + const url = `${oneInchUrl}${endpoint}?${new URLSearchParams(params)}`; + const response = await executeRequestInQueue(url); + if (response.error) { + throw new Error(`failed to receive response from oneinch: ${response.error}`); + } + return response; +}; + +async function _getOneinchValidProtocols(chainId: number) { + // Fetch all supported protocols except for the limit orders + const response: LiquiditySourcesResponse = await executeOneInchApiRequest(chainId, '/liquidity-sources'); + const protocolIds = response.protocols.map(protocol => protocol.id); + return protocolIds.filter(protocolId => !protocolId.toLowerCase().includes('limit')); +} + +export const getOneinchValidProtocols = memoizee(_getOneinchValidProtocols, { + promise: true, + length: 1, + maxAge: ONE_DAY_MS, +}); + +export async function getOneinchSwapParameters( + network: string, + collateralSymbol: string, + amount: string, + marketId: string, + slippage = '1' +): Promise { + const isFork = getNetworkConfigByType(network).isFork; + const chainId = isFork ? 1 : getDecimalChainIdByNetworkType(network); + if (!isFork && !SUPPORTED_1INCH_NETWORK_IDS.includes(chainId)) { + throw new Error(`1inch does not support network ${network}`); + } + const toTokenAddress = await getTokenAddressByNetworkAndSymbol(network, 'DAI'); + const fromTokenAddress = await getTokenAddressByNetworkAndSymbol(network, collateralSymbol); + const calleeAddress = getCalleeAddressByCollateralType( + network, + getCollateralConfigBySymbol(collateralSymbol).ilk, + marketId + ); + // Documentation https://docs.1inch.io/docs/aggregation-protocol/api/swap-params/ + const swapParams = { + fromTokenAddress, + toTokenAddress, + fromAddress: calleeAddress, + amount, + slippage, + allowPartialFill: false, // disable partial fill + disableEstimate: true, // disable eth_estimateGas + compatibilityMode: true, // always receive parameters for the `swap` call + }; + const oneinchResponse = await executeOneInchApiRequest(chainId, '/swap', swapParams); + const functionSignature = ethers.utils.hexDataSlice(oneinchResponse.tx.data, 0, 4); // see https://docs.soliditylang.org/en/develop/abi-spec.html#function-selector + if (functionSignature !== EXPECTED_SIGNATURE) { + throw new Error(`Unexpected 1inch function signature: ${functionSignature}, expected: ${EXPECTED_SIGNATURE}`); + } + return oneinchResponse; +} + +export async function extractPathFromSwapResponseProtocols( + network: string, + oneInchRoutes: OneInchSwapRoute[] +): Promise { + const pathStepsResolves = await Promise.all( + oneInchRoutes[0].map(async route => { + return await Promise.all([ + await getErc20SymbolByAddress(network, route[0].fromTokenAddress), + await getErc20SymbolByAddress(network, route[0].toTokenAddress), + ]); + }) + ); + const path = [pathStepsResolves[0][0]]; + for (const step of pathStepsResolves) { + path.push(step[1]); + } + return path; +} + +export async function getOneInchMarketData( + network: string, + collateral: CollateralConfig, + amount: BigNumber, + marketId: string +) { + const swapData = await getOneinchSwapParameters( + network, + collateral.symbol, + amount.shiftedBy(collateral.decimals).toFixed(0), + marketId + ); + const path = await extractPathFromSwapResponseProtocols(network, swapData.protocols); + const calleeData = swapData.tx.data; + const estimatedGas = swapData.tx.gas; + const exchangeFeeEth = new BigNumber(swapData.tx.gasPrice).multipliedBy(estimatedGas); + const exchangeFeeDai = await convertETHtoDAI(network, exchangeFeeEth); + const to = swapData.tx.to; + return { + path, + exchangeFeeEth, + exchangeFeeDai, + calleeData, + to, + }; +} diff --git a/core/src/calleeFunctions/helpers/uniswapV3.ts b/core/src/calleeFunctions/helpers/uniswapV3.ts index 4fa3d7f64..f5dadae70 100644 --- a/core/src/calleeFunctions/helpers/uniswapV3.ts +++ b/core/src/calleeFunctions/helpers/uniswapV3.ts @@ -6,6 +6,8 @@ import { DAI_NUMBER_OF_DIGITS, MKR_NUMBER_OF_DIGITS } from '../../constants/UNIT import { getCollateralConfigBySymbol } from '../../constants/COLLATERALS'; import { getTokenAddressByNetworkAndSymbol } from '../../tokens'; import { Pool } from '../../types'; +import memoizee from 'memoizee'; +import { MARKET_DATA_RECORDS_CACHE_MS } from '..'; const UNISWAP_V3_QUOTER_ADDRESS = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6'; export const UNISWAP_FEE = 3000; // denominated in hundredths of a bip @@ -54,7 +56,7 @@ export const convertCollateralToDaiUsingPool = async function ( return daiAmount; }; -export const convertSymbolToDai = async function ( +const _convertSymbolToDai = async function ( network: string, symbol: string, amount: BigNumber, @@ -73,6 +75,15 @@ export const convertSymbolToDai = async function ( return daiAmount; }; +export const convertSymbolToDai = memoizee(_convertSymbolToDai, { + promise: true, + length: 4, + maxAge: MARKET_DATA_RECORDS_CACHE_MS, + normalizer: (args: any[]) => { + return JSON.stringify(args); // use normalizer due to BigNumber object being an argument + }, +}); + export const convertDaiToSymbol = async function ( network: string, symbol: string, diff --git a/core/src/calleeFunctions/index.ts b/core/src/calleeFunctions/index.ts index ce81f7b1a..785e6bffc 100644 --- a/core/src/calleeFunctions/index.ts +++ b/core/src/calleeFunctions/index.ts @@ -6,6 +6,7 @@ import type { CollateralSymbol, Pool, PriceWithPools, + GetCalleeDataParams, } from '../types'; import memoizee from 'memoizee'; import BigNumber from '../bignumber'; @@ -14,11 +15,13 @@ import UniswapV2LpTokenCalleeDai from './UniswapV2LpTokenCalleeDai'; import WstETHCurveUniv3Callee from './WstETHCurveUniv3Callee'; import CurveLpTokenUniv3Callee from './CurveLpTokenUniv3Callee'; import UniswapV3Callee from './UniswapV3Callee'; +import OneInchCallee from './OneInchCallee'; import rETHCurveUniv3Callee from './rETHCurveUniv3Callee'; import { getCollateralConfigByType, getCollateralConfigBySymbol } from '../constants/COLLATERALS'; import { routeToPool } from './helpers/pools'; +import { getOneInchMarketData } from './helpers/oneInch'; -const MARKET_DATA_RECORDS_CACHE_MS = 29 * 1000; +export const MARKET_DATA_RECORDS_CACHE_MS = 29 * 1000; const allCalleeFunctions: Record = { UniswapV2CalleeDai, @@ -27,6 +30,7 @@ const allCalleeFunctions: Record = { CurveLpTokenUniv3Callee, UniswapV3Callee, rETHCurveUniv3Callee, + OneInchCallee, }; export const getCalleeData = async function ( @@ -34,7 +38,7 @@ export const getCalleeData = async function ( collateralType: string, marketId: string, profitAddress: string, - preloadedPools?: Pool[] + params?: GetCalleeDataParams ): Promise { const collateral = getCollateralConfigByType(collateralType); const marketData = collateral.exchanges[marketId]; @@ -46,7 +50,7 @@ export const getCalleeData = async function ( collateral, marketId, profitAddress, - preloadedPools + params ); }; @@ -75,24 +79,31 @@ const _getMarketDataById = async function ( if (!allCalleeFunctions[calleeConfig?.callee]) { throw new Error(`Unsupported callee "${calleeConfig?.callee}" for collateral symbol "${collateral.symbol}"`); } - let marketPrice: PriceWithPools; - + let marketPriceResult: { marketPrice: PriceWithPools; errorMessage?: string }; try { - marketPrice = await allCalleeFunctions[calleeConfig.callee].getMarketPrice( + const marketPrice = await allCalleeFunctions[calleeConfig.callee].getMarketPrice( network, collateral, marketId, amount ); + marketPriceResult = { marketPrice }; } catch (error: any) { - const errorMessage = error?.message; + marketPriceResult = { + marketPrice: { price: new BigNumber(NaN), pools: undefined }, + errorMessage: error?.message, + }; + } + if (calleeConfig.callee === 'OneInchCallee') { + const apiExchangeData = await getOneInchMarketData(network, collateral, amount, marketId); return { ...calleeConfig, - marketUnitPrice: new BigNumber(NaN), - pools: [], - errorMessage, + marketUnitPrice: marketPriceResult.marketPrice.price, + errorMessage: marketPriceResult.errorMessage, + oneInch: apiExchangeData, }; } + const { marketPrice } = marketPriceResult; const marketUnitPrice = marketPrice.price; if (calleeConfig.callee !== 'UniswapV2LpTokenCalleeDai' && marketPrice.pools) { return { diff --git a/core/src/calleeFunctions/rETHCurveUniv3Callee.ts b/core/src/calleeFunctions/rETHCurveUniv3Callee.ts index 789e05eec..0382c0576 100644 --- a/core/src/calleeFunctions/rETHCurveUniv3Callee.ts +++ b/core/src/calleeFunctions/rETHCurveUniv3Callee.ts @@ -1,5 +1,5 @@ import { ethers } from 'ethers'; -import type { CalleeFunctions, CollateralConfig, Pool } from '../types'; +import type { CalleeFunctions, CollateralConfig, GetCalleeDataParams, Pool } from '../types'; import BigNumber from '../bignumber'; import { getContractAddressByName, getJoinNameByCollateralType } from '../contracts'; import { convertCollateralToDai, encodePools } from './helpers/uniswapV3'; @@ -14,12 +14,13 @@ const getCalleeData = async function ( collateral: CollateralConfig, marketId: string, profitAddress: string, - preloadedPools?: Pool[] + params?: GetCalleeDataParams ): Promise { const calleeConfig = collateral.exchanges[marketId]; if (calleeConfig?.callee !== 'rETHCurveUniv3Callee') { throw new Error(`Can not encode route for the "${collateral.ilk}"`); } + const preloadedPools = !!params && 'pools' in params ? params.pools : undefined; if (!preloadedPools) { throw new Error(`Can not encode route for the "${collateral.ilk}" without preloaded pools`); } diff --git a/core/src/constants/CALLEES.ts b/core/src/constants/CALLEES.ts index f505ffdcf..45303f1df 100644 --- a/core/src/constants/CALLEES.ts +++ b/core/src/constants/CALLEES.ts @@ -10,6 +10,7 @@ const CALLEES: Record = { CurveLpTokenUniv3Callee: '0x71f2198849F3B1372EA90c079BD634928583f2d2', UniswapV3Callee: '0xdB9C76109d102d2A1E645dCa3a7E671EBfd8e11A', rETHCurveUniv3Callee: '0x7cdAb0fE16efb1EFE89e53B141347D7F299d6610', + OneInchCallee: '0x19c916CDAFB41FAdd4CEd3dCf412e0302291563A', }, '0x5': { UniswapV2CalleeDai: '0x6d9139ac89ad2263f138633de20e47bcae253938', diff --git a/core/src/constants/COLLATERALS.ts b/core/src/constants/COLLATERALS.ts index f2e06032e..e6796049e 100644 --- a/core/src/constants/COLLATERALS.ts +++ b/core/src/constants/COLLATERALS.ts @@ -34,6 +34,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -55,6 +58,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -76,6 +82,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -97,6 +106,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -135,6 +147,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: [], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -156,6 +171,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: [], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -177,6 +195,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: [], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -198,6 +219,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITHOUT_NEXT_PRICE, }, @@ -219,6 +243,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -240,6 +267,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -261,6 +291,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -282,6 +315,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -303,6 +339,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITHOUT_NEXT_PRICE, @@ -325,6 +364,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -346,6 +388,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITHOUT_NEXT_PRICE, }, @@ -367,6 +412,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -388,6 +436,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITHOUT_NEXT_PRICE, }, @@ -409,6 +460,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITHOUT_NEXT_PRICE, }, @@ -430,6 +484,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -451,6 +508,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -472,6 +532,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -493,6 +556,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -514,6 +580,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -535,6 +604,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, @@ -556,6 +628,9 @@ const COLLATERALS: Record = { callee: 'UniswapV2CalleeDai', route: ['ETH'], }, + '1inch': { + callee: 'OneInchCallee', + }, }, oracle: CONFIG_WITH_NEXT_PRICE, }, diff --git a/core/src/contracts.ts b/core/src/contracts.ts index 14da59746..8f9e2e079 100644 --- a/core/src/contracts.ts +++ b/core/src/contracts.ts @@ -34,6 +34,8 @@ import PROXY_FACTORY from './abis/PROXY_FACTORY.json'; import PROXY_ACTIONS from './abis/PROXY_ACTIONS.json'; import MCD_PAUSE from './abis/MCD_PAUSE.json'; +const ERC20_SYMBOL_CALL_CACHE_TIME_MS = 1000 * 60 * 60 * 24; // 1 day + export const getClipperNameByCollateralType = function (collateralType: string): string { const suffix = collateralType.toUpperCase().replace('-', '_'); return `MCD_CLIP_${suffix}`; @@ -134,4 +136,15 @@ export const getContractValue = async function ( return new BigNumber(variableHex._hex).shiftedBy(-decimals); }; +const _getErc20SymbolByAddress = async function (network: string, address: string): Promise { + const contract = await getErc20Contract(network, address); + return await contract.symbol(); +}; + +export const getErc20SymbolByAddress = memoizee(_getErc20SymbolByAddress, { + promise: true, + length: 2, + maxAge: ERC20_SYMBOL_CALL_CACHE_TIME_MS, +}); + export default getContract; diff --git a/core/src/fees.ts b/core/src/fees.ts index 4654bdb86..b497a5205 100644 --- a/core/src/fees.ts +++ b/core/src/fees.ts @@ -1,9 +1,10 @@ import type { Auction, AuctionTransaction, TransactionFees, VaultTransactionFees, ExchangeFees } from './types'; import BigNumber from './bignumber'; -import { getMarketPrice } from './calleeFunctions'; import { getGasPriceForUI } from './gas'; import getSigner from './signer'; import { getCollateralAuthorizationStatus, getWalletAuthorizationStatus } from './authorizations'; +import { convertSymbolToDai } from './calleeFunctions/helpers/uniswapV3'; +import { ETH_NUMBER_OF_DIGITS } from './constants/UNITS'; export const BID_TRANSACTION_GAS_LIMIT = 145438; export const SWAP_TRANSACTION_GAS_LIMIT = 722651; @@ -12,7 +13,7 @@ export const RESTART_TRANSACTION_GAS_LIMIT = 209182; export const LIQUIDATION_TRANSACTION_GAS_LIMIT = 446658; export const convertETHtoDAI = async function (network: string, eth: BigNumber): Promise { - const exchangeRate = await getMarketPrice(network, 'ETH'); + const exchangeRate = await convertSymbolToDai(network, 'ETH', new BigNumber(1), ETH_NUMBER_OF_DIGITS); return eth.multipliedBy(exchangeRate); }; diff --git a/core/src/surplus.ts b/core/src/surplus.ts index 15ea8499c..ce2dcb528 100644 --- a/core/src/surplus.ts +++ b/core/src/surplus.ts @@ -12,11 +12,17 @@ import BigNumber from './bignumber'; import getContract from './contracts'; import { Contract } from 'ethers'; import getNetworkDate from './date'; -import { MKR_NUMBER_OF_DIGITS, RAD, RAD_NUMBER_OF_DIGITS, WAD, WAD_NUMBER_OF_DIGITS } from './constants/UNITS'; +import { + ETH_NUMBER_OF_DIGITS, + MKR_NUMBER_OF_DIGITS, + RAD, + RAD_NUMBER_OF_DIGITS, + WAD, + WAD_NUMBER_OF_DIGITS, +} from './constants/UNITS'; import executeTransaction from './execute'; import { getGasPriceForUI } from './gas'; -import { getMarketPrice } from './calleeFunctions'; -import { convertMkrToDai } from './calleeFunctions/helpers/uniswapV3'; +import { convertMkrToDai, convertSymbolToDai } from './calleeFunctions/helpers/uniswapV3'; const getSurplusAuctionLastIndex = async (contract: Contract): Promise => { const auctionsQuantityBinary = await contract.kicks(); @@ -168,7 +174,7 @@ export const collectSurplusAuction = async function (network: string, auctionInd const getSurplusTransactionFees = async function (network: string): Promise { const gasPrice = await getGasPriceForUI(network); - const exchangeRate = await getMarketPrice(network, 'ETH'); + const exchangeRate = await convertSymbolToDai(network, 'ETH', new BigNumber(1), ETH_NUMBER_OF_DIGITS); const restartTransactionFeeEth = gasPrice.multipliedBy(80563); const allowanceTransactionFeeEth = gasPrice.multipliedBy(48373); diff --git a/core/src/types.ts b/core/src/types.ts index 79828ccb6..d6c3944f3 100644 --- a/core/src/types.ts +++ b/core/src/types.ts @@ -93,6 +93,10 @@ export declare interface AutoRouterCalleeConfig { automaticRouter: true; } +export declare interface OneInchCalleeConfig { + callee: 'OneInchCallee'; +} + export declare interface UniswapV2LpTokenCalleeConfig { callee: 'UniswapV2LpTokenCalleeDai'; token0: string; @@ -123,18 +127,32 @@ export declare interface MarketDataRegular extends MarketDataBase { pools: Pool[]; } +export declare interface MarketDataOneInch extends MarketDataBase { + oneInch: { + to: string; + path: string[]; + calleeData: string; + exchangeFeeEth: BigNumber; + exchangeFeeDai: BigNumber; + }; +} + export declare interface MarketDataUniswapV2LpToken extends MarketDataBase, Omit {} -export type MarketData = MarketDataRegular | MarketDataUniswapV2LpToken; +export type MarketData = MarketDataRegular | MarketDataUniswapV2LpToken | MarketDataOneInch; export declare interface ValueSlotAddressAndOffset { slot: string; offset: number; } -export type CalleeConfig = RegularCalleeConfig | AutoRouterCalleeConfig | UniswapV2LpTokenCalleeConfig; +export type CalleeConfig = + | RegularCalleeConfig + | AutoRouterCalleeConfig + | UniswapV2LpTokenCalleeConfig + | OneInchCalleeConfig; export declare interface CollateralConfig { title: string; ilk: string; @@ -179,6 +197,7 @@ export declare interface CalleeAddresses { CurveLpTokenUniv3Callee?: string; UniswapV3Callee?: string; rETHCurveUniv3Callee?: string; + OneInchCallee?: string; } export type CalleeNames = keyof CalleeAddresses; @@ -194,7 +213,7 @@ export declare interface CalleeFunctions { collateral: CollateralConfig, marketId: string, profitAddress: string, - pools?: Pool[] + params?: GetCalleeDataParams ) => Promise; getMarketPrice: ( network: string, @@ -407,6 +426,7 @@ export declare interface VaultTransactionLiquidated extends VaultBase { state: 'liquidated'; pastLiquidations: LiquidationEvent[]; } +export declare type GetCalleeDataParams = { pools: Pool[] } | { oneInchParams: { txData: string; to: string } }; export declare interface VaultTransactionBase extends Vault, VaultTransactionFees, OraclePrices { liquidationRatio: BigNumber; diff --git a/frontend/components/auction/collateral/MarketPriceSelection.vue b/frontend/components/auction/collateral/MarketPriceSelection.vue index 2ebac715f..bb1f07544 100644 --- a/frontend/components/auction/collateral/MarketPriceSelection.vue +++ b/frontend/components/auction/collateral/MarketPriceSelection.vue @@ -19,10 +19,13 @@ {{ id }} - {{ formatRouteFromPools(marketData ? marketData.pools : undefined) }} + {{ getRouteFromMarketData(marketData) }} -
+
+ Error: {{ marketData.errorMessage }} +
+