Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

patch: Improve autorouter #561

Merged
merged 14 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions core/src/calleeFunctions/CurveLpTokenUniv3Callee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import BigNumber from '../bignumber';
import { getContractAddressByName, getJoinNameByCollateralType } from '../contracts';
import { convertCrvethToEth, CURVE_COIN_INDEX, CURVE_POOL_ADDRESS } from './helpers/curve';
import { convertCollateralToDai, encodePools } from './helpers/uniswapV3';
import { routeToPool } from './helpers/pools';

export const CHARTER_MANAGER_ADDRESS = '0x8377CD01a5834a6EaD3b7efb482f678f2092b77e';

Expand Down Expand Up @@ -38,18 +39,25 @@ const getCalleeData = async function (

const getMarketPrice = async function (
network: string,
_collateral: CollateralConfig,
_marketId: string,
collateral: CollateralConfig,
marketId: string,
collateralAmount: BigNumber
): Promise<BigNumber> {
): Promise<{ price: BigNumber; pools: Pool[] }> {
const marketData = collateral.exchanges[marketId];
if (marketData.callee !== 'CurveLpTokenUniv3Callee') {
throw new Error(`Can not get market price for the "${collateral.ilk}"`);
}
// convert stETH into ETH
const wethAmount = await convertCrvethToEth(network, collateralAmount);

// convert ETH into DAI
const daiAmount = await convertCollateralToDai(network, 'ETH', wethAmount);

// return price per unit
return daiAmount.dividedBy(collateralAmount);
return {
price: daiAmount.dividedBy(collateralAmount),
pools: await routeToPool(network, marketData.route, collateral.symbol),
};
};

const CurveLpTokenUniv3Callee: CalleeFunctions = {
Expand Down
14 changes: 11 additions & 3 deletions core/src/calleeFunctions/UniswapV2CalleeDai.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { CalleeFunctions, CollateralConfig } from '../types';
import type { CalleeFunctions, CollateralConfig, Pool } from '../types';
import { ethers } from 'ethers';
import BigNumber from '../bignumber';
import { getContractAddressByName, getJoinNameByCollateralType } from '../contracts';
import { getUniswapRouteAddressesBySymbol, getRegularTokenExchangeRateBySymbol } from './helpers/uniswapV2';
import { routeToPool } from './helpers/pools';

const getCalleeData = async function (
network: string,
Expand Down Expand Up @@ -30,8 +31,15 @@ const getMarketPrice = async function (
collateral: CollateralConfig,
marketId: string,
amount: BigNumber
): Promise<BigNumber> {
return await getRegularTokenExchangeRateBySymbol(network, collateral.symbol, marketId, amount);
): Promise<{ price: BigNumber; pools: Pool[] }> {
const marketData = collateral.exchanges[marketId];
if (marketData.callee !== 'UniswapV2CalleeDai') {
throw new Error(`Can not get market price for the "${collateral.ilk}"`);
}
return {
price: await getRegularTokenExchangeRateBySymbol(network, collateral.symbol, marketId, amount),
pools: await routeToPool(network, marketData.route, collateral.symbol),
};
};

const UniswapV2CalleeDai: CalleeFunctions = {
Expand Down
4 changes: 2 additions & 2 deletions core/src/calleeFunctions/UniswapV2LpTokenCalleeDai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const getMarketPrice = async function (
collateral: CollateralConfig,
marketId: string,
amount: BigNumber
): Promise<BigNumber> {
): Promise<{ price: BigNumber; pools: undefined }> {
const marketData = collateral.exchanges[marketId];
if (marketData?.callee !== 'UniswapV2LpTokenCalleeDai') {
throw new Error(`"${collateral.symbol}" is not a UniSwap LP token`);
Expand All @@ -58,7 +58,7 @@ const getMarketPrice = async function (
portionOfTheTotalSupply
);
const totalPrice = totalPriceOfToken0.plus(totalPriceOfToken1);
return totalPrice.dividedBy(amount);
return { price: totalPrice.dividedBy(amount), pools: undefined };
};

const UniswapV2CalleeDai: CalleeFunctions = {
Expand Down
45 changes: 29 additions & 16 deletions core/src/calleeFunctions/UniswapV3Callee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getContractAddressByName, getJoinNameByCollateralType } from '../contra
import { convertCollateralToDaiUsingPool, encodePools } from './helpers/uniswapV3';
import { getPools } from '.';
import { routeToPool } from './helpers/pools';
import { getRouteAndGasQuote } from './helpers/uniswapV3';
import { fetchAutoRouteInformation } from './helpers/uniswapAutoRouter';

const getCalleeData = async function (
network: string,
Expand Down Expand Up @@ -40,23 +40,36 @@ const getMarketPrice = async function (
collateral: CollateralConfig,
marketId: string,
collateralAmount: BigNumber
): Promise<BigNumber> {
// convert collateral into DAI
const { route, fees } = await getRouteAndGasQuote(network, collateral.symbol, collateralAmount, marketId);
if (!route) {
throw new Error(`No route found for ${collateral.symbol} to DAI`);
): Promise<{ price: BigNumber; pools: Pool[] }> {
const calleeConfig = collateral.exchanges[marketId];
const isAutorouted = 'automaticRouter' in calleeConfig;
if (calleeConfig?.callee !== 'UniswapV3Callee') {
throw new Error(`getCalleeData called with invalid collateral type "${collateral.ilk}"`);
}
const pools = await routeToPool(network, route, collateral.symbol, fees);
const daiAmount = await convertCollateralToDaiUsingPool(
network,
collateral.symbol,
marketId,
collateralAmount,
pools
);
const { route, fees, totalPriceAdjusted, pools } = isAutorouted
? await fetchAutoRouteInformation(network, collateral.symbol, collateralAmount.toFixed())
: {
route: calleeConfig.route,
fees: undefined,
pools: undefined,
totalPriceAdjusted: undefined,
};

// return price per unit
return daiAmount.dividedBy(collateralAmount);
if (!pools && route) {
const generatedPools = await routeToPool(network, route, collateral.symbol, fees);
const daiAmount = await convertCollateralToDaiUsingPool(
network,
collateral.symbol,
marketId,
collateralAmount,
generatedPools
);
return { price: daiAmount.dividedBy(collateralAmount), pools: generatedPools };
}
if (totalPriceAdjusted && pools) {
return { price: totalPriceAdjusted.dividedBy(collateralAmount), pools: pools };
}
throw new Error(`Failed to compute market data due to lack of information`);
};

const UniswapV2CalleeDai: CalleeFunctions = {
Expand Down
10 changes: 7 additions & 3 deletions core/src/calleeFunctions/WstETHCurveUniv3Callee.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { CalleeFunctions, CollateralConfig } from '../types';
import type { CalleeFunctions, CollateralConfig, Pool } from '../types';
import { ethers } from 'ethers';
import BigNumber from '../bignumber';
import { getContractAddressByName, getJoinNameByCollateralType } from '../contracts';
import { convertStethToEth } from './helpers/curve';
import { convertCollateralToDai, UNISWAP_FEE } from './helpers/uniswapV3';
import { convertWstethToSteth } from './helpers/wsteth';
import { routeToPool } from './helpers/pools';

const getCalleeData = async function (
network: string,
Expand All @@ -29,7 +30,7 @@ const getMarketPrice = async function (
collateral: CollateralConfig,
marketId: string,
collateralAmount: BigNumber
): Promise<BigNumber> {
): Promise<{ price: BigNumber; pools: Pool[] }> {
const marketData = collateral.exchanges[marketId];
if (marketData?.callee !== 'WstETHCurveUniv3Callee') {
throw new Error(`Invalid callee used to get market price for ${collateral.ilk}`);
Expand All @@ -44,7 +45,10 @@ const getMarketPrice = async function (
const daiAmount = await convertCollateralToDai(network, 'ETH', ethAmount);

// return price per unit
return daiAmount.dividedBy(collateralAmount);
return {
price: daiAmount.dividedBy(collateralAmount),
pools: await routeToPool(network, marketData.route, collateral.symbol),
};
};

const UniswapV2CalleeDai: CalleeFunctions = {
Expand Down
14 changes: 13 additions & 1 deletion core/src/calleeFunctions/helpers/uniswapAutoRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,34 @@ const _fetchAutoRouteInformation = async function (
throw new Error('Only V3 routes are supported.');
}
const fees = bestRoute.route.pools.map(pool => pool.fee);
const pools = bestRoute.route.pools.map(pool => {
if (!pool.token1.symbol || !pool.token0.symbol) {
throw new Error(`Could not get symbol for token from autorouter. Pool: ${pool}`);
}
return {
addresses: [pool.token1.address, pool.token0.address],
fee: pool.fee,
routes: [pool.token1.symbol, pool.token0.symbol],
};
});
const quote = new BigNumber(autoRouteData.quote.toFixed());
const quoteGasAdjusted = new BigNumber(autoRouteData.quoteGasAdjusted.toFixed());
return {
totalPrice: new BigNumber(autoRouteData.quote.toFixed(DAI_NUMBER_OF_DIGITS)),
route,
quote,
quoteGasAdjusted,
totalPriceAdjusted: quoteGasAdjusted,
errorMessage: undefined,
fees,
pools,
};
} catch (error: any) {
return {
totalPrice: undefined,
route: undefined,
quote: undefined,
fees: undefined,
pools: undefined,
errorMessage: error.toString(),
};
}
Expand Down
27 changes: 1 addition & 26 deletions core/src/calleeFunctions/helpers/uniswapV3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import getProvider from '../../provider';
import { DAI_NUMBER_OF_DIGITS, MKR_NUMBER_OF_DIGITS } from '../../constants/UNITS';
import { getCollateralConfigBySymbol } from '../../constants/COLLATERALS';
import { getTokenAddressByNetworkAndSymbol } from '../../tokens';
import { CollateralSymbol, Pool } from '../../types';
import { fetchAutoRouteInformation } from './uniswapAutoRouter';
import { Pool } from '../../types';

const UNISWAP_V3_QUOTER_ADDRESS = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6';
export const UNISWAP_FEE = 3000; // denominated in hundredths of a bip
Expand All @@ -32,30 +31,6 @@ export const encodePools = async function (_network: string, pools: Pool[]): Pro
return ethers.utils.solidityPack(types, values);
};

export const getRouteAndGasQuote = async (
network: string,
collateralSymbol: CollateralSymbol,
collateralAmount: BigNumber,
marketId: string
) => {
const collateral = getCollateralConfigBySymbol(collateralSymbol);
const calleeConfig = collateral.exchanges[marketId];
const isAutorouted = 'automaticRouter' in calleeConfig;
if (calleeConfig?.callee !== 'UniswapV3Callee') {
throw new Error(`getCalleeData called with invalid collateral type "${collateral.ilk}"`);
}
if (isAutorouted) {
const { route, quoteGasAdjusted, fees } = await fetchAutoRouteInformation(
network,
collateralSymbol,
collateralAmount.toFixed()
);
return { route, quoteGasAdjusted, fees };
} else {
return { route: calleeConfig.route, quoteGasAdjusted: undefined, fees: undefined };
}
};

export const convertCollateralToDaiUsingPool = async function (
network: string,
collateralSymbol: string,
Expand Down
61 changes: 24 additions & 37 deletions core/src/calleeFunctions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import type {
MarketData,
CollateralConfig,
CollateralSymbol,
RegularCalleeConfig,
UniswapV2LpTokenCalleeConfig,
AutoRouterCalleeConfig,
Pool,
PriceWithPools,
} from '../types';
import memoizee from 'memoizee';
import BigNumber from '../bignumber';
Expand All @@ -19,7 +17,6 @@ import UniswapV3Callee from './UniswapV3Callee';
import rETHCurveUniv3Callee from './rETHCurveUniv3Callee';
import { getCollateralConfigByType, getCollateralConfigBySymbol } from '../constants/COLLATERALS';
import { routeToPool } from './helpers/pools';
import { fetchAutoRouteInformation } from './helpers/uniswapAutoRouter';

const MARKET_PRICE_CACHE_MS = 10 * 1000;

Expand Down Expand Up @@ -56,71 +53,61 @@ export const getCalleeData = async function (
export const getPools = async (
network: string,
collateral: CollateralConfig,
marketId: string,
amount: BigNumber = new BigNumber(1)
marketId: string
): Promise<Pool[] | undefined> => {
const calleeConfig = collateral.exchanges[marketId];
if ('route' in calleeConfig) {
return await routeToPool(network, calleeConfig.route, collateral.symbol);
}
if ('automaticRouter' in calleeConfig) {
const { route, fees } = await fetchAutoRouteInformation(network, collateral.symbol, amount.toFixed());
if (!route) {
throw new Error('No automatic route can be found');
}
return await routeToPool(network, route, collateral.symbol, fees);
throw new Error('This function should not be called for callees whre autorouter is enabled');
}
return undefined;
};

export const enrichCalleeConfigWithPools = async (
network: string,
collateral: CollateralConfig,
marketId: string,
amount: BigNumber
): Promise<UniswapV2LpTokenCalleeConfig | ((RegularCalleeConfig | AutoRouterCalleeConfig) & { pools: Pool[] })> => {
const config = collateral.exchanges[marketId];
if (config.callee === 'UniswapV2LpTokenCalleeDai') {
return { ...config };
}
const pools = await getPools(network, collateral, marketId, amount);
if (pools) {
return { ...config, pools };
}
throw new Error('Failed to get pools');
};

export const getMarketDataById = async function (
network: string,
collateral: CollateralConfig,
marketId: string,
amount: BigNumber = new BigNumber('1')
): Promise<MarketData> {
const calleeConfig = await enrichCalleeConfigWithPools(network, collateral, marketId, amount);
const calleeConfig = collateral.exchanges[marketId];
if (!allCalleeFunctions[calleeConfig?.callee]) {
throw new Error(`Unsupported callee "${calleeConfig?.callee}" for collateral symbol "${collateral.symbol}"`);
}
let marketUnitPrice: BigNumber;
let marketPrice: PriceWithPools;

try {
marketUnitPrice = await allCalleeFunctions[calleeConfig.callee].getMarketPrice(
marketPrice = await allCalleeFunctions[calleeConfig.callee].getMarketPrice(
network,
collateral,
marketId,
amount
);
} catch (error: any) {
const errorMessage = error?.message;
marketUnitPrice = new BigNumber(NaN);
return {
...calleeConfig,
marketUnitPrice,
marketUnitPrice: new BigNumber(NaN),
pools: [],
errorMessage,
};
}
return {
...calleeConfig,
marketUnitPrice: marketUnitPrice ? marketUnitPrice : new BigNumber(NaN),
};
const marketUnitPrice = marketPrice.price;
if (calleeConfig.callee !== 'UniswapV2LpTokenCalleeDai' && marketPrice.pools) {
return {
...calleeConfig,
marketUnitPrice: marketUnitPrice ? marketUnitPrice : new BigNumber(NaN),
pools: marketPrice.pools,
};
}
if (calleeConfig.callee === 'UniswapV2LpTokenCalleeDai') {
return {
...calleeConfig,
marketUnitPrice: marketUnitPrice ? marketUnitPrice : new BigNumber(NaN),
};
}
throw new Error('No pools found where expected');
};

export const getMarketDataRecords = async function (
Expand Down
Loading