Skip to content

Commit

Permalink
Merge pull request #64 from curvefi/fix/deposit-or-withdraw-bonus
Browse files Browse the repository at this point in the history
Fix/deposit-or-withdraw-bonus
  • Loading branch information
Macket authored Jul 13, 2022
2 parents 6864dc2 + 20eabc6 commit 4959af4
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 65 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@curvefi/api",
"version": "2.4.1",
"version": "2.4.2",
"description": "JavaScript library for curve.fi",
"main": "lib/index.js",
"author": "Macket",
Expand Down
69 changes: 10 additions & 59 deletions src/pools/PoolTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1274,13 +1274,9 @@ export class PoolTemplate {
return await this.calcLpTokenAmount(amounts, false);
}

// OVERRIDE
public async withdrawImbalanceBonus(amounts: (number | string)[]): Promise<string> {
if (this.isCrypto) throw Error(`withdrawImbalanceBonus method doesn't exist for pool ${this.name} (id: ${this.name})`);

const totalAmount = amounts.map(checkNumber).map(Number).reduce((a, b) => a + b);
const expected = Number(await this.withdrawImbalanceExpected(amounts));

return await this._withdrawBonus(totalAmount, expected);
throw Error(`withdrawImbalanceBonus method doesn't exist for pool ${this.name} (id: ${this.name})`);
}

public async withdrawImbalanceIsApproved(amounts: (number | string)[]): Promise<boolean> {
Expand Down Expand Up @@ -1337,13 +1333,9 @@ export class PoolTemplate {
return await this.calcLpTokenAmountWrapped(amounts, false);
}

// OVERRIDE
public async withdrawImbalanceWrappedBonus(amounts: (number | string)[]): Promise<string> {
if (this.isCrypto) throw Error(`withdrawImbalanceWrappedBonus method doesn't exist for pool ${this.name} (id: ${this.name})`);

const totalAmount = amounts.map(checkNumber).map(Number).reduce((a, b) => a + b);
const expected = Number(await this.withdrawImbalanceWrappedExpected(amounts));

return await this._withdrawBonus(totalAmount, expected, false);
throw Error(`withdrawImbalanceWrappedBonus method doesn't exist for pool ${this.name} (id: ${this.name})`);
}

// OVERRIDE
Expand Down Expand Up @@ -1371,15 +1363,9 @@ export class PoolTemplate {
return ethers.utils.formatUnits(_expected, this.underlyingDecimals[i]);
}

// OVERRIDE
public async withdrawOneCoinBonus(lpTokenAmount: number | string, coin: string | number): Promise<string> {
const totalAmount = Number(await this.withdrawOneCoinExpected(lpTokenAmount, coin));

if (this.isCrypto) {
const coinPrice = (await this._underlyingPrices())[this._getCoinIdx(coin)];
return await this._withdrawCryptoBonus(totalAmount * coinPrice, Number(lpTokenAmount));
}

return await this._withdrawBonus(totalAmount, Number(lpTokenAmount));
throw Error(`withdrawOneCoinBonus method doesn't exist for pool ${this.name} (id: ${this.name})`);
}

public async withdrawOneCoinIsApproved(lpTokenAmount: number | string): Promise<boolean> {
Expand Down Expand Up @@ -1423,19 +1409,9 @@ export class PoolTemplate {
return ethers.utils.formatUnits(_expected, this.wrappedDecimals[i]);
}

// OVERRIDE
public async withdrawOneCoinWrappedBonus(lpTokenAmount: number | string, coin: string | number): Promise<string> {
if (this.isFake || this.isPlain) {
throw Error(`${this.name} pool doesn't have this method`);
}

const totalAmount = Number(await this.withdrawOneCoinWrappedExpected(lpTokenAmount, coin));

if (this.isCrypto) {
const coinPrice = (await this._underlyingPrices())[this._getCoinIdx(coin, false)];
return await this._withdrawCryptoBonus(totalAmount * coinPrice, Number(lpTokenAmount));
}

return await this._withdrawBonus(totalAmount, Number(lpTokenAmount), false);
throw Error(`withdrawOneCoinWrappedBonus method doesn't exist for pool ${this.name} (id: ${this.name})`);
}

// OVERRIDE
Expand Down Expand Up @@ -1878,6 +1854,7 @@ export class PoolTemplate {
return addresses.length === 1 ? balances[addresses[0]] : balances
}

// Used by mixin. Don't delete it!!!
private _underlyingPrices = async (): Promise<number[]> => {
const promises = [];
for (const addr of this.underlyingCoinAddresses) {
Expand All @@ -1887,6 +1864,7 @@ export class PoolTemplate {
return await Promise.all(promises)
}

// Used by mixin. Don't delete it!!!
// NOTE! It may crash!
private _wrappedPrices = async (): Promise<number[]> => {
const promises = [];
Expand All @@ -1896,31 +1874,4 @@ export class PoolTemplate {

return await Promise.all(promises)
}

private _withdrawCryptoBonus = async (totalAmountUSD: number, lpTokenAmount: number, useUnderlying = true): Promise<string> => {
const prices: number[] = useUnderlying ? await this._underlyingPrices() : await this._wrappedPrices();

const balancedAmounts = useUnderlying ?
await this.withdrawExpected(String(lpTokenAmount)) :
await this.withdrawWrappedExpected(String(lpTokenAmount));
const balancedTotalAmountsUSD = balancedAmounts.reduce((s, b, i) => s + (Number(b) * prices[i]), 0);

return String((totalAmountUSD - balancedTotalAmountsUSD) / totalAmountUSD * 100)
}

// TODO make the same as _withdrawCryptoBonus
private _withdrawBonus = async (totalAmount: number, expected: number, useUnderlying = true): Promise<string> => {
const poolBalances: number[] = useUnderlying ?
(await this.stats.underlyingBalances()).map(Number) :
(await this.stats.wrappedBalances()).map(Number);
const poolTotalBalance: number = poolBalances.reduce((a,b) => a + b);
const poolBalancesRatios: number[] = poolBalances.map((b) => b / poolTotalBalance);

const balancedAmounts: string[] = poolBalancesRatios.map((r) => String(r * totalAmount));
const balancedExpected = useUnderlying ?
Number(await this.withdrawImbalanceExpected(balancedAmounts)) :
Number(await this.withdrawImbalanceWrappedExpected(balancedAmounts));

return String((balancedExpected - expected) / balancedExpected * 100)
}
}
27 changes: 22 additions & 5 deletions src/pools/mixins/depositBonusMixins.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ethers} from "ethers";
import { curve } from "../../curve";
import { PoolTemplate } from "../PoolTemplate";
import { checkNumber } from "../../utils";

Expand All @@ -23,15 +25,23 @@ export const depositBonusMixin: PoolTemplate = {
// @ts-ignore
export const depositWrappedBonusMixin: PoolTemplate = {
async depositWrappedBonus(amounts: (number | string)[]): Promise<string> {
const totalAmount = amounts.map(checkNumber).map(Number).reduce((a, b) => a + b);
let vp = 1;
if (this.isMeta) {
const basePoolAddress = curve.constants.POOLS_DATA[this.basePool].swap_address;
vp = Number(ethers.utils.formatUnits(await curve.contracts[basePoolAddress].contract.get_virtual_price(curve.constantOptions)));
}
const prices = this.wrappedCoins.map((_, i, arr) => i === arr.length - 1 ? vp : 1);
const totalValue = amounts.map(checkNumber).map(Number).reduce((s, a, i) => s + (a * prices[i]), 0);
const expected = Number(await this.depositWrappedExpected(amounts));

// @ts-ignore
const poolBalances: number[] = (await this.stats.wrappedBalances()).map(Number);
const poolTotalBalance: number = poolBalances.reduce((a,b) => a + b);
const poolBalancesRatios: number[] = poolBalances.map((b) => b / poolTotalBalance);
const poolValues = poolBalances.map((b, i) => b * prices[i]);
const poolTotalValue = poolValues.reduce((a,b) => a + b);
const poolRatios = poolValues.map((b) => b / poolTotalValue);

const balancedAmounts = poolBalancesRatios.map((r) => r * totalAmount);
const balancedValues = poolRatios.map((r) => r * totalValue);
const balancedAmounts = balancedValues.map((a, i) => a / prices[i]);
const balancedExpected = Number(await this.depositWrappedExpected(balancedAmounts));

return String((expected - balancedExpected) / expected * 100)
Expand Down Expand Up @@ -65,7 +75,14 @@ export const depositBonusCryptoMixin: PoolTemplate = {
export const depositWrappedBonusCryptoMixin: PoolTemplate = {
async depositWrappedBonus(amounts: string[]): Promise<string> {
// @ts-ignore
const prices = await this._wrappedPrices();
let prices = await this._wrappedPrices();
let vp = 1;
if (this.isMeta) {
const basePoolAddress = curve.constants.POOLS_DATA[this.basePool].swap_address;
vp = Number(ethers.utils.formatUnits(await curve.contracts[basePoolAddress].contract.get_virtual_price(curve.constantOptions)));
}
prices = prices.map((p, i) => i === prices.length - 1 ? p * vp : p);

const totalAmountUSD = amounts.map(checkNumber).map(Number).reduce((s, a, i) => s + (a * prices[i]), 0);
const expected = Number(await this.depositWrappedExpected(amounts));

Expand Down
112 changes: 112 additions & 0 deletions src/pools/mixins/withdrawBonusMixins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { ethers} from "ethers";
import { curve } from "../../curve";
import { PoolTemplate } from "../PoolTemplate";
import { checkNumber } from "../../utils";


// @ts-ignore
export const withdrawImbalanceBonusMixin: PoolTemplate = {
async withdrawImbalanceBonus(amounts: (number | string)[]): Promise<string> {
const totalAmount = amounts.map(checkNumber).map(Number).reduce((a, b) => a + b);
const lpTokenAmount = await this.withdrawImbalanceExpected(amounts);

const balancedAmounts = await this.withdrawExpected(lpTokenAmount);
const balancedTotalAmount = balancedAmounts.map(Number).reduce((a, b) => a + b);

return String((totalAmount - balancedTotalAmount) / Math.max(totalAmount, balancedTotalAmount) * 100);
},
}

// @ts-ignore
export const withdrawImbalanceWrappedBonusMixin: PoolTemplate = {
async withdrawImbalanceWrappedBonus(amounts: (number | string)[]): Promise<string> {
let vp = 1;
if (this.isMeta) {
const basePoolAddress = curve.constants.POOLS_DATA[this.basePool].swap_address;
vp = Number(ethers.utils.formatUnits(await curve.contracts[basePoolAddress].contract.get_virtual_price(curve.constantOptions)));
}
const prices = this.wrappedCoins.map((_, i, arr) => i === arr.length - 1 ? vp : 1);

const totalValue = amounts.map(checkNumber).map(Number).reduce((s, a, i) => s + (a * prices[i]), 0);
const lpTokenAmount = Number(await this.withdrawImbalanceWrappedExpected(amounts));

const balancedAmounts = await this.withdrawWrappedExpected(lpTokenAmount);
const balancedTotalValue = balancedAmounts.map(Number).reduce((s, a, i) => s + (a * prices[i]), 0);

return String((totalValue - balancedTotalValue) / Math.max(totalValue, balancedTotalValue) * 100);
},
}

// @ts-ignore
export const withdrawOneCoinBonusMixin: PoolTemplate = {
async withdrawOneCoinBonus(lpTokenAmount: number | string, coin: string | number): Promise<string> {
const totalAmount = Number(await this.withdrawOneCoinExpected(lpTokenAmount, coin));
const balancedAmounts = await this.withdrawExpected(lpTokenAmount);
const balancedTotalAmount = balancedAmounts.map(Number).reduce((a, b) => a + b);

return String((totalAmount - balancedTotalAmount) / Math.max(totalAmount, balancedTotalAmount) * 100);
},
}

// @ts-ignore
export const withdrawOneCoinWrappedBonusMixin: PoolTemplate = {
async withdrawOneCoinWrappedBonus(lpTokenAmount: number | string, coin: string | number): Promise<string> {
let vp = 1;
if (this.isMeta) {
const basePoolAddress = curve.constants.POOLS_DATA[this.basePool].swap_address;
vp = Number(ethers.utils.formatUnits(await curve.contracts[basePoolAddress].contract.get_virtual_price(curve.constantOptions)));
}
const prices = this.wrappedCoins.map((_, i, arr) => i === arr.length - 1 ? vp : 1);

const coinAmount = Number(await this.withdrawOneCoinWrappedExpected(lpTokenAmount, coin));
// @ts-ignore
const totalValue = coinAmount * prices[this._getCoinIdx(coin)];

const balancedAmounts = await this.withdrawWrappedExpected(lpTokenAmount);
const balancedTotalValue = balancedAmounts.map(Number).reduce((s, a, i) => s + (a * prices[i]), 0);

return String((totalValue - balancedTotalValue) / Math.max(totalValue, balancedTotalValue) * 100);
},
}

// @ts-ignore
export const withdrawOneCoinCryptoBonusMixin: PoolTemplate = {
async withdrawOneCoinBonus(lpTokenAmount: number | string, coin: string | number): Promise<string> {
// @ts-ignore
const prices: number[] = await this._underlyingPrices();
// @ts-ignore
const coinPrice = prices[this._getCoinIdx(coin)];

const totalAmount = Number(await this.withdrawOneCoinExpected(lpTokenAmount, coin));
const totalAmountUSD = totalAmount * coinPrice;

const balancedAmounts = await this.withdrawExpected(lpTokenAmount);
const balancedTotalAmountsUSD = balancedAmounts.reduce((s, b, i) => s + (Number(b) * prices[i]), 0);

return String((totalAmountUSD - balancedTotalAmountsUSD) / Math.max(totalAmountUSD, balancedTotalAmountsUSD) * 100)
},
}

// @ts-ignore
export const withdrawOneCoinWrappedCryptoBonusMixin: PoolTemplate = {
async withdrawOneCoinWrappedBonus(lpTokenAmount: number | string, coin: string | number): Promise<string> {
let vp = 1;
if (this.isMeta) {
const basePoolAddress = curve.constants.POOLS_DATA[this.basePool].swap_address;
vp = Number(ethers.utils.formatUnits(await curve.contracts[basePoolAddress].contract.get_virtual_price(curve.constantOptions)));
}

// @ts-ignore
const prices: number[] = (await this._wrappedPrices()).map((p, i) => i === prices.length - 1 ? p * vp : p);
// @ts-ignore
const coinPrice = prices[this._getCoinIdx(coin)];

const totalAmount = Number(await this.withdrawOneCoinWrappedExpected(lpTokenAmount, coin));
const totalAmountUSD = totalAmount * coinPrice;

const balancedAmounts = await this.withdrawWrappedExpected(lpTokenAmount);
const balancedTotalAmountsUSD = balancedAmounts.reduce((s, b, i) => s + (Number(b) * prices[i]), 0);

return String((totalAmountUSD - balancedTotalAmountsUSD) / Math.max(totalAmountUSD, balancedTotalAmountsUSD) * 100)
},
}
19 changes: 19 additions & 0 deletions src/pools/poolConstructor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { depositWrapped2argsMixin, depositWrapped3argsMixin } from "./mixins/dep
import { withdrawExpectedMixin, withdrawExpectedLendingOrCryptoMixin, withdrawExpectedMetaMixin, withdrawExpectedAtricrypto3Mixin, withdrawWrappedExpectedMixin } from "./mixins/withdrawExpectedMixins";
import { withdrawMetaFactoryMixin, withdrawZapMixin, withdrawLendingOrCryptoMixin, withdrawPlainMixin } from "./mixins/withdrawMixins";
import { withdrawWrapped2argsMixin, withdrawWrapped3argsMixin } from "./mixins/withdrawWrappedMixins";
import { withdrawImbalanceBonusMixin, withdrawOneCoinBonusMixin, withdrawOneCoinCryptoBonusMixin, withdrawImbalanceWrappedBonusMixin, withdrawOneCoinWrappedBonusMixin, withdrawOneCoinWrappedCryptoBonusMixin } from "./mixins/withdrawBonusMixins";
import { withdrawImbalanceMetaFactoryMixin, withdrawImbalanceZapMixin, withdrawImbalanceLendingMixin, withdrawImbalancePlainMixin } from "./mixins/withdrawImbalanceMixins";
import { withdrawImbalanceWrapped2argsMixin, withdrawImbalanceWrapped3argsMixin } from "./mixins/withdrawImbalanceWrappedMixins";
import { withdrawOneCoinExpectedMetaFactoryMixin, withdrawOneCoinExpectedZapMixin, withdrawOneCoinExpected3argsMixin, withdrawOneCoinExpected2argsMixin } from "./mixins/withdrawOneCoinExpectedMixins";
Expand Down Expand Up @@ -98,6 +99,24 @@ export const getPool = (poolId: string): PoolTemplate => {
Object.assign(Pool.prototype, withdrawPlainMixin);
}

// withdrawImbalanceBonus and withdrawOneCoinBonus
if (!poolDummy.isCrypto) {
Object.assign(Pool.prototype, withdrawImbalanceBonusMixin);
Object.assign(Pool.prototype, withdrawOneCoinBonusMixin);
} else {
Object.assign(Pool.prototype, withdrawOneCoinCryptoBonusMixin);
}

// withdrawImbalanceWrappedBonus and withdrawOneCoinWrappedBonus
if (!poolDummy.isPlain && !poolDummy.isFake) {
if (!poolDummy.isCrypto) {
Object.assign(Pool.prototype, withdrawImbalanceWrappedBonusMixin);
Object.assign(Pool.prototype, withdrawOneCoinWrappedBonusMixin);
} else {
Object.assign(Pool.prototype, withdrawOneCoinWrappedCryptoBonusMixin);
}
}

// withdrawWrapped and withdrawWrappedEstimateGas
if (!poolDummy.isPlain && !poolDummy.isFake) {
if ((poolDummy.isLending || poolDummy.isCrypto) && !poolDummy.zap) {
Expand Down

0 comments on commit 4959af4

Please sign in to comment.