Skip to content

Commit

Permalink
Merge pull request #382 from curvefi/feat/seedAmounts
Browse files Browse the repository at this point in the history
Feat: seed amounts
  • Loading branch information
Macket authored Aug 19, 2024
2 parents 6faa712 + 644b086 commit 931a581
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 105 deletions.
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1607,7 +1607,9 @@ import curve from "@curvefi/api";
// factory-v2-221
const pool = curve.getPool(poolId);

await pool.depositAndStake([10, 10, 10]); // Initial amounts for stable pool must be equal
const amounts = await pool.getSeedAmounts(10);
// [ '10.0', '10.0', '10.0' ]
await pool.depositAndStake(amounts);
const balances = await pool.stats.underlyingBalances();
// [ '10.0', '10.0', '10.0' ]
})()
Expand Down Expand Up @@ -1748,17 +1750,19 @@ import curve from "@curvefi/api";
const pool = curve.getPool(poolId);

// Deposit & Stake Wrapped

await pool.depositAndStakeWrapped([10, 10]); // Initial wrapped amounts for stable metapool must be equal

const amounts = await pool.getSeedAmounts(10);
// [ '10', '9.666800376685890985' ]
await pool.depositAndStakeWrapped(amounts);
const balances = await pool.stats.wrappedBalances();
// [ '10.0', '10.0' ]
// [ '10', '9.666800376685890985' ]

// Or deposit & Stake Underlying

// const amounts = pool.metaUnderlyingSeedAmounts(30);
// [ '30', '10.000000000000000000', '10.000000', '10.000000' ]
// const amounts = pool.getSeedAmounts(10, true); // useUnderlying = true
// [ '10', '3.690404151768511181', '3.713621', '2.595975' ]
// await pool.depositAndStake(amounts);
// [ '30.0', '9.272021785560442569', '8.927595', '11.800485' ]
// [ '10', '3.690404151768511181', '3.713621', '2.595975' ]
})()
```
Expand Down Expand Up @@ -1891,7 +1895,7 @@ import curve from "@curvefi/api";
// factory-crypto-155
const pool = curve.getPool(poolId);

const amounts = await pool.cryptoSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice
const amounts = await pool.getSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice
// [ '30', '0.02' ]
await pool.depositAndStake(amounts);
const underlyingBalances = await pool.stats.underlyingBalances();
Expand Down Expand Up @@ -1943,7 +1947,7 @@ import curve from "@curvefi/api";
// factory-twocrypto-155
const pool = curve.getPool(poolId);

const amounts = await pool.cryptoSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice
const amounts = await pool.getSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice
// [ '30', '0.02' ]
await pool.depositAndStake(amounts);
const underlyingBalances = await pool.stats.underlyingBalances();
Expand Down Expand Up @@ -2030,7 +2034,7 @@ import curve from "@curvefi/api";
// factory-tricrypto-2
const pool = curve.getPool(poolId);

const amounts = await pool.cryptoSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice
const amounts = await pool.getSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice
// [ '30', '0.017647058823529412', '0.00111111' ]
await pool.depositAndStake(amounts);
const underlyingBalances = await pool.stats.underlyingBalances();
Expand Down
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.61.15",
"version": "2.62.0",
"description": "JavaScript library for curve.fi",
"main": "lib/index.js",
"author": "Macket",
Expand Down
165 changes: 77 additions & 88 deletions src/pools/PoolTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ export class PoolTemplate {

const decimals = useUnderlying ? this.underlyingDecimals : this.wrappedDecimals;
const amounts = _amounts.map((_a, i) => curve.formatUnits(_a, decimals[i]));
const seedAmounts = await this.cryptoSeedAmounts(amounts[0]); // Checks N coins is 2 or 3 and amounts > 0
const seedAmounts = await this.getSeedAmounts(amounts[0]); // Checks N coins is 2 or 3 and amounts > 0
amounts.forEach((a, i) => {
if (!BN(a).eq(BN(seedAmounts[i]))) throw Error(`Amounts must be = ${seedAmounts}`);
});
Expand Down Expand Up @@ -630,17 +630,10 @@ export class PoolTemplate {
const decimals = useUnderlying ? this.underlyingDecimals : this.wrappedDecimals;
const amounts = _amounts.map((_a, i) => curve.formatUnits(_a, decimals[i]));

if (this.isMeta && useUnderlying) {
const seedAmounts = this.metaUnderlyingSeedAmounts(amounts[0]); // Checks N coins == 2 and amounts > 0
amounts.forEach((a, i) => {
if (!BN(a).eq(BN(seedAmounts[i]))) throw Error(`Amounts must be = ${seedAmounts}`);
});
} else {
if (_amounts[0] <= curve.parseUnits("0")) throw Error("Initial deposit amounts must be > 0");
amounts.forEach((a) => {
if (a !== amounts[0]) throw Error("Initial deposit amounts must be equal");
});
}
const seedAmounts = await this.getSeedAmounts(amounts[0]); // Checks N coins == 2 and amounts > 0
amounts.forEach((a, i) => {
if (!BN(a).eq(BN(seedAmounts[i]))) throw Error(`Amounts must be = ${seedAmounts}`);
});

const _amounts18Decimals: bigint[] = amounts.map((a) => parseUnits(a));
return _amounts18Decimals.reduce((_a, _b) => _a + _b);
Expand Down Expand Up @@ -682,43 +675,50 @@ export class PoolTemplate {

// ---------------- DEPOSIT ----------------

public metaUnderlyingSeedAmounts(amount1: number | string): string[] {
if (this.isCrypto) throw Error(`Use cryptoSeedAmounts method for ${this.name} pool`);
if (!this.isMeta) throw Error("metaUnderlyingSeedAmounts method exists only for meta stable pools");

public async getSeedAmounts(amount1: number | string, useUnderlying = false): Promise<string[]> {
const amount1BN = BN(amount1);
if (amount1BN.lte(0)) throw Error("Initial deposit amounts must be > 0");

const amounts = [_cutZeros(amount1BN.toFixed(this.underlyingDecimals[0]))];
for (let i = 1; i < this.underlyingDecimals.length; i++) {
amounts.push(amount1BN.div(this.underlyingDecimals.length - 1).toFixed(this.underlyingDecimals[i]));
}
if (this.isCrypto) {
const decimals = this.isMeta ? this.wrappedDecimals : this.underlyingDecimals;

if (decimals.length === 2) {
const priceScaleBN = toBN(await curve.contracts[this.address].contract.price_scale(curve.constantOptions));
return [_cutZeros(amount1BN.toFixed(decimals[0])), _cutZeros(amount1BN.div(priceScaleBN).toFixed(decimals[1]))];
} else if (decimals.length === 3) {
const priceScaleBN = (await curve.multicallProvider.all([
curve.contracts[this.address].multicallContract.price_scale(0),
curve.contracts[this.address].multicallContract.price_scale(1),
]) as bigint[]).map((_p) => toBN(_p));
return [
_cutZeros(amount1BN.toFixed(decimals[0])),
_cutZeros(amount1BN.div(priceScaleBN[0]).toFixed(decimals[1])),
_cutZeros(amount1BN.div(priceScaleBN[1]).toFixed(decimals[2])),
];
}

return amounts
}
throw Error("getSeedAmounts method doesn't exist for crypto pools with N coins > 3");
} else {
const amounts = [_cutZeros(amount1BN.toFixed(this.wrappedDecimals[0]))];

public async cryptoSeedAmounts(amount1: number | string): Promise<string[]> {
if (!this.isCrypto) throw Error("cryptoSeedAmounts method doesn't exist for stable pools");
const decimals = this.isMeta ? this.wrappedDecimals : this.underlyingDecimals;
const amount1BN = BN(amount1);
if (amount1BN.lte(0)) throw Error("Initial deposit amounts must be > 0");
if (this.isMeta && useUnderlying) {
const basePool = new PoolTemplate(this.basePool);
const basePoolBalancesBN = (await basePool.stats.underlyingBalances()).map(BN);
const totalBN = basePoolBalancesBN.reduce((a, b) => a.plus(b));
for (let i = 1; i < this.underlyingDecimals.length; i++) {
amounts.push(amount1BN.times(basePoolBalancesBN[i - 1]).div(totalBN).toFixed(this.underlyingDecimals[i]));
}

if (decimals.length === 2) {
const priceScaleBN = toBN(await curve.contracts[this.address].contract.price_scale(curve.constantOptions));
return [_cutZeros(amount1BN.toFixed(decimals[0])), _cutZeros(amount1BN.div(priceScaleBN).toFixed(decimals[1]))];
} else if (decimals.length === 3) {
const priceScaleBN = (await curve.multicallProvider.all([
curve.contracts[this.address].multicallContract.price_scale(0),
curve.contracts[this.address].multicallContract.price_scale(1),
]) as bigint[]).map((_p) => toBN(_p));
return [
_cutZeros(amount1BN.toFixed(decimals[0])),
_cutZeros(amount1BN.div(priceScaleBN[0]).toFixed(decimals[1])),
_cutZeros(amount1BN.div(priceScaleBN[1]).toFixed(decimals[2])),
];
}
return amounts.map(_cutZeros)
}

const storedRatesBN = await this._storedRatesBN(false);
for (let i = 1; i < this.wrappedDecimals.length; i++) {
amounts.push(amount1BN.times(storedRatesBN[0]).div(storedRatesBN[i]).toFixed(this.wrappedDecimals[i]));
}

throw Error("cryptoSeedAmounts method doesn't exist for pools with N coins > 3");
return amounts.map(_cutZeros)
}
}

// OVERRIDE
Expand Down Expand Up @@ -757,31 +757,21 @@ export class PoolTemplate {

public async depositBonus(amounts: (number | string)[]): Promise<string> {
const amountsBN = amounts.map(BN);
let prices: number[] = [];


//for crvusd and stable-ng implementations
const isUseStoredRates = isMethodExist(curve.contracts[this.address].contract, 'stored_rates') && this.isPlain;
let pricesBN: BigNumber[] = [];

if(this.isCrypto || this.id === 'wsteth') {
prices = await this._underlyingPrices();
} else if (isUseStoredRates) {
const result = await this._stored_rates();
result.forEach((item, index) => {
prices.push(Number(item)/(10 ** (36 - this.underlyingDecimals[index])))
})
pricesBN = (await this._underlyingPrices()).map(BN);
} else {
prices = this.underlyingCoins.map(() => 1);
pricesBN = await this._storedRatesBN(true);
}

const pricesBN = prices.map(BN);
const balancesBN = (await this.stats.underlyingBalances()).map(BN);
const balancedAmounts = this._balancedAmountsWithSameValue(amountsBN, pricesBN, balancesBN);

const expectedBN = BN(await this.depositExpected(amounts));
const balancedExpectedBN = BN(await this.depositExpected(balancedAmounts));

return String(expectedBN.minus(balancedExpectedBN).div(balancedExpectedBN).times(100))
return expectedBN.minus(balancedExpectedBN).div(balancedExpectedBN).times(100).toString()
}

public async depositIsApproved(amounts: (number | string)[]): Promise<boolean> {
Expand Down Expand Up @@ -1666,29 +1656,21 @@ export class PoolTemplate {
}

public async withdrawImbalanceBonus(amounts: (number | string)[]): Promise<string> {
let prices: number[] = [];

//for crvusd and stable-ng implementations
const isUseStoredRates = isMethodExist(curve.contracts[this.address].contract, 'stored_rates') && this.isPlain;
let pricesBN: BigNumber[] = [];

if(this.isCrypto || this.id === 'wsteth') {
prices = await this._underlyingPrices();
} else if (isUseStoredRates) {
const result = await this._stored_rates();
result.forEach((item, index) => {
prices.push(Number(item)/(10 ** (36 - this.underlyingDecimals[index])))
})
pricesBN = (await this._underlyingPrices()).map(BN);
} else {
prices = this.underlyingCoins.map(() => 1);
pricesBN = await this._storedRatesBN(true);
}

const value = amounts.map(checkNumber).map(Number).reduce((s, a, i) => s + (a * prices[i]), 0);
const valueBN = amounts.map(BN).reduce((sBN, aBN, i) => pricesBN[i].times(aBN).plus(sBN), BN(0));
const lpTokenAmount = await this.withdrawImbalanceExpected(amounts);

const balancedAmounts = await this.withdrawExpected(lpTokenAmount);
const balancedValue = balancedAmounts.map(Number).reduce((s, a, i) => s + (a * prices[i]), 0);
const balancedValueBN = balancedAmounts.map(BN).reduce((sBN, aBN, i) => pricesBN[i].times(aBN).plus(sBN), BN(0));

return String((value - balancedValue) / balancedValue * 100);
return valueBN.minus(balancedValueBN).div(balancedValueBN).times(100).toString();
}

public async withdrawImbalanceIsApproved(amounts: (number | string)[]): Promise<boolean> {
Expand Down Expand Up @@ -1783,30 +1765,23 @@ export class PoolTemplate {
}

public async withdrawOneCoinBonus(lpTokenAmount: number | string, coin: string | number): Promise<string> {
let prices: number[] = [];

//for crvusd and stable-ng implementations
const isUseStoredRates = isMethodExist(curve.contracts[this.address].contract, 'stored_rates') && this.isPlain;
let pricesBN: BigNumber[] = [];

if(this.isCrypto || this.id === 'wsteth') {
prices = await this._underlyingPrices();
} else if (isUseStoredRates) {
const result = await this._stored_rates();
result.forEach((item, index) => {
prices.push(Number(item)/(10 ** (36 - this.underlyingDecimals[index])))
})
pricesBN = (await this._underlyingPrices()).map(BN);
} else {
prices = this.underlyingCoins.map(() => 1);
pricesBN = await this._storedRatesBN(true);
}
const coinPrice = prices[this._getCoinIdx(coin)];

const amount = Number(await this.withdrawOneCoinExpected(lpTokenAmount, coin));
const value = amount * coinPrice;
const coinPriceBN = pricesBN[this._getCoinIdx(coin)];

const amountBN = BN(await this.withdrawOneCoinExpected(lpTokenAmount, coin));
const valueBN = amountBN.times(coinPriceBN);

const balancedAmounts = await this.withdrawExpected(lpTokenAmount);
const balancedValue = balancedAmounts.map(Number).reduce((s, a, i) => s + (a * prices[i]), 0);
const balancedValueBN = balancedAmounts.map(BN).reduce((sBN, aBN, i) => pricesBN[i].times(aBN).plus(sBN), BN(0));

return String((value - balancedValue) / balancedValue * 100);
return valueBN.minus(balancedValueBN).div(balancedValueBN).times(100).toString();
}

public async withdrawOneCoinIsApproved(lpTokenAmount: number | string): Promise<boolean> {
Expand Down Expand Up @@ -2339,8 +2314,22 @@ export class PoolTemplate {
return addresses.length === 1 ? balances[addresses[0]] : balances
}

private _stored_rates = async (): Promise<number[]> => {
return await curve.contracts[this.address].contract.stored_rates();
private _storedRatesBN = async (useUnderlying: boolean): Promise<BigNumber[]> => {
if (this.isMeta) {
if (useUnderlying) return this.underlyingCoins.map(() => BN(1));

const _vp = await curve.contracts[curve.getPoolsData()[this.basePool].swap_address].contract.get_virtual_price();
return [BN(1), toBN(_vp)]
}

//for crvusd and stable-ng implementations
const isUseStoredRates = isMethodExist(curve.contracts[this.address].contract, 'stored_rates') && this.isPlain;
if (isUseStoredRates) {
const _stored_rates: bigint[] = await curve.contracts[this.address].contract.stored_rates();
return _stored_rates.map((_r, i) => toBN(_r, 36 - this.wrappedDecimals[i]));
}

return this.wrappedCoins.map(() => BN(1))
}

private _underlyingPrices = async (): Promise<number[]> => {
Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const MAX_ALLOWANCE = BigInt("1157920892373161954235709850086879078532699
// Formatting numbers

export const _cutZeros = (strn: string): string => {
return strn.replace(/0+$/gi, '').replace(/\.$/gi, '');
return strn.replace(/(\.\d*[1-9])0+$/gi, '$1').replace(/\.0+$/gi, '');
}

export const checkNumber = (n: number | string): number | string => {
Expand Down
14 changes: 9 additions & 5 deletions test/readme.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,9 @@ const deployPlainPoolTest = async () => {
console.log(poolId);
const pool = curve.getPool(poolId);

await pool.depositAndStake([10, 10, 10]); // Initial amounts for stable pool must be equal
const amounts = await pool.getSeedAmounts(10);
console.log(amounts);
await pool.depositAndStake(amounts); // Initial amounts for stable pool must be equal
const balances = await pool.stats.underlyingBalances();
console.log(balances);
}
Expand Down Expand Up @@ -660,13 +662,15 @@ const deployMetaPoolTest = async () => {

// Deposit & Stake Wrapped

await pool.depositAndStakeWrapped([10, 10]); // Initial wrapped amounts for stable metapool must be equal
const amounts = await pool.getSeedAmounts(10);
console.log(amounts)
await pool.depositAndStakeWrapped(amounts);
const balances = await pool.stats.wrappedBalances();
console.log(balances);

// Or deposit & Stake Underlying

// const amounts = pool.metaUnderlyingSeedAmounts(30);
// const amounts = pool.getSeedAmounts(10, true); // useUnderlying = true
// console.log(amounts);
// await pool.depositAndStake(amounts);
// console.log(await pool.stats.underlyingBalances());
Expand Down Expand Up @@ -731,7 +735,7 @@ const deployCryptoPoolTest = async () => {
console.log(poolId);
const pool = curve.getPool(poolId);

const amounts = await pool.cryptoSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice
const amounts = await pool.getSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice
console.log(amounts);
await pool.depositAndStake(amounts);
const underlyingBalances = await pool.stats.underlyingBalances();
Expand Down Expand Up @@ -798,7 +802,7 @@ const deployTricryptoPoolTest = async () => {
console.log(poolId);
const pool = curve.getPool(poolId);

const amounts = await pool.cryptoSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice
const amounts = await pool.getSeedAmounts(30); // Initial amounts for crypto pools must have the ratio corresponding to initialPrice
console.log(amounts);
await pool.depositAndStake(amounts);
const underlyingBalances = await pool.stats.underlyingBalances();
Expand Down

0 comments on commit 931a581

Please sign in to comment.