Skip to content

Commit

Permalink
Hotfix: Metastable rounding issues (#762)
Browse files Browse the repository at this point in the history
* fix: correctly scale token amounts being passed to StableMaths to use priceRates

* fix: add some slack for rounding errors for exactOut exits

* 1.16.1
  • Loading branch information
TomAFrench authored Aug 30, 2021
1 parent a112bf0 commit bb9b46a
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 24 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@balancer-labs/frontend-v2",
"version": "1.16.0",
"version": "1.16.1",
"engines": {
"node": "14.x",
"npm": ">=7"
Expand Down
50 changes: 32 additions & 18 deletions src/services/pool/calculator/stable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ export default class Stable {
public exactTokensInForBPTOut(tokenAmounts: string[]): BigNumber {
const amp = bnum(this.calc.pool.onchain.amp?.toString() || '0');
const ampAdjusted = this.adjustAmp(amp);
const denormAmounts = this.calc.denormAmounts(
tokenAmounts,
this.calc.poolTokenDecimals.map(() => 18)
const amounts = this.calc.pool.tokens.map(({ priceRate }, i) =>
this.scaleInput(tokenAmounts[i], priceRate)
);
const amounts = denormAmounts.map(a => bnum(a.toString()));

const bptOut = SDK.StableMath._calcBptOutGivenExactTokensIn(
ampAdjusted,
Expand All @@ -40,6 +38,7 @@ export default class Stable {
return this.scaleOutput(
bptOut.toString(),
this.calc.poolDecimals,
'1',
BigNumber.ROUND_DOWN // If OUT given IN, round down
);
}
Expand All @@ -48,11 +47,9 @@ export default class Stable {
const amp = bnum(this.calc.pool.onchain.amp?.toString() || '0');
const ampAdjusted = this.adjustAmp(amp);

const denormAmounts = this.calc.denormAmounts(
tokenAmounts,
this.calc.poolTokenDecimals.map(() => 18)
const amounts = this.calc.pool.tokens.map(({ priceRate }, i) =>
this.scaleInput(tokenAmounts[i], priceRate)
);
const amounts = denormAmounts.map(a => bnum(a.toString()));

const bptIn = SDK.StableMath._calcBptInGivenExactTokensOut(
ampAdjusted,
Expand All @@ -65,15 +62,16 @@ export default class Stable {
return this.scaleOutput(
bptIn.toString(),
this.calc.poolDecimals,
'1',
BigNumber.ROUND_UP // If IN given OUT, round up
);
}

public bptInForExactTokenOut(amount: string, tokenIndex: number): BigNumber {
const amp = bnum(this.calc.pool.onchain.amp?.toString() || '0');
const ampAdjusted = this.adjustAmp(amp);
const amounts = this.calc.pool.tokenAddresses.map((address, i) => {
if (i === tokenIndex) return bnum(parseUnits(amount, 18).toString());
const amounts = this.calc.pool.tokens.map(({ priceRate }, i) => {
if (i === tokenIndex) return this.scaleInput(amount, priceRate);
return bnum('0');
});

Expand All @@ -88,6 +86,7 @@ export default class Stable {
return this.scaleOutput(
bptIn.toString(),
this.calc.poolDecimals,
'1',
BigNumber.ROUND_UP // If IN given OUT, round up
);
}
Expand All @@ -100,12 +99,13 @@ export default class Stable {
return this.scaleOutput(
'0',
this.calc.poolTokenDecimals[tokenIndex],
this.calc.pool.tokens[tokenIndex]?.priceRate ?? '1',
BigNumber.ROUND_DOWN // If OUT given IN, round down
);

const amp = bnum(this.calc.pool.onchain.amp?.toString() || '0');
const ampAdjusted = this.adjustAmp(amp);
const bptAmountIn = bnum(parseUnits(bptAmount, 18).toString());
const bptAmountIn = this.scaleInput(bptAmount);

const tokenAmountOut = SDK.StableMath._calcTokenOutGivenExactBptIn(
ampAdjusted,
Expand All @@ -119,6 +119,7 @@ export default class Stable {
return this.scaleOutput(
tokenAmountOut.toString(),
this.calc.poolTokenDecimals[tokenIndex],
this.calc.pool.tokens[tokenIndex]?.priceRate ?? '1',
BigNumber.ROUND_DOWN // If OUT given IN, round down
);
}
Expand Down Expand Up @@ -202,9 +203,10 @@ export default class Stable {
balance,
this.calc.poolTokenDecimals[i]
);
const scaledBalance = parseUnits(normalizedBalance, 18)
.mul(parseUnits(this.calc.pool.tokens[i].priceRate ?? '1', 18))
.div(parseUnits('1', 18));
const scaledBalance = this.scaleInput(
normalizedBalance,
this.calc.pool.tokens[i].priceRate
);
return bnum(scaledBalance.toString());
});
}
Expand All @@ -218,15 +220,27 @@ export default class Stable {
return bnum(scaledSupply.toString());
}

private scaleInput(normalizedAmount: string, priceRate = '1'): BigNumber {
const denormAmount = bnum(parseUnits(normalizedAmount, 18).toString())
.times(priceRate)
.toFixed(0, BigNumber.ROUND_UP);

return bnum(denormAmount.toString());
}

private scaleOutput(
amount: string,
decimals: number,
priceRate: string,
rounding: BigNumber.RoundingMode
): BigNumber {
const normalizedAmount = bnum(formatUnits(amount, 18)).toFixed(
decimals,
rounding
);
const amountAfterPriceRate = bnum(amount)
.div(priceRate)
.toString();

const normalizedAmount = bnum(amountAfterPriceRate)
.div(parseUnits('1', 18).toString())
.toFixed(decimals, rounding);
const scaledAmount = parseUnits(normalizedAmount, decimals);

return bnum(scaledAmount.toString());
Expand Down
10 changes: 7 additions & 3 deletions src/services/pool/exchange/serializers/ExitParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import PoolExchange from '..';
import { encodeExitStablePool } from '@/lib/utils/balancer/stablePoolEncoding';
import { encodeExitWeightedPool } from '@/lib/utils/balancer/weightedPoolEncoding';
import { parseUnits } from '@ethersproject/units';
import { BigNumberish } from '@ethersproject/bignumber';
import { BigNumber, BigNumberish } from '@ethersproject/bignumber';
import { isStable } from '@/composables/usePool';

export default class ExitParams {
Expand Down Expand Up @@ -41,14 +41,18 @@ export default class ExitParams {
account,
{
assets: this.exchange.pool.tokenAddresses,
minAmountsOut: parsedAmountsOut,
minAmountsOut: parsedAmountsOut.map(amount =>
// This is a hack to get around rounding issues for MetaStable pools
// TODO: do this more elegantly
amount.gt(0) ? amount.sub(1) : amount
),
userData: txData,
toInternalBalance: this.toInternalBalance
}
];
}

private parseAmounts(amounts: string[]): BigNumberish[] {
private parseAmounts(amounts: string[]): BigNumber[] {
return amounts.map((amount, i) => {
const token = this.exchange.pool.tokenAddresses[i];
return parseUnits(
Expand Down

4 comments on commit bb9b46a

@vercel
Copy link

@vercel vercel bot commented on bb9b46a Aug 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on bb9b46a Aug 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on bb9b46a Aug 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

app – ./

app-git-master-balancer.vercel.app
app.balancer.fi
app-balancer.vercel.app
pm2.vercel.app

@vercel
Copy link

@vercel vercel bot commented on bb9b46a Aug 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.