Skip to content

Commit

Permalink
refactor: Multi-callee support in collateral config (#511)
Browse files Browse the repository at this point in the history
  • Loading branch information
aomafarag committed Nov 4, 2022
1 parent a4e59b5 commit 24a1932
Show file tree
Hide file tree
Showing 19 changed files with 675 additions and 224 deletions.
13 changes: 12 additions & 1 deletion bot/src/keepers/collateral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ const checkAndParticipateIfPossible = async function (network: string, auction:
return;
}

// check if callee is valid
if (!auctionTransaction.suggestedMarketId) {
console.info(`collateral keeper: auction "${auction.id}" has no valid market`);
return;
}

// check auction's profit
if (!auctionTransaction.transactionGrossProfit || auctionTransaction.transactionGrossProfit.isLessThan(0)) {
if (auctionTransaction.transactionGrossProfit) {
Expand Down Expand Up @@ -103,7 +109,12 @@ const checkAndParticipateIfPossible = async function (network: string, auction:

// bid on the Auction
console.info(`collateral keeper: auction "${auctionTransaction.id}": attempting swap execution`);
const bidHash = await bidWithCallee(network, auctionTransaction, walletAddress);
const bidHash = await bidWithCallee(
network,
auctionTransaction,
auctionTransaction.suggestedMarketId,
walletAddress
);
console.info(
`collateral keeper: auction "${auctionTransaction.id}" was succesfully executed via "${bidHash}" transaction`
);
Expand Down
132 changes: 106 additions & 26 deletions core/src/auctions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import type { Auction, AuctionInitialInfo, AuctionTransaction, Notifier, TakeEvent } from './types';
import type {
Auction,
AuctionInitialInfo,
AuctionTransaction,
Notifier,
TakeEvent,
MarketData,
ExchangeFees,
} from './types';
import BigNumber from './bignumber';
import fetchAuctionsByCollateralType, {
fetchAuctionByCollateralTypeAndAuctionIndex,
fetchAuctionStatus,
fetchMinimumBidDai,
} from './fetch';
import { getCalleeData, getMarketPrice } from './calleeFunctions';
import { getBestMarketId, getCalleeData, getMarketDataRecords, getMarketPrice } from './calleeFunctions';
import { fetchCalcParametersByCollateralType } from './params';
import executeTransaction from './execute';
import { RAY_NUMBER_OF_DIGITS, WAD_NUMBER_OF_DIGITS, NULL_BYTES } from './constants/UNITS';
Expand All @@ -20,7 +28,7 @@ import {
import { getSupportedCollateralTypes } from './addresses';
import getContract, { getClipperNameByCollateralType } from './contracts';
import convertNumberTo32Bytes from './helpers/convertNumberTo32Bytes';
import { enrichAuctionWithTransactionFees, getApproximateTransactionFees } from './fees';
import { enrichAuctionWithTransactionFees, getApproximateTransactionFees, getDefaultMarketFee } from './fees';
import parseAuctionId from './helpers/parseAuctionId';
import { EventFilter } from 'ethers';
import getNetworkDate, { fetchDateByBlockNumber } from './date';
Expand Down Expand Up @@ -67,26 +75,97 @@ const calculateCollateralToCoverDebt = async function (network: string, auction:
return auction.debtDAI.dividedBy(marketPriceForAllCollateral);
};

const enrichAuctionWithMarketValues = async function (auction: Auction, network: string): Promise<Auction> {
export const enrichMarketDataRecordsWithValues = async function (
auction: AuctionTransaction,
marketDataRecords: Record<string, MarketData>,
exchangeFees: ExchangeFees,
amount: BigNumber = new BigNumber(1)
): Promise<Record<string, MarketData>> {
const exchangeFeePerUnitDAI = exchangeFees.exchangeFeeDAI.dividedBy(amount);
let enrichedMarketDataRecords = {};
for (const marketId in marketDataRecords) {
let marketData = marketDataRecords[marketId];
// enrich with values dependent on marketUnitPrice
if (marketData.marketUnitPrice.isNaN()) {
enrichedMarketDataRecords = {
...enrichedMarketDataRecords,
[marketId]: { ...marketData },
};
continue;
}
marketData = {
...marketData,
marketUnitPrice: marketData.marketUnitPrice.plus(exchangeFeePerUnitDAI),
marketUnitPriceToUnitPriceRatio: auction.approximateUnitPrice
.minus(marketData.marketUnitPrice)
.dividedBy(marketData.marketUnitPrice),
...exchangeFees,
transactionGrossProfit: calculateTransactionGrossProfit(
marketData.marketUnitPrice,
amount,
auction.approximateUnitPrice
),
};
// enrich with values dependent on fees
if (marketData.transactionGrossProfit && auction.combinedSwapFeesDAI) {
marketData = {
...marketData,
transactionNetProfit: marketData.transactionGrossProfit.minus(auction.combinedSwapFeesDAI),
};
}
// enrich with values dependent on currentDate
try {
const currentDate = await getNetworkDate(auction.network);
marketData = {
...marketData,
transactionGrossProfitDate: calculateTransactionGrossProfitDate(
auction,
marketData.marketUnitPrice,
currentDate
),
};
} catch {}
enrichedMarketDataRecords = {
...enrichedMarketDataRecords,
[marketId]: { ...marketData },
};
}
return enrichedMarketDataRecords;
};

export const enrichAuctionWithMarketDataRecords = async function (
auction: AuctionTransaction,
network: string
): Promise<AuctionTransaction> {
if (!auction.isActive || !auction.approximateUnitPrice || auction.isFinished) {
return auction;
}
try {
const collateralToCoverDebt = await calculateCollateralToCoverDebt(network, auction);
const marketUnitPrice = await getMarketPrice(network, auction.collateralSymbol, collateralToCoverDebt);
const marketUnitPriceToUnitPriceRatio = auction.approximateUnitPrice
.minus(marketUnitPrice)
.dividedBy(marketUnitPrice);
const auctionWithMarketValues = {
const exchangeFees = await getDefaultMarketFee(network);
const marketDataRecords = await getMarketDataRecords(network, auction.collateralSymbol, collateralToCoverDebt);
const enrichedMarketDataRecords = await enrichMarketDataRecordsWithValues(
auction,
marketDataRecords,
exchangeFees,
collateralToCoverDebt
);
const suggestedMarketId = await getBestMarketId(enrichedMarketDataRecords);
const marketUnitPrice = enrichedMarketDataRecords[suggestedMarketId].marketUnitPrice;
const marketUnitPriceToUnitPriceRatio =
enrichedMarketDataRecords[suggestedMarketId].marketUnitPriceToUnitPriceRatio;
const transactionGrossProfit = enrichedMarketDataRecords[suggestedMarketId].transactionGrossProfit;
const transactionGrossProfitDate = enrichedMarketDataRecords[suggestedMarketId].transactionGrossProfitDate;
const transactionNetProfit = enrichedMarketDataRecords[suggestedMarketId].transactionNetProfit;
return {
...auction,
collateralToCoverDebt,
suggestedMarketId,
marketUnitPrice,
marketUnitPriceToUnitPriceRatio,
};
const transactionGrossProfit = calculateTransactionGrossProfit(auctionWithMarketValues);
return {
...auctionWithMarketValues,
transactionGrossProfit,
transactionGrossProfitDate,
transactionNetProfit: transactionNetProfit ? transactionNetProfit : new BigNumber(NaN),
marketDataRecords,
};
} catch (error: any) {
// since it's expected that some collaterals are not tradable on some networks
Expand All @@ -111,13 +190,11 @@ export const enrichAuctionWithPriceDrop = async function (auction: Auction): Pro
const secondsTillNextPriceDrop = calculateAuctionDropTime(auctionWithParams, currentDate);
const approximateUnitPrice = calculateAuctionPrice(auctionWithParams, currentDate);
const totalPrice = auction.collateralAmount.multipliedBy(approximateUnitPrice);
const transactionGrossProfitDate = calculateTransactionGrossProfitDate(auctionWithParams, currentDate);
return {
...auctionWithParams,
secondsTillNextPriceDrop,
approximateUnitPrice,
totalPrice,
transactionGrossProfitDate,
};
} catch (error) {
return {
Expand All @@ -127,12 +204,14 @@ export const enrichAuctionWithPriceDrop = async function (auction: Auction): Pro
}
};

export const enrichAuctionWithPriceDropAndMarketValue = async function (
export const enrichAuctionWithPriceDropAndMarketDataRecords = async function (
auction: Auction,
network: string
): Promise<Auction> {
const enrichedAuctionWithNewPriceDrop = await enrichAuctionWithPriceDrop(auction);
return await enrichAuctionWithMarketValues(enrichedAuctionWithNewPriceDrop, network);
const fees = await getApproximateTransactionFees(network);
const auctionWithFees = await enrichAuctionWithTransactionFees(enrichedAuctionWithNewPriceDrop, fees, network);
return await enrichAuctionWithMarketDataRecords(auctionWithFees, network);
};

export const fetchSingleAuctionById = async function (
Expand Down Expand Up @@ -173,21 +252,21 @@ export const enrichAuction = async function (
// enrich them with price drop
const auctionWithPriceDrop = await enrichAuctionWithPriceDrop(auctionWithStatus);

// enrich them with market values
const auctionWithMarketValue = await enrichAuctionWithMarketValues(auctionWithPriceDrop, network);

// enrich with profit and fee calculation
const fees = await getApproximateTransactionFees(network);
const auctionWithFees = await enrichAuctionWithTransactionFees(auctionWithMarketValue, fees, network);
const auctionWithFees = await enrichAuctionWithTransactionFees(auctionWithPriceDrop, fees, network);

// enrich them with market data
const auctionWithMarketDataRecords = await enrichAuctionWithMarketDataRecords(auctionWithFees, network);

if (auction.debtDAI.isEqualTo(0)) {
return {
...auctionWithFees,
...auctionWithMarketDataRecords,
isFinished: true,
isActive: false,
};
}
return auctionWithFees;
return auctionWithMarketDataRecords;
};

const enrichTakeEventWithDate = async function (network: string, takeEvent: TakeEvent): Promise<TakeEvent> {
Expand Down Expand Up @@ -254,11 +333,12 @@ export const bidWithDai = async function (
export const bidWithCallee = async function (
network: string,
auction: Auction,
marketId: string,
profitAddress: string,
notifier?: Notifier
): Promise<string> {
const calleeAddress = getCalleeAddressByCollateralType(network, auction.collateralType);
const calleeData = await getCalleeData(network, auction.collateralType, profitAddress);
const calleeAddress = getCalleeAddressByCollateralType(network, auction.collateralType, marketId);
const calleeData = await getCalleeData(network, auction.collateralType, marketId, profitAddress);
const contractName = getClipperNameByCollateralType(auction.collateralType);
const contractParameters = [
convertNumberTo32Bytes(auction.index),
Expand Down
7 changes: 5 additions & 2 deletions core/src/calleeFunctions/CurveLpTokenUniv3Callee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ export const CHARTER_MANAGER_ADDRESS = '0x8377CD01a5834a6EaD3b7efb482f678f2092b7
const getCalleeData = async function (
network: string,
collateral: CollateralConfig,
marketId: string,
profitAddress: string
): Promise<string> {
if (collateral.exchange.callee !== 'CurveLpTokenUniv3Callee') {
const marketData = collateral.exchanges[marketId];
if (marketData?.callee !== 'CurveLpTokenUniv3Callee') {
throw new Error(`Can not encode route for the "${collateral.ilk}"`);
}
const route = await encodeRoute(network, collateral.exchange.route);
const route = await encodeRoute(network, marketData.route);
const curveData = [CURVE_POOL_ADDRESS, CURVE_COIN_INDEX];
const joinAdapterAddress = await getContractAddressByName(network, getJoinNameByCollateralType(collateral.ilk));
const minProfit = 1;
Expand All @@ -33,6 +35,7 @@ const getCalleeData = async function (
const getMarketPrice = async function (
network: string,
_collateral: CollateralConfig,
_marketId: string,
collateralAmount: BigNumber
): Promise<BigNumber> {
// convert stETH into ETH
Expand Down
9 changes: 6 additions & 3 deletions core/src/calleeFunctions/UniswapV2CalleeDai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { getUniswapRouteAddressesBySymbol, getRegularTokenExchangeRateBySymbol }
const getCalleeData = async function (
network: string,
collateral: CollateralConfig,
marketId: string,
profitAddress: string
): Promise<string> {
if (collateral.exchange.callee !== 'UniswapV2CalleeDai') {
const marketData = collateral.exchanges[marketId];
if (marketData?.callee !== 'UniswapV2CalleeDai') {
throw new Error(`getCalleeData called with invalid collateral type "${collateral.ilk}"`);
}
const joinAdapterAddress = await getContractAddressByName(network, getJoinNameByCollateralType(collateral.ilk));
Expand All @@ -19,16 +21,17 @@ const getCalleeData = async function (
profitAddress,
joinAdapterAddress,
minProfit,
await getUniswapRouteAddressesBySymbol(network, collateral.symbol),
await getUniswapRouteAddressesBySymbol(network, collateral.symbol, marketId),
]);
};

const getMarketPrice = async function (
network: string,
collateral: CollateralConfig,
marketId: string,
amount: BigNumber
): Promise<BigNumber> {
return await getRegularTokenExchangeRateBySymbol(network, collateral.symbol, amount);
return await getRegularTokenExchangeRateBySymbol(network, collateral.symbol, marketId, amount);
};

const UniswapV2CalleeDai: CalleeFunctions = {
Expand Down
20 changes: 13 additions & 7 deletions core/src/calleeFunctions/UniswapV2LpTokenCalleeDai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {
const getCalleeData = async function (
network: string,
collateral: CollateralConfig,
marketId: string,
profitAddress: string
): Promise<string> {
if (collateral.exchange.callee !== 'UniswapV2LpTokenCalleeDai') {
const marketData = collateral.exchanges[marketId];
if (marketData?.callee !== 'UniswapV2LpTokenCalleeDai') {
throw new Error(`getCalleeData called with invalid collateral type "${collateral.ilk}"`);
}
const joinAdapterAddress = await getContractAddressByName(network, getJoinNameByCollateralType(collateral.ilk));
Expand All @@ -25,30 +27,34 @@ const getCalleeData = async function (
profitAddress,
joinAdapterAddress,
minProfit,
await getUniswapRouteAddressesBySymbol(network, collateral.exchange.token0),
await getUniswapRouteAddressesBySymbol(network, collateral.exchange.token1),
await getUniswapRouteAddressesBySymbol(network, marketData.token0, marketId),
await getUniswapRouteAddressesBySymbol(network, marketData.token1, marketId),
]);
};

const getMarketPrice = async function (
network: string,
collateral: CollateralConfig,
marketId: string,
amount: BigNumber
): Promise<BigNumber> {
if (collateral.exchange.callee !== 'UniswapV2LpTokenCalleeDai') {
const marketData = collateral.exchanges[marketId];
if (marketData?.callee !== 'UniswapV2LpTokenCalleeDai') {
throw new Error(`"${collateral.symbol}" is not a UniSwap LP token`);
}
const uniswapPair = await getUniswapPairBySymbols(network, collateral.exchange.token0, collateral.exchange.token1);
const uniswapPair = await getUniswapPairBySymbols(network, marketData.token0, marketData.token1);
const totalSupply = await getLpTokenTotalSupply(network, collateral.symbol);
const portionOfTheTotalSupply = amount.div(totalSupply);
const totalPriceOfToken0 = await getTotalPriceInDai(
network,
uniswapPair.reserveOf(await getUniswapTokenBySymbol(network, collateral.exchange.token0)),
uniswapPair.reserveOf(await getUniswapTokenBySymbol(network, marketData.token0)),
marketId,
portionOfTheTotalSupply
);
const totalPriceOfToken1 = await getTotalPriceInDai(
network,
uniswapPair.reserveOf(await getUniswapTokenBySymbol(network, collateral.exchange.token1)),
uniswapPair.reserveOf(await getUniswapTokenBySymbol(network, marketData.token1)),
marketId,
portionOfTheTotalSupply
);
const totalPrice = totalPriceOfToken0.plus(totalPriceOfToken1);
Expand Down
9 changes: 6 additions & 3 deletions core/src/calleeFunctions/UniswapV3Callee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import { convertCollateralToDaiUsingRoute, encodeRoute } from './helpers/uniswap
const getCalleeData = async function (
network: string,
collateral: CollateralConfig,
marketId: string,
profitAddress: string
): Promise<string> {
if (collateral.exchange.callee !== 'UniswapV3Callee') {
const marketData = collateral.exchanges[marketId];
if (marketData?.callee !== 'UniswapV3Callee') {
throw new Error(`getCalleeData called with invalid collateral type "${collateral.ilk}"`);
}
const joinAdapterAddress = await getContractAddressByName(network, getJoinNameByCollateralType(collateral.ilk));
const minProfit = 1;
const uniswapV3route = await encodeRoute(network, [collateral.symbol, ...collateral.exchange.route]);
const uniswapV3route = await encodeRoute(network, [collateral.symbol, ...marketData.route]);
const typesArray = ['address', 'address', 'uint256', 'bytes', 'address'];
return ethers.utils.defaultAbiCoder.encode(typesArray, [
profitAddress,
Expand All @@ -28,10 +30,11 @@ const getCalleeData = async function (
const getMarketPrice = async function (
network: string,
collateral: CollateralConfig,
marketId: string,
collateralAmount: BigNumber
): Promise<BigNumber> {
// convert collateral into DAI
const daiAmount = await convertCollateralToDaiUsingRoute(network, collateral.symbol, collateralAmount);
const daiAmount = await convertCollateralToDaiUsingRoute(network, collateral.symbol, marketId, collateralAmount);

// return price per unit
return daiAmount.dividedBy(collateralAmount);
Expand Down
Loading

0 comments on commit 24a1932

Please sign in to comment.