Skip to content

Commit

Permalink
Merge branch 'master' of github.com:Gearbox-protocol/sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
Mikael committed Sep 8, 2022
2 parents 24bf174 + c90cee2 commit f7f14e5
Show file tree
Hide file tree
Showing 11 changed files with 731 additions and 90 deletions.
1 change: 1 addition & 0 deletions src/apy/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LPTokens, SupportedToken } from "../tokens/token";

export type TokensWithAPY = LPTokens | Extract<SupportedToken, "LDO">;
export type LpTokensAPY = Record<TokensWithAPY, number>;

export * from "./convexAPY";
export * from "./curveAPY";
Expand Down
62 changes: 62 additions & 0 deletions src/core/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Signer } from "ethers";

import { AdapterInterface } from "../contracts/adapters";
import { SupportedContract } from "../contracts/contracts";
import { PathFinderResultStructOutput } from "../types/contracts/pathfinder/interfaces/IPathFinder";
import { EVMTx } from "./eventOrTx";
import { TXSwap } from "./transactions";

export enum AdapterType {
Swap = 1,
LP = 2,
}

interface BaseAdapterProps {
name: string;
type: AdapterType;
adapterInterface: AdapterInterface;
contractAddress: string;
adapterAddress: string;
contractSymbol: SupportedContract;
}

interface ExecuteProps {
tradePath: PathFinderResultStructOutput;
slippage: number;
signer: Signer;
}

export class BaseAdapter {
readonly name: string;
readonly type: AdapterType;
readonly adapterInterface: AdapterInterface;
readonly contractAddress: string;
readonly adapterAddress: string;
readonly contractSymbol: SupportedContract;

constructor(props: BaseAdapterProps) {
this.name = props.name;
this.type = props.type;
this.adapterInterface = props.adapterInterface;
this.adapterAddress = props.adapterAddress;
this.contractAddress = props.contractAddress;
this.contractSymbol = props.contractSymbol;
}

async execute(props: ExecuteProps): Promise<EVMTx> {
console.debug(props);
return {} as TXSwap;
}

static connect(tradePath: PathFinderResultStructOutput) {
console.debug(tradePath);
return new BaseAdapter({
name: "",
type: AdapterType.Swap,
adapterInterface: AdapterInterface.NO_SWAP,
adapterAddress: "",
contractAddress: "",
contractSymbol: "CONVEX_3CRV_POOL",
});
}
}
152 changes: 152 additions & 0 deletions src/core/assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { BigNumber } from "ethers";

import { LPTokens } from "../tokens/token";
import { TokenData } from "../tokens/tokenData";
import { nonNegativeBn } from "../utils/math";
import { sortBalances } from "./creditAccount";

export interface Asset {
token: string;
balance: BigNumber;
}

export interface LpAsset extends Asset {
symbol: LPTokens;
name: string;
}

interface NextAssetProps<T extends Asset> {
allowedTokens: Array<string>;
selectedAssets: Array<T>;
balances: Record<string, BigNumber>;
tokensList: Record<string, TokenData>;
prices?: Record<string, BigNumber>;
}

export function nextAsset<T extends Asset>({
allowedTokens,
selectedAssets,
balances,
tokensList,
prices = {},
}: NextAssetProps<T>): string | undefined {
const selectedRecord = selectedAssets.reduce<Record<string, true>>(
(acc, { token }) => {
acc[token.toLowerCase()] = true;
return acc;
},
{},
);

const notSelected = allowedTokens.filter(allowedToken => {
const alreadySelected = selectedRecord[allowedToken.toLowerCase()];
return !alreadySelected;
});

const sorted = sortBalances(
getBalances(notSelected, balances),
prices,
tokensList,
);

const [address] = sorted[0] || [];

return address;
}

export function getBalances(
allowedTokens: Array<string>,
externalBalances: Record<string, BigNumber>,
): Record<string, BigNumber> {
return allowedTokens.reduce((acc, address) => {
const addressLc = address.toLowerCase();
return {
...acc,
[addressLc]: externalBalances[addressLc] || BigNumber.from(0),
};
}, {});
}

export function constructAssetRecord<A extends Asset>(a: Array<A>) {
const record = a.reduce<Record<string, A>>((acc, asset) => {
acc[asset.token] = asset;
return acc;
}, {});
return record;
}

export type WrapResult<T> = [Array<T>, BigNumber, BigNumber];

export const memoWrapETH = (ethAddress: string, wethAddress: string) =>
function wrapETH<T extends Asset>(assets: Array<T>): WrapResult<T> {
const assetsRecord = constructAssetRecord(assets);

const weth = assetsRecord[wethAddress];
const { balance: wethAmount = BigNumber.from(0) } = weth || {};

const eth = assetsRecord[ethAddress];
const { balance: ethAmount = BigNumber.from(0) } = eth || {};

const wethOrETH = weth || eth;

if (wethOrETH) {
assetsRecord[wethAddress] = {
...wethOrETH,
token: wethAddress,
balance: nonNegativeBn(ethAmount).add(nonNegativeBn(wethAmount)),
};
}

if (eth) {
delete assetsRecord[ethAddress];
}

return [Object.values(assetsRecord), ethAmount, wethAmount];
};

/**
* Sums the the second assets list into the first assets list
* Creates new assets.
*/
export function sumAssets<A extends Asset, B extends Asset>(
a: Array<A>,
b: Array<B>,
): Array<A | B> {
const aRecord = constructAssetRecord(a);

const resRecord = b.reduce<Record<string, A | B>>((acc, bAsset) => {
const aAsset = acc[bAsset.token];
const { balance: amount = BigNumber.from(0) } = aAsset || {};

const amountSum = nonNegativeBn(bAsset.balance).add(nonNegativeBn(amount));
const aOrB = aAsset || bAsset;

acc[bAsset.token] = {
...aOrB,
balance: amountSum,
};

return acc;
}, aRecord);

return Object.values(resRecord);
}

/**
* Subtracts the the second assets list from the first assets list
* Balance cant be negative; doesn't create new assets.
*/
export function subAssets<A extends Asset, B extends Asset>(
a: Array<A>,
b: Array<B>,
): Array<A> {
const bRecord = constructAssetRecord(b);

return a.map(asset => {
const bAsset = bRecord[asset.token];
const { balance: bAmount = BigNumber.from(0) } = bAsset || {};

const amountSub = nonNegativeBn(asset.balance).sub(nonNegativeBn(bAmount));
return { ...asset, balance: nonNegativeBn(amountSub) };
});
}
87 changes: 79 additions & 8 deletions src/core/creditAccount.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { BigNumber } from "ethers";

import { LpTokensAPY } from "../apy";
import {
CreditAccountDataExtendedPayload,
CreditAccountDataPayload,
} from "../payload/creditAccount";
import { TokenData } from "../tokens/tokenData";
import { calcTotalPrice } from "../utils/price";
import { toBN, toSignificant } from "../utils/formatter";
import { calcTotalPrice, convertByPrice } from "../utils/price";
import { Asset, LpAsset } from "./assets";
import {
PERCENTAGE_DECIMALS,
PERCENTAGE_FACTOR,
PRICE_DECIMALS,
RAY,
WAD,
WAD_DECIMALS_POW,
} from "./constants";

export interface Balance {
address: string;
balance: BigNumber;
}

export class CreditAccountData {
public readonly id: string;

Expand Down Expand Up @@ -89,9 +89,9 @@ export class CreditAccountData {
balancesSorted(
prices: Record<string, BigNumber>,
tokens: Record<string, TokenData>,
): Array<Balance> {
): Array<Asset> {
return sortBalances(this.balances, prices, tokens).map(
([address, balance]) => ({ address, balance }),
([token, balance]) => ({ token, balance }),
);
}
}
Expand Down Expand Up @@ -162,3 +162,74 @@ export class CreditAccountDataExtended extends CreditAccountData {
this.since = BigNumber.from(payload.since).toNumber();
}
}

export interface CalcOverallAPYProps {
lpAssets: Array<LpAsset>;
lpAPY: LpTokensAPY | undefined;
prices: Record<string, BigNumber>;

tokensList: Record<string, TokenData>;

totalValue: BigNumber | undefined;
debt: BigNumber | undefined;
borrowRate: number;
underlyingToken: string;
}

export function calcOverallAPY({
lpAssets,
lpAPY,
prices,

tokensList,

totalValue,
debt,
borrowRate,
underlyingToken,
}: CalcOverallAPYProps): number | undefined {
if (!lpAPY || !totalValue || !debt) return undefined;

const assetAPYMoney = lpAssets.reduce(
(acc, { symbol, token: tokenAddress, balance: amount }) => {
const apy = lpAPY[symbol] || 0;
const price = prices[tokenAddress] || BigNumber.from(0);
const token = tokensList[tokenAddress];

const apyBN = toBN(
(apy / PERCENTAGE_DECIMALS).toString(),
WAD_DECIMALS_POW,
);

const money = calcTotalPrice(price, amount, token?.decimals);
const apyMoney = money.mul(apyBN).div(WAD);

return acc.add(apyMoney);
},
BigNumber.from(0),
);

const { decimals: underlyingDecimals = 18, address: underlyingAddress = "" } =
tokensList[underlyingToken] || {};
const underlyingPrice = prices[underlyingAddress] || PRICE_DECIMALS;
const assetAPYAmountInUnderlying = convertByPrice(assetAPYMoney, {
price: underlyingPrice,
decimals: underlyingDecimals,
});

const borrowAPY = toBN(
(borrowRate / PERCENTAGE_DECIMALS).toString(),
WAD_DECIMALS_POW,
);
const debtAPY = debt.mul(borrowAPY).div(WAD);

const yourAssets = totalValue.sub(debt);

const apyInPercent = assetAPYAmountInUnderlying
.sub(debtAPY)
.mul(PERCENTAGE_DECIMALS)
.mul(WAD)
.div(yourAssets);

return Number(toSignificant(apyInPercent, WAD_DECIMALS_POW));
}
Loading

0 comments on commit f7f14e5

Please sign in to comment.