Skip to content

Commit

Permalink
refactor: Improve autorouter (#561)
Browse files Browse the repository at this point in the history
  • Loading branch information
KirillDogadin-std authored Feb 7, 2023
1 parent ba9c4e4 commit 4cb58a4
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 97 deletions.
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

0 comments on commit 4cb58a4

Please sign in to comment.