Skip to content

Commit

Permalink
feat: CRVV1ETHSTETH callee support (#156)
Browse files Browse the repository at this point in the history
  • Loading branch information
valiafetisov authored Apr 14, 2022
1 parent c61eccb commit 7feb3f3
Show file tree
Hide file tree
Showing 15 changed files with 219 additions and 59 deletions.
4 changes: 4 additions & 0 deletions chaos-proxy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const PORT = parseInt(process.env.PORT || '8545');
throw new Error('CHAOSLABS_SIMULATIONS is not set');
}
const simulationId = await chooseSimulationId(CHAOSLABS_SIMULATIONS);
if (!simulationId) {
console.info('Exiting since no simulation was selected');
return;
}
const targetURL = await getSimulationUrl(CHAOSLABS_ACCESS_TOKEN, simulationId);
await startProxy(targetURL, PORT);
})();
53 changes: 53 additions & 0 deletions core/src/calleeFunctions/CurveLpTokenUniv3Callee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { CalleeFunctions, CollateralConfig } from '../types';
import { ethers } from 'ethers';
import BigNumber from '../bignumber';
import { getContractAddressByName, getJoinNameByCollateralType } from '../contracts';
import { convertCrvethToEth, CURVE_COIN_INDEX, CURVE_POOL_ADDRESS } from './helpers/curve';
import { encodeRoute, convertCollateralToDai } from './helpers/uniswapV3';

export const CHARTER_MANAGER_ADDRESS = '0x8377CD01a5834a6EaD3b7efb482f678f2092b77e';

const getCalleeData = async function (
network: string,
collateral: CollateralConfig,
profitAddress: string
): Promise<string> {
if (collateral.exchange.callee !== 'CurveLpTokenUniv3Callee') {
throw new Error(`Can not encode route for the "${collateral.ilk}"`);
}
const route = await encodeRoute(network, collateral.exchange.route);
const curveData = [CURVE_POOL_ADDRESS, CURVE_COIN_INDEX];
const joinAdapterAddress = await getContractAddressByName(network, getJoinNameByCollateralType(collateral.ilk));
const minProfit = 0;
const typesArray = ['address', 'address', 'uint256', 'bytes', 'address', 'tuple(address,uint256)'];
return ethers.utils.defaultAbiCoder.encode(typesArray, [
profitAddress,
joinAdapterAddress,
minProfit,
route,
CHARTER_MANAGER_ADDRESS,
curveData,
]);
};

const getMarketPrice = async function (
network: string,
_collateral: CollateralConfig,
collateralAmount: BigNumber
): Promise<BigNumber> {
// 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);
};

const UniswapV2CalleeDai: CalleeFunctions = {
getCalleeData,
getMarketPrice,
};

export default UniswapV2CalleeDai;
42 changes: 10 additions & 32 deletions core/src/calleeFunctions/WstETHCurveUniv3Callee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@ import type { CalleeFunctions, CollateralConfig } from '../types';
import { ethers } from 'ethers';
import BigNumber from '../bignumber';
import getContract, { getContractAddressByName, getJoinNameByCollateralType } from '../contracts';
import getProvider from '../provider';
import CURVE_POOL_ABI from '../abis/CURVE_POOL.json';
import { abi as UNISWAP_V3_QUOTER_ABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json';
import { DAI_NUMBER_OF_DIGITS } from '../constants/UNITS';

const CURVE_POOL_ADDRESS = '0xDC24316b9AE028F1497c275EB9192a3Ea0f67022';
const UNISWAP_V3_QUOTER_ADDRESS = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6';
const UNISWAP_FEE = 3000; // denominated in hundredths of a bip
import { ETH_NUMBER_OF_DIGITS } from '../constants/UNITS';
import { convertStethToEth } from './helpers/curve';
import { convertCollateralToDai, UNISWAP_FEE } from './helpers/uniswapV3';

const getCalleeData = async function (
network: string,
Expand All @@ -31,39 +26,22 @@ const getCalleeData = async function (
const getMarketPrice = async function (
network: string,
collateral: CollateralConfig,
amount: BigNumber
collateralAmount: BigNumber
): Promise<BigNumber> {
// convert wstETH into stETH
const collateralContract = await getContract(network, collateral.symbol);
const collateralIntegerAmount = amount.shiftedBy(collateral.decimals).toFixed(0);
const collateralIntegerAmount = collateralAmount.shiftedBy(collateral.decimals).toFixed(0);
const stethIntegerAmount = await collateralContract.getStETHByWstETH(collateralIntegerAmount);
const stethAmount = new BigNumber(stethIntegerAmount._hex).shiftedBy(-ETH_NUMBER_OF_DIGITS);

// convert stETH into ETH
const provider = await getProvider(network);
const curvePoolContract = await new ethers.Contract(
CURVE_POOL_ADDRESS,
CURVE_POOL_ABI as ethers.ContractInterface,
provider
);
const wethIntegerAmount = await curvePoolContract.get_dy(1, 0, stethIntegerAmount);
const ethAmount = await convertStethToEth(network, stethAmount);

// get uniswap quotes
const uniswapV3quoterContract = await new ethers.Contract(
UNISWAP_V3_QUOTER_ADDRESS,
UNISWAP_V3_QUOTER_ABI,
provider
);
const daiIntegerAmount = await uniswapV3quoterContract.callStatic.quoteExactInputSingle(
await getContractAddressByName(network, 'ETH'),
await getContractAddressByName(network, 'MCD_DAI'),
UNISWAP_FEE,
wethIntegerAmount,
0
);
const daiAmount = new BigNumber(daiIntegerAmount._hex).shiftedBy(-DAI_NUMBER_OF_DIGITS);
// convert ETH into DAI
const daiAmount = await convertCollateralToDai(network, 'ETH', ethAmount);

// return price per unit
return daiAmount.dividedBy(amount);
return daiAmount.dividedBy(collateralAmount);
};

const UniswapV2CalleeDai: CalleeFunctions = {
Expand Down
29 changes: 29 additions & 0 deletions core/src/calleeFunctions/helpers/curve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ethers } from 'ethers';
import BigNumber from '../../bignumber';
import getProvider from '../../provider';
import CURVE_POOL_ABI from '../../abis/CURVE_POOL.json';
import { ETH_NUMBER_OF_DIGITS } from '../../constants/UNITS';

export const CURVE_POOL_ADDRESS = '0xDC24316b9AE028F1497c275EB9192a3Ea0f67022';
export const CURVE_COIN_INDEX = 0;

const getCurvePollContract = async function (network: string): Promise<ethers.Contract> {
const provider = await getProvider(network);
return new ethers.Contract(CURVE_POOL_ADDRESS, CURVE_POOL_ABI as ethers.ContractInterface, provider);
};

export const convertStethToEth = async function (network: string, stethAmount: BigNumber): Promise<BigNumber> {
const curvePoolContract = await getCurvePollContract(network);
const stethIntegerAmount = stethAmount.shiftedBy(ETH_NUMBER_OF_DIGITS).toFixed(0);
const wethIntegerAmount = await curvePoolContract.get_dy(1, CURVE_COIN_INDEX, stethIntegerAmount);
return new BigNumber(wethIntegerAmount._hex).shiftedBy(-ETH_NUMBER_OF_DIGITS);
};

export const convertCrvethToEth = async function (network: string, stethAmount: BigNumber) {
const curvePoolContract = await getCurvePollContract(network);
const stethIntegerAmount = stethAmount.shiftedBy(ETH_NUMBER_OF_DIGITS).toFixed(0);
const wethIntegerAmount = await curvePoolContract.calc_withdraw_one_coin(stethIntegerAmount, CURVE_COIN_INDEX, {
gasLimit: 1000000,
});
return new BigNumber(wethIntegerAmount._hex).shiftedBy(-ETH_NUMBER_OF_DIGITS);
};
51 changes: 51 additions & 0 deletions core/src/calleeFunctions/helpers/uniswapV3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ethers } from 'ethers';
import { abi as UNISWAP_V3_QUOTER_ABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/Quoter.sol/Quoter.json';
import BigNumber from '../../bignumber';
import getProvider from '../../provider';
import { getContractAddressByName } from '../../contracts';
import { DAI_NUMBER_OF_DIGITS } from '../../constants/UNITS';
import { getCollateralConfigBySymbol } from '../../constants/COLLATERALS';

const UNISWAP_V3_QUOTER_ADDRESS = '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6';
export const UNISWAP_FEE = 3000; // denominated in hundredths of a bip

export const encodeRoute = async function (network: string, collateralSymbols: string[]): Promise<string> {
const types = [] as string[];
const values = [] as Array<string | number>;

for (const collateralSymbol of collateralSymbols) {
types.push('address');
values.push(await getContractAddressByName(network, collateralSymbol));

types.push('uint24');
values.push(UNISWAP_FEE);
}

types.push('address');
values.push(await getContractAddressByName(network, 'MCD_DAI'));
return ethers.utils.solidityPack(types, values);
};

export const convertCollateralToDai = async function (
network: string,
collateralSymbol: string,
collateralAmount: BigNumber
): Promise<BigNumber> {
const provider = await getProvider(network);
const uniswapV3quoterContract = await new ethers.Contract(
UNISWAP_V3_QUOTER_ADDRESS,
UNISWAP_V3_QUOTER_ABI,
provider
);
const collateral = await getCollateralConfigBySymbol(collateralSymbol);
const collateralIntegerAmount = collateralAmount.shiftedBy(collateral.decimals).toFixed(0);
const daiIntegerAmount = await uniswapV3quoterContract.callStatic.quoteExactInputSingle(
await getContractAddressByName(network, collateralSymbol),
await getContractAddressByName(network, 'MCD_DAI'),
UNISWAP_FEE,
collateralIntegerAmount,
0
);
const daiAmount = new BigNumber(daiIntegerAmount._hex).shiftedBy(-DAI_NUMBER_OF_DIGITS);
return daiAmount;
};
2 changes: 2 additions & 0 deletions core/src/calleeFunctions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import BigNumber from '../bignumber';
import UniswapV2CalleeDai from './UniswapV2CalleeDai';
import UniswapV2LpTokenCalleeDai from './UniswapV2LpTokenCalleeDai';
import WstETHCurveUniv3Callee from './WstETHCurveUniv3Callee';
import CurveLpTokenUniv3Callee from './CurveLpTokenUniv3Callee';
import { getCollateralConfigByType, getCollateralConfigBySymbol } from '../constants/COLLATERALS';

const MARKET_PRICE_CACHE_MS = 10 * 1000;
Expand All @@ -12,6 +13,7 @@ const allCalleeFunctions: Record<CalleeNames, CalleeFunctions> = {
UniswapV2CalleeDai,
UniswapV2LpTokenCalleeDai,
WstETHCurveUniv3Callee,
CurveLpTokenUniv3Callee,
};

export const getCalleeData = async function (
Expand Down
1 change: 1 addition & 0 deletions core/src/constants/CALLEES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const CALLEES: Record<string, CalleeAddresses | undefined> = {
UniswapV2CalleeDai: '0x49399BB0Fcb52b32aB5A0909206BFf7B54FF80b3',
UniswapV2LpTokenCalleeDai: '0x74893C37beACf205507ea794470b13DE06294220',
WstETHCurveUniv3Callee: '0xC2D837173592194131827775a6Cd88322a98C825',
CurveLpTokenUniv3Callee: '0x71f2198849F3B1372EA90c079BD634928583f2d2',
},
'0x5': {
UniswapV2CalleeDai: '0x6d9139ac89ad2263f138633de20e47bcae253938',
Expand Down
50 changes: 30 additions & 20 deletions core/src/constants/COLLATERALS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,26 @@ const COLLATERALS: Record<string, CollateralConfig> = {
route: ['ETH'],
},
},
'WBTC-B': {
title: 'Wrapped BTC',
ilk: 'WBTC-B',
symbol: 'WBTC',
decimals: 8,
exchange: {
callee: 'UniswapV2CalleeDai',
route: ['ETH'],
},
},
'WBTC-C': {
title: 'Wrapped BTC',
ilk: 'WBTC-C',
symbol: 'WBTC',
decimals: 8,
exchange: {
callee: 'UniswapV2CalleeDai',
route: ['ETH'],
},
},
'YFI-A': {
title: 'yearn.finance',
ilk: 'YFI-A',
Expand Down Expand Up @@ -231,26 +251,6 @@ const COLLATERALS: Record<string, CollateralConfig> = {
route: ['ETH'],
},
},
'WBTC-B': {
title: 'Wrapped BTC',
ilk: 'WBTC-B',
symbol: 'WBTC',
decimals: 8,
exchange: {
callee: 'UniswapV2CalleeDai',
route: ['ETH'],
},
},
'WBTC-C': {
title: 'Wrapped BTC',
ilk: 'WBTC-C',
symbol: 'WBTC',
decimals: 8,
exchange: {
callee: 'UniswapV2CalleeDai',
route: ['ETH'],
},
},
'WSTETH-A': {
title: 'Lido wstETH',
ilk: 'WSTETH-A',
Expand All @@ -261,6 +261,16 @@ const COLLATERALS: Record<string, CollateralConfig> = {
route: [],
},
},
'CRVV1ETHSTETH-A': {
title: 'Curve stETH',
ilk: 'CRVV1ETHSTETH-A',
symbol: 'CRVV1ETHSTETH',
decimals: 18,
exchange: {
callee: 'CurveLpTokenUniv3Callee',
route: ['ETH'],
},
},
'UNIV2DAIETH-A': {
title: 'UNIV2DAIETH LP',
ilk: 'UNIV2DAIETH-A',
Expand Down
15 changes: 15 additions & 0 deletions core/src/helpers/parseMetamaskError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import truncateText from './truncateText';

const parseMetamaskError = function (errorMessage = ''): string {
const jsonFirstIndex = errorMessage.indexOf('{');
const jsonLastIndex = errorMessage.lastIndexOf('}');
try {
const jsonString = errorMessage.substring(jsonFirstIndex, jsonLastIndex + 1);
const metamaskError = JSON.parse(jsonString);
return truncateText(metamaskError?.value?.data?.message || 'unknown');
} catch {
return truncateText(errorMessage || 'unknown');
}
};

export default parseMetamaskError;
2 changes: 1 addition & 1 deletion core/src/helpers/truncateText.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const truncateText = function (text: string, maxLength = 100) {
const truncateText = function (text: string, maxLength = 80) {
if (text.length > maxLength) {
return `${text.substring(0, maxLength).trim()}...`;
}
Expand Down
6 changes: 3 additions & 3 deletions core/src/tracker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Notifier, MessageContent } from './types';
import { ethers } from 'ethers';
import { v4 as uuidv4 } from 'uuid';
import truncateText from './helpers/truncateText';
import parseMetamaskError from './helpers/parseMetamaskError';

const DEFAULT_NOTIFICATION_DURATION = 3;
const NUMBER_OF_BLOCKS_TO_CONFIRM = 5;
Expand Down Expand Up @@ -35,7 +35,7 @@ const trackTransaction = async function (
});
} catch (error: any) {
notifier('error', {
content: `Transaction error: ${truncateText(error?.message || 'unknown')}`,
content: `Transaction error: "${parseMetamaskError(error?.message)}"`,
key: messageId,
duration: DEFAULT_NOTIFICATION_DURATION,
});
Expand Down Expand Up @@ -72,7 +72,7 @@ const trackTransaction = async function (
return confirmedTransactionReceipt.transactionHash;
} catch (error: any) {
notifier('error', {
content: `Transaction was rejected with error: "${truncateText(error?.message || 'unknown')}"`,
content: `Transaction was rejected with error: "${parseMetamaskError(error?.message)}"`,
key: messageId,
duration: DEFAULT_NOTIFICATION_DURATION,
});
Expand Down
3 changes: 2 additions & 1 deletion core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export declare interface AuctionTransaction extends Auction, TransactionFees {
}

export declare interface UniswapV2CalleeConfig {
callee: 'UniswapV2CalleeDai' | 'WstETHCurveUniv3Callee';
callee: 'UniswapV2CalleeDai' | 'WstETHCurveUniv3Callee' | 'CurveLpTokenUniv3Callee';
route: string[];
}

Expand Down Expand Up @@ -98,6 +98,7 @@ export declare interface CalleeAddresses {
UniswapV2CalleeDai: string;
UniswapV2LpTokenCalleeDai: string;
WstETHCurveUniv3Callee?: string;
CurveLpTokenUniv3Callee?: string;
}

export type CalleeNames = keyof CalleeAddresses;
Expand Down
1 change: 1 addition & 0 deletions frontend/components/layout/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
:has-accepted-terms="hasAcceptedTerms"
@update:isModalOpen="$emit('update:isModalOpen', $event)"
@changeWalletType="$emit('changeWalletType', $event)"
@openWalletModal="$emit('openWalletModal')"
@openTermsModal="$emit('openTermsModal')"
/>

Expand Down
Loading

0 comments on commit 7feb3f3

Please sign in to comment.