diff --git a/package.json b/package.json index fa9ae3b1..8298e10b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sor", - "version": "4.0.1-beta.16", + "version": "4.0.1-beta.17", "license": "GPL-3.0-only", "main": "dist/index.js", "module": "dist/index.esm.js", diff --git a/src/frontendHelpers/stableHelpers.ts b/src/frontendHelpers/stableHelpers.ts index 99ecfc00..fa772fd6 100644 --- a/src/frontendHelpers/stableHelpers.ts +++ b/src/frontendHelpers/stableHelpers.ts @@ -37,7 +37,7 @@ export function BPTForTokensZeroPriceImpact( const amountBPTOut = amounts.reduce((totalBptOut, amountIn, i) => { // Calculate amount of BPT gained per token in const poolPairData: StablePoolPairData = { - amp: amp, + amp: BigNumber.from(amp), allBalances: allBalancesDownScaled, tokenIndexIn: i, balanceOut: bptTotalSupply, diff --git a/src/pools/elementPool/elementPool.ts b/src/pools/elementPool/elementPool.ts index b83b9f23..3ad200ad 100644 --- a/src/pools/elementPool/elementPool.ts +++ b/src/pools/elementPool/elementPool.ts @@ -1,7 +1,7 @@ import { BigNumber, formatFixed, parseFixed } from '@ethersproject/bignumber'; import { WeiPerEther as ONE, Zero } from '@ethersproject/constants'; import { isSameAddress } from '../../utils'; -import { BigNumber as OldBigNumber, bnum } from '../../utils/bignumber'; +import { BigNumber as OldBigNumber, bnum, ZERO } from '../../utils/bignumber'; import { PoolBase, PoolTypes, @@ -20,6 +20,7 @@ import { _derivativeSpotPriceAfterSwapTokenInForExactTokenOut, getTimeTillExpiry, } from './elementMath'; +import { universalNormalizedLiquidity } from '../liquidity'; type ElementPoolToken = Pick; @@ -32,7 +33,7 @@ export type ElementPoolPairData = PoolPairBase & { currentBlockTimestamp: number; }; -export class ElementPool implements PoolBase { +export class ElementPool implements PoolBase { poolType: PoolTypes = PoolTypes.Element; id: string; address: string; @@ -147,16 +148,12 @@ export class ElementPool implements PoolBase { return poolPairData; } - // Normalized liquidity is an abstract term that can be thought of the - // inverse of the slippage. It is proportional to the token balances in the - // pool but also depends on the shape of the invariant curve. - // As a standard, we define normalized liquidity in tokenOut getNormalizedLiquidity(poolPairData: ElementPoolPairData): OldBigNumber { - // This could be refined by using the inverse of the slippage, but - // in practice this won't have a big impact in path selection for - // multi-hops so not a big priority - return bnum( - formatFixed(poolPairData.balanceOut, poolPairData.decimalsOut) + return universalNormalizedLiquidity( + this._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + ZERO + ) ); } diff --git a/src/pools/gyro2Pool/gyro2Pool.ts b/src/pools/gyro2Pool/gyro2Pool.ts index 4232ed38..cae710a6 100644 --- a/src/pools/gyro2Pool/gyro2Pool.ts +++ b/src/pools/gyro2Pool/gyro2Pool.ts @@ -1,7 +1,7 @@ import { getAddress } from '@ethersproject/address'; import { WeiPerEther as ONE, Zero } from '@ethersproject/constants'; import { formatFixed, BigNumber } from '@ethersproject/bignumber'; -import { BigNumber as OldBigNumber, bnum } from '../../utils/bignumber'; +import { BigNumber as OldBigNumber, bnum, ZERO } from '../../utils/bignumber'; import { PoolBase, @@ -20,7 +20,6 @@ import { _calculateNewSpotPrice, _derivativeSpotPriceAfterSwapExactTokenInForTokenOut, _derivativeSpotPriceAfterSwapTokenInForExactTokenOut, - _getNormalizedLiquidity, } from './gyro2Math'; import { _normalizeBalances, @@ -29,6 +28,7 @@ import { } from '../gyroHelpers/helpers'; import { mulDown, divDown } from '../gyroHelpers/gyroSignedFixedPoint'; import { SWAP_LIMIT_FACTOR } from '../gyroHelpers/constants'; +import { universalNormalizedLiquidity } from '../liquidity'; export type Gyro2PoolPairData = PoolPairBase & { sqrtAlpha: BigNumber; @@ -40,7 +40,7 @@ export type Gyro2PoolToken = Pick< 'address' | 'balance' | 'decimals' >; -export class Gyro2Pool implements PoolBase { +export class Gyro2Pool implements PoolBase { poolType: PoolTypes = PoolTypes.Gyro2; id: string; address: string; @@ -131,27 +131,12 @@ export class Gyro2Pool implements PoolBase { } getNormalizedLiquidity(poolPairData: Gyro2PoolPairData): OldBigNumber { - const balances = [poolPairData.balanceIn, poolPairData.balanceOut]; - const normalizedBalances = _normalizeBalances(balances, [ - poolPairData.decimalsIn, - poolPairData.decimalsOut, - ]); - const invariant = _calculateInvariant( - normalizedBalances, - poolPairData.sqrtAlpha, - poolPairData.sqrtBeta + return universalNormalizedLiquidity( + this._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + ZERO + ) ); - const [, virtualParamOut] = _findVirtualParams( - invariant, - poolPairData.sqrtAlpha, - poolPairData.sqrtBeta - ); - const normalisedLiquidity = _getNormalizedLiquidity( - normalizedBalances, - virtualParamOut - ); - - return bnum(formatFixed(normalisedLiquidity, 18)); } getLimitAmountSwap( diff --git a/src/pools/gyro3Pool/gyro3Pool.ts b/src/pools/gyro3Pool/gyro3Pool.ts index 5995597f..d4d0ebef 100644 --- a/src/pools/gyro3Pool/gyro3Pool.ts +++ b/src/pools/gyro3Pool/gyro3Pool.ts @@ -1,7 +1,7 @@ import { getAddress } from '@ethersproject/address'; import { WeiPerEther as ONE, Zero } from '@ethersproject/constants'; import { formatFixed, BigNumber } from '@ethersproject/bignumber'; -import { BigNumber as OldBigNumber, bnum } from '../../utils/bignumber'; +import { BigNumber as OldBigNumber, bnum, ZERO } from '../../utils/bignumber'; import { PoolBase, @@ -19,7 +19,6 @@ import { _calculateNewSpotPrice, _derivativeSpotPriceAfterSwapExactTokenInForTokenOut, _derivativeSpotPriceAfterSwapTokenInForExactTokenOut, - _getNormalizedLiquidity, } from './gyro3Math'; import { @@ -29,6 +28,7 @@ import { } from '../gyroHelpers/helpers'; import { mulDown, divDown } from '../gyroHelpers/gyroSignedFixedPoint'; import { SWAP_LIMIT_FACTOR } from '../gyroHelpers/constants'; +import { universalNormalizedLiquidity } from '../liquidity'; export type Gyro3PoolPairData = PoolPairBase & { balanceTertiary: BigNumber; // Balance of the unchanged asset @@ -40,7 +40,7 @@ export type Gyro3PoolToken = Pick< 'address' | 'balance' | 'decimals' >; -export class Gyro3Pool implements PoolBase { +export class Gyro3Pool implements PoolBase { poolType: PoolTypes = PoolTypes.Gyro3; id: string; address: string; @@ -147,31 +147,12 @@ export class Gyro3Pool implements PoolBase { } getNormalizedLiquidity(poolPairData: Gyro3PoolPairData): OldBigNumber { - const balances = [ - poolPairData.balanceIn, - poolPairData.balanceOut, - poolPairData.balanceTertiary, - ]; - const decimals = [ - poolPairData.decimalsIn, - poolPairData.decimalsOut, - poolPairData.decimalsTertiary, - ]; - const normalizedBalances = _normalizeBalances(balances, decimals); - - const invariant = _calculateInvariant( - normalizedBalances, - this.root3Alpha + return universalNormalizedLiquidity( + this._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + ZERO + ) ); - - const virtualOffsetInOut = mulDown(invariant, this.root3Alpha); - - const normalisedLiquidity = _getNormalizedLiquidity( - normalizedBalances, - virtualOffsetInOut - ); - - return bnum(formatFixed(normalisedLiquidity, 18)); } getLimitAmountSwap( diff --git a/src/pools/gyroEPool/gyroEPool.ts b/src/pools/gyroEPool/gyroEPool.ts index ac42d6fd..656bd08a 100644 --- a/src/pools/gyroEPool/gyroEPool.ts +++ b/src/pools/gyroEPool/gyroEPool.ts @@ -1,7 +1,7 @@ import { getAddress } from '@ethersproject/address'; import { WeiPerEther as ONE, Zero } from '@ethersproject/constants'; import { formatFixed, BigNumber } from '@ethersproject/bignumber'; -import { BigNumber as OldBigNumber, bnum } from '../../utils/bignumber'; +import { BigNumber as OldBigNumber, bnum, ZERO } from '../../utils/bignumber'; import { PoolBase, @@ -32,9 +32,9 @@ import { calcSpotPriceAfterSwapInGivenOut, calcDerivativePriceAfterSwapOutGivenIn, calcDerivativeSpotPriceAfterSwapInGivenOut, - calculateNormalizedLiquidity, } from './gyroEMath/gyroEMath'; import { SWAP_LIMIT_FACTOR } from '../gyroHelpers/constants'; +import { universalNormalizedLiquidity } from '../liquidity'; export type GyroEPoolPairData = PoolPairBase & { tokenInIsToken0: boolean; @@ -64,7 +64,7 @@ type DerivedGyroEParamsFromSubgraph = { dSq: string; }; -export class GyroEPool implements PoolBase { +export class GyroEPool implements PoolBase { poolType: PoolTypes = PoolTypes.GyroE; id: string; address: string; @@ -212,38 +212,12 @@ export class GyroEPool implements PoolBase { } getNormalizedLiquidity(poolPairData: GyroEPoolPairData): OldBigNumber { - const normalizedBalances = normalizeBalances( - [poolPairData.balanceIn, poolPairData.balanceOut], - [poolPairData.decimalsIn, poolPairData.decimalsOut] - ); - - const orderedNormalizedBalances = balancesFromTokenInOut( - normalizedBalances[0], - normalizedBalances[1], - poolPairData.tokenInIsToken0 - ); - - const [currentInvariant, invErr] = calculateInvariantWithError( - orderedNormalizedBalances, - this.gyroEParams, - this.derivedGyroEParams - ); - - const invariant: Vector2 = { - x: currentInvariant.add(invErr.mul(2)), - y: currentInvariant, - }; - - const normalizedLiquidity = calculateNormalizedLiquidity( - orderedNormalizedBalances, - this.gyroEParams, - this.derivedGyroEParams, - invariant, - this.swapFee, - poolPairData.tokenInIsToken0 + return universalNormalizedLiquidity( + this._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + ZERO + ) ); - - return bnum(formatFixed(normalizedLiquidity, 18)); } getLimitAmountSwap( diff --git a/src/pools/linearPool/linearPool.ts b/src/pools/linearPool/linearPool.ts index c3ba9b9d..0cd9416b 100644 --- a/src/pools/linearPool/linearPool.ts +++ b/src/pools/linearPool/linearPool.ts @@ -1,5 +1,5 @@ import { BigNumber, parseFixed, formatFixed } from '@ethersproject/bignumber'; -import { bnum, scale, ZERO } from '../../utils/bignumber'; +import { bnum, INFINITY, scale, ZERO } from '../../utils/bignumber'; import { BigNumber as OldBigNumber } from '../../utils/bignumber'; import { WeiPerEther as ONE, Zero } from '@ethersproject/constants'; import { isSameAddress } from '../../utils'; @@ -65,7 +65,7 @@ export type LinearPoolPairData = PoolPairBase & { virtualBptSupply: BigNumber; }; -export class LinearPool implements PoolBase { +export class LinearPool implements PoolBase { poolType: PoolTypes = PoolTypes.Linear; id: string; address: string; @@ -203,7 +203,9 @@ export class LinearPool implements PoolBase { // eslint-disable-next-line @typescript-eslint/no-unused-vars getNormalizedLiquidity(poolPairData: LinearPoolPairData): OldBigNumber { - return bnum(0); + return INFINITY; // It is the inverse of zero + // This is correct since linear pools have no price impact, + // except for the swap fee that is expected to be small. } getLimitAmountSwap( diff --git a/src/pools/liquidity.ts b/src/pools/liquidity.ts new file mode 100644 index 00000000..96bf698d --- /dev/null +++ b/src/pools/liquidity.ts @@ -0,0 +1,13 @@ +import { BigNumber as OldBigNumber, ZERO, bnum } from '../utils/bignumber'; + +/* +It is possible to compute the normalized liquidity using another function already existing at every pool type, which is the derivative of spot price after swap. +https://quark-ceres-740.notion.site/SOR-Normalized-liquidity-and-highest-liquidity-pool-d81bd3db48e5482ab2275a8eecac33b4 +*/ +export function universalNormalizedLiquidity( + derivativeSpotPriceAtZero: OldBigNumber +): OldBigNumber { + const ans = bnum(1).div(derivativeSpotPriceAtZero); + if (ans.isNaN() || ans.lt(ZERO) || !ans.isFinite()) return ZERO; + return ans; +} diff --git a/src/pools/metaStablePool/metaStablePool.ts b/src/pools/metaStablePool/metaStablePool.ts index 7126668e..c9ff49da 100644 --- a/src/pools/metaStablePool/metaStablePool.ts +++ b/src/pools/metaStablePool/metaStablePool.ts @@ -23,6 +23,7 @@ import { _calcTokensOutGivenExactBptIn, } from '../stablePool/stableMathBigInt'; import { StablePoolPairData } from '../stablePool/stablePool'; +import { universalNormalizedLiquidity } from '../liquidity'; type MetaStablePoolToken = Pick< SubgraphToken, @@ -34,7 +35,7 @@ export type MetaStablePoolPairData = StablePoolPairData & { tokenOutPriceRate: BigNumber; }; -export class MetaStablePool implements PoolBase { +export class MetaStablePool implements PoolBase { poolType: PoolTypes = PoolTypes.MetaStable; id: string; address: string; @@ -111,7 +112,7 @@ export class MetaStablePool implements PoolBase { // Get all token balances const allBalances = this.tokens.map(({ balance, priceRate }) => - bnum(balance).times(priceRate) + bnum(balance).times(bnum(priceRate)) ); const allBalancesScaled = this.tokens.map(({ balance, priceRate }) => parseFixed(balance, 18).mul(parseFixed(priceRate, 18)).div(ONE) @@ -141,11 +142,10 @@ export class MetaStablePool implements PoolBase { } getNormalizedLiquidity(poolPairData: MetaStablePoolPairData): OldBigNumber { - // This is an approximation as the actual normalized liquidity is a lot more complicated to calculate - return bnum( - formatFixed( - poolPairData.balanceOut.mul(poolPairData.amp), - poolPairData.decimalsOut + MetaStablePool.AMP_DECIMALS + return universalNormalizedLiquidity( + this._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + ZERO ) ); } @@ -354,8 +354,12 @@ export class MetaStablePool implements PoolBase { poolPairData: MetaStablePoolPairData, amount: OldBigNumber ): OldBigNumber { - const priceRateIn = formatFixed(poolPairData.tokenInPriceRate, 18); - const priceRateOut = formatFixed(poolPairData.tokenOutPriceRate, 18); + const priceRateIn = bnum( + formatFixed(poolPairData.tokenInPriceRate, 18) + ); + const priceRateOut = bnum( + formatFixed(poolPairData.tokenOutPriceRate, 18) + ); const amountConverted = amount.times( formatFixed(poolPairData.tokenInPriceRate, 18) ); @@ -370,8 +374,12 @@ export class MetaStablePool implements PoolBase { poolPairData: MetaStablePoolPairData, amount: OldBigNumber ): OldBigNumber { - const priceRateIn = formatFixed(poolPairData.tokenInPriceRate, 18); - const priceRateOut = formatFixed(poolPairData.tokenOutPriceRate, 18); + const priceRateIn = bnum( + formatFixed(poolPairData.tokenInPriceRate, 18) + ); + const priceRateOut = bnum( + formatFixed(poolPairData.tokenOutPriceRate, 18) + ); const amountConverted = amount.times( formatFixed(poolPairData.tokenOutPriceRate, 18) ); @@ -386,7 +394,9 @@ export class MetaStablePool implements PoolBase { poolPairData: MetaStablePoolPairData, amount: OldBigNumber ): OldBigNumber { - const priceRateOut = formatFixed(poolPairData.tokenOutPriceRate, 18); + const priceRateOut = bnum( + formatFixed(poolPairData.tokenOutPriceRate, 18) + ); return _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( amount, poolPairData @@ -397,8 +407,12 @@ export class MetaStablePool implements PoolBase { poolPairData: MetaStablePoolPairData, amount: OldBigNumber ): OldBigNumber { - const priceRateIn = formatFixed(poolPairData.tokenInPriceRate, 18); - const priceRateOut = formatFixed(poolPairData.tokenOutPriceRate, 18); + const priceRateIn = bnum( + formatFixed(poolPairData.tokenInPriceRate, 18) + ); + const priceRateOut = bnum( + formatFixed(poolPairData.tokenOutPriceRate, 18) + ); return _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( amount, poolPairData diff --git a/src/pools/phantomStablePool/phantomStableMath.ts b/src/pools/phantomStablePool/phantomStableMath.ts index b5b03ee0..f7b11217 100644 --- a/src/pools/phantomStablePool/phantomStableMath.ts +++ b/src/pools/phantomStablePool/phantomStableMath.ts @@ -21,8 +21,11 @@ import { PhantomStablePoolPairData } from './phantomStablePool'; // P = product of balances (n+1) * D + ( A * n^n āˆ’ 1)* (n^n * P / D^(nāˆ’1)) // // n = number of tokens // **********************************************************************************************/ + +const AMP_PRECISION_BNUM = bnum(1000); + export function _invariant( - amp: BigNumber, // amp + A: BigNumber, balances: OldBigNumber[] // balances ): OldBigNumber { let sum = ZERO; @@ -35,9 +38,9 @@ export function _invariant( } let prevInv = ZERO; let inv = sum; - // amp is passed as an ethers bignumber while maths uses bignumber.js - const ampAdjusted = bnum(formatFixed(amp, 3)); - const ampTimesNpowN = ampAdjusted.times(totalCoins ** totalCoins); // A*n^n + // A is passed as an ethers bignumber while maths uses bignumber.js + const AAdjusted = bnum(formatFixed(A, 3)); + const ATimesNpowN = AAdjusted.times(totalCoins ** totalCoins); // A*n^n for (let i = 0; i < 255; i++) { let P_D = bnum(totalCoins).times(balances[0]); @@ -50,11 +53,11 @@ export function _invariant( inv = bnum(totalCoins) .times(inv) .times(inv) - .plus(ampTimesNpowN.times(sum).times(P_D)) + .plus(ATimesNpowN.times(sum).times(P_D)) .div( bnum(totalCoins + 1) .times(inv) - .plus(ampTimesNpowN.minus(1).times(P_D)) + .plus(ATimesNpowN.minus(1).times(P_D)) ); // Equality with the precision of 1 if (inv.gt(prevInv)) { @@ -69,34 +72,6 @@ export function _invariant( return inv; } -// // This function has to be zero if the invariant D was calculated correctly -// // It was only used for double checking that the invariant was correct -// export function _invariantValueFunction( -// amp: BigNumber, // amp -// balances: BigNumber[], // balances -// D: BigNumber -// ): BigNumber { -// let invariantValueFunction; -// let prod = ONE; -// let sum = ZERO; -// for (let i = 0; i < balances.length; i++) { -// prod = prod.times(balances[i]); -// sum = sum.plus(balances[i]); -// } -// let n = bnum(balances.length); - -// // NOT! working based on Daniel's equation: https://www.notion.so/Analytical-for-2-tokens-1cd46debef6648dd81f2d75bae941fea -// // invariantValueFunction = amp.times(sum) -// // .plus((ONE.div(n.pow(n)).minus(amp)).times(D)) -// // .minus((ONE.div(n.pow(n.times(2)).times(prod))).times(D.pow(n.plus(ONE)))); -// invariantValueFunction = D.pow(n.plus(ONE)) -// .div(n.pow(n).times(prod)) -// .plus(D.times(amp.times(n.pow(n)).minus(ONE))) -// .minus(amp.times(n.pow(n)).times(sum)); - -// return invariantValueFunction; -// } - // Adapted from StableMath.sol, _outGivenIn() // * Added swap fee at very first line /********************************************************************************************** @@ -120,13 +95,16 @@ export function _exactTokenInForTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); + let tokenAmountIn = amount; tokenAmountIn = tokenAmountIn .times(EONE.sub(swapFee).toString()) .div(EONE.toString()); //Invariant is rounded up - const inv = _invariant(amp, balances); + const inv = _invariant(A, balances); let p = inv; let sum = ZERO; const totalCoins = bnum(balances.length); @@ -148,7 +126,7 @@ export function _exactTokenInForTokenOut( } //Calculate out balance - const y = _solveAnalyticalBalance(sum, inv, amp, n_pow_n, p); + const y = _solveAnalyticalBalance(sum, inv, A, n_pow_n, p); //Result is rounded down // return balances[tokenIndexOut] > y ? balances[tokenIndexOut].minus(y) : 0; @@ -178,9 +156,11 @@ export function _tokenInForExactTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const tokenAmountOut = amount; //Invariant is rounded up - const inv = _invariant(amp, balances); + const inv = _invariant(A, balances); let p = inv; let sum = ZERO; const totalCoins = bnum(balances.length); @@ -202,7 +182,7 @@ export function _tokenInForExactTokenOut( } //Calculate in balance - const y = _solveAnalyticalBalance(sum, inv, amp, n_pow_n, p); + const y = _solveAnalyticalBalance(sum, inv, A, n_pow_n, p); //Result is rounded up return y @@ -232,10 +212,12 @@ export function _tokenInForExactBPTOut( swapFee, } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const bptAmountOut = amount; // Get current invariant - const currentInvariant = _invariant(amp, balances); + const currentInvariant = _invariant(A, balances); // Calculate new invariant const bnumBalanceOut = bnum(formatFixed(virtualBptSupply, decimalsOut)); const newInvariant = bnumBalanceOut @@ -253,7 +235,7 @@ export function _tokenInForExactBPTOut( // get amountInAfterFee const newBalanceTokenIndex = _getTokenBalanceGivenInvariantAndAllOtherBalances( - amp, + A, balances, newInvariant, tokenIndexIn @@ -287,10 +269,12 @@ export function _BPTInForExactTokenOut( swapFee, } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const tokenAmountOut = amount; // Get current invariant - const currentInvariant = _invariant(amp, balances); + const currentInvariant = _invariant(A, balances); // First calculate the sum of all token balances which will be used to calculate // the current weights of each token relative to the sum of all balances @@ -320,7 +304,7 @@ export function _BPTInForExactTokenOut( balances[tokenIndexOut] = balances[tokenIndexOut].minus(amountOutBeforeFee); // get new invariant taking into account swap fees - const newInvariant = _invariant(amp, balances); + const newInvariant = _invariant(A, balances); // return amountBPTIn const bnumBalanceIn = bnum(formatFixed(virtualBptSupply, decimalsIn)); @@ -332,7 +316,7 @@ export function _BPTInForExactTokenOut( //This function calculates the balance of a given token (tokenIndex) // given all the other balances and the invariant function _getTokenBalanceGivenInvariantAndAllOtherBalances( - amp: BigNumber, + A: BigNumber, balances: OldBigNumber[], inv: OldBigNumber, tokenIndex: number @@ -355,23 +339,23 @@ function _getTokenBalanceGivenInvariantAndAllOtherBalances( } // Calculate token balance - return _solveAnalyticalBalance(sum, inv, amp, nPowN, p); + return _solveAnalyticalBalance(sum, inv, A, nPowN, p); } //This function calcuates the analytical solution to find the balance required export function _solveAnalyticalBalance( sum: OldBigNumber, inv: OldBigNumber, - amp: BigNumber, + A: BigNumber, n_pow_n: OldBigNumber, p: OldBigNumber ): OldBigNumber { - // amp is passed as an ethers bignumber while maths uses bignumber.js - const oldBN_amp = bnum(formatFixed(amp, 3)); + // A is passed as an ethers bignumber while maths uses bignumber.js + const oldBN_A = bnum(formatFixed(A, 3)); //Round up p - p = p.times(inv).div(oldBN_amp.times(n_pow_n).times(n_pow_n)); + p = p.times(inv).div(oldBN_A.times(n_pow_n).times(n_pow_n)); //Round down b - const b = sum.plus(inv.div(oldBN_amp.times(n_pow_n))); + const b = sum.plus(inv.div(oldBN_A.times(n_pow_n))); //Round up c // let c = inv >= b // ? inv.minus(b).plus(Math.sqrtUp(inv.minus(b).times(inv.minus(b)).plus(p.times(4)))) @@ -413,10 +397,12 @@ export function _exactTokenInForBPTOut( decimalsOut, } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const tokenAmountIn = amount; // Get current invariant - const currentInvariant = _invariant(amp, balances); + const currentInvariant = _invariant(A, balances); // First calculate the sum of all token balances which will be used to calculate // the current weights of each token relative to the sum of all balances @@ -447,7 +433,7 @@ export function _exactTokenInForBPTOut( balances[tokenIndexIn] = balances[tokenIndexIn].plus(amountInAfterFee); // get new invariant taking into account swap fees - const newInvariant = _invariant(amp, balances); + const newInvariant = _invariant(A, balances); const bnumBalanceOut = bnum(formatFixed(virtualBptSupply, decimalsOut)); @@ -471,6 +457,8 @@ export function _exactBPTInForTokenOut( const { amp, allBalances, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const bptAmountIn = amount; /********************************************************************************************** @@ -478,7 +466,7 @@ export function _exactBPTInForTokenOut( **********************************************************************************************/ // Get current invariant - const currentInvariant = _invariant(amp, balances); + const currentInvariant = _invariant(A, balances); // Calculate new invariant const bnumBalanceIn = bnum(formatFixed(poolPairData.virtualBptSupply, 18)); const newInvariant = bnumBalanceIn @@ -495,7 +483,7 @@ export function _exactBPTInForTokenOut( const newBalanceTokenIndex = _getTokenBalanceGivenInvariantAndAllOtherBalances( - amp, + A, balances, newInvariant, tokenIndexOut @@ -517,7 +505,7 @@ export function _exactBPTInForTokenOut( } export function _poolDerivatives( - amp: BigNumber, + A: BigNumber, balances: OldBigNumber[], tokenIndexIn: number, tokenIndexOut: number, @@ -525,7 +513,7 @@ export function _poolDerivatives( wrt_out: boolean ): OldBigNumber { const totalCoins = balances.length; - const D = _invariant(amp, balances); + const D = _invariant(A, balances); let S = ZERO; for (let i = 0; i < totalCoins; i++) { if (i != tokenIndexIn && i != tokenIndexOut) { @@ -534,9 +522,9 @@ export function _poolDerivatives( } const x = balances[tokenIndexIn]; const y = balances[tokenIndexOut]; - // amp is passed as an ethers bignumber while maths uses bignumber.js - const ampAdjusted = bnum(formatFixed(amp, 3)); - const a = ampAdjusted.times(totalCoins ** totalCoins); // = ampTimesNpowN + // A is passed as an ethers bignumber while maths uses bignumber.js + const AAdjusted = bnum(formatFixed(A, 3)); + const a = AAdjusted.times(totalCoins ** totalCoins); // = ATimesNpowN const b = S.minus(D).times(a).plus(D); const twoaxy = bnum(2).times(a).times(x).times(y); const partial_x = twoaxy.plus(a.times(y).times(y)).plus(b.times(y)); @@ -568,7 +556,7 @@ export function _poolDerivatives( ///////// export function _poolDerivativesBPT( - amp: BigNumber, + A: BigNumber, balances: OldBigNumber[], bptSupply: OldBigNumber, tokenIndexIn: number, @@ -577,7 +565,7 @@ export function _poolDerivativesBPT( wrt_out: boolean ): OldBigNumber { const totalCoins = balances.length; - const D = _invariant(amp, balances); + const D = _invariant(A, balances); let S = ZERO; let D_P = D.div(totalCoins); for (let i = 0; i < totalCoins; i++) { @@ -587,17 +575,19 @@ export function _poolDerivativesBPT( } } const x = balances[tokenIndexIn]; - const alpha = bnum(amp.toString()).times(totalCoins ** totalCoins); // = ampTimesNpowN + const alpha = bnum(A.toString()).times(totalCoins ** totalCoins); // = ATimesNpowN const beta = alpha.times(S); - const gamma = ONE.minus(alpha); + const gamma = AMP_PRECISION_BNUM.minus(alpha); const partial_x = bnum(2) .times(alpha) .times(x) .plus(beta) .plus(gamma.times(D)); - const minus_partial_D = D_P.times(totalCoins + 1).minus(gamma.times(x)); + const minus_partial_D = D_P.times(totalCoins + 1) + .times(AMP_PRECISION_BNUM) + .minus(gamma.times(x)); const partial_D = ZERO.minus(minus_partial_D); - let ans; + let ans: OldBigNumber; if (is_first_derivative) { ans = partial_x.div(minus_partial_D).times(bptSupply).div(D); } else { @@ -641,6 +631,8 @@ export function _spotPriceAfterSwapExactTokenInForTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); balances[tokenIndexIn] = balances[tokenIndexIn].plus( amount.times(EONE.sub(swapFee).toString()).div(EONE.toString()) ); @@ -648,7 +640,7 @@ export function _spotPriceAfterSwapExactTokenInForTokenOut( _exactTokenInForTokenOut(amount, poolPairData) ); let ans = _poolDerivatives( - amp, + A, balances, tokenIndexIn, tokenIndexOut, @@ -668,13 +660,15 @@ export function _spotPriceAfterSwapTokenInForExactTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const _in = _tokenInForExactTokenOut(amount, poolPairData) .times(EONE.sub(swapFee).toString()) .div(EONE.toString()); balances[tokenIndexIn] = balances[tokenIndexIn].plus(_in); balances[tokenIndexOut] = balances[tokenIndexOut].minus(amount); let ans = _poolDerivatives( - amp, + A, balances, tokenIndexIn, tokenIndexOut, @@ -718,6 +712,8 @@ export function _spotPriceAfterSwapExactTokenInForBPTOut( swapFee, } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const feeFactor = _feeFactor(balances, tokenIndexIn, swapFee); let bnumBalanceOut = bnum(formatFixed(virtualBptSupply, decimalsOut)); balances[tokenIndexIn] = balances[tokenIndexIn].plus( @@ -727,7 +723,7 @@ export function _spotPriceAfterSwapExactTokenInForBPTOut( _exactTokenInForBPTOut(amount, poolPairData) ); let ans = _poolDerivativesBPT( - amp, + A, balances, bnumBalanceOut, tokenIndexIn, @@ -754,13 +750,15 @@ export function _spotPriceAfterSwapTokenInForExactBPTOut( swapFee, } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const _in = _tokenInForExactBPTOut(amount, poolPairData); const feeFactor = _feeFactor(balances, tokenIndexIn, swapFee); balances[tokenIndexIn] = balances[tokenIndexIn].plus(_in.times(feeFactor)); let bnumBalanceOut = bnum(formatFixed(virtualBptSupply, decimalsOut)); bnumBalanceOut = bnumBalanceOut.plus(amount); let ans = _poolDerivativesBPT( - amp, + A, balances, bnumBalanceOut, tokenIndexIn, @@ -787,6 +785,8 @@ export function _spotPriceAfterSwapExactBPTInForTokenOut( decimalsIn, } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const _out = _exactBPTInForTokenOut(amount, poolPairData); const feeFactor = _feeFactor(balances, tokenIndexOut, swapFee); @@ -796,8 +796,9 @@ export function _spotPriceAfterSwapExactBPTInForTokenOut( _out.div(feeFactor) ); bnumBalanceIn = bnumBalanceIn.minus(amount); + const ans = _poolDerivativesBPT( - amp, + A, balances, bnumBalanceIn, tokenIndexOut, @@ -823,6 +824,9 @@ export function _spotPriceAfterSwapBPTInForExactTokenOut( swapFee, } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); + const feeFactor = _feeFactor(balances, tokenIndexOut, swapFee); balances[tokenIndexOut] = balances[tokenIndexOut].minus( amount.div(feeFactor) @@ -832,7 +836,7 @@ export function _spotPriceAfterSwapBPTInForExactTokenOut( _BPTInForExactTokenOut(amount, poolPairData) ); const ans = _poolDerivativesBPT( - amp, + A, balances, bnumBalanceIn, tokenIndexOut, @@ -856,6 +860,9 @@ export function _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); + balances[tokenIndexIn] = balances[tokenIndexIn].plus( amount.times(EONE.sub(swapFee).toString()).div(EONE.toString()) ); @@ -863,7 +870,7 @@ export function _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( _exactTokenInForTokenOut(amount, poolPairData) ); return _poolDerivatives( - amp, + A, balances, tokenIndexIn, tokenIndexOut, @@ -881,6 +888,9 @@ export function _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); + const bnumSwapFee = bnum(formatFixed(swapFee, 18)); const _in = _tokenInForExactTokenOut(amount, poolPairData).times( bnum(1).minus(bnumSwapFee) @@ -889,7 +899,7 @@ export function _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( balances[tokenIndexOut] = balances[tokenIndexOut].minus(amount); const feeFactor = bnum(1).minus(bnumSwapFee); return _poolDerivatives( - amp, + A, balances, tokenIndexIn, tokenIndexOut, @@ -907,6 +917,9 @@ export function _derivativeSpotPriceAfterSwapExactTokenInForBPTOut( const { amp, allBalances, balanceOut, decimalsOut, tokenIndexIn, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); + const feeFactor = _feeFactor(balances, tokenIndexIn, swapFee); balances[tokenIndexIn] = balances[tokenIndexIn].plus( amount.times(feeFactor) @@ -916,7 +929,7 @@ export function _derivativeSpotPriceAfterSwapExactTokenInForBPTOut( _exactTokenInForBPTOut(amount, poolPairData) ); const ans = _poolDerivativesBPT( - amp, + A, balances, bnumBalanceOut, tokenIndexIn, @@ -936,13 +949,16 @@ export function _derivativeSpotPriceAfterSwapTokenInForExactBPTOut( const { amp, allBalances, balanceOut, decimalsOut, tokenIndexIn, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); + const _in = _tokenInForExactBPTOut(amount, poolPairData); const feeFactor = _feeFactor(balances, tokenIndexIn, swapFee); balances[tokenIndexIn] = balances[tokenIndexIn].plus(_in.times(feeFactor)); let bnumBalanceOut = bnum(formatFixed(balanceOut, decimalsOut)); bnumBalanceOut = bnumBalanceOut.plus(amount); return _poolDerivativesBPT( - amp, + A, balances, bnumBalanceOut, tokenIndexIn, @@ -961,6 +977,9 @@ export function _derivativeSpotPriceAfterSwapBPTInForExactTokenOut( const { amp, allBalances, balanceIn, decimalsIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); + const _in = _BPTInForExactTokenOut(amount, poolPairData); const feeFactor = _feeFactor(balances, tokenIndexOut, swapFee); balances[tokenIndexOut] = balances[tokenIndexOut].minus( @@ -969,7 +988,7 @@ export function _derivativeSpotPriceAfterSwapBPTInForExactTokenOut( let bnumBalanceIn = bnum(formatFixed(balanceIn, decimalsIn)); bnumBalanceIn = bnumBalanceIn.minus(_in); const ans = _poolDerivativesBPT( - amp, + A, balances, bnumBalanceIn, tokenIndexOut, @@ -989,6 +1008,9 @@ export function _derivativeSpotPriceAfterSwapExactBPTInForTokenOut( const { amp, allBalances, balanceIn, decimalsIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); + const _out = _exactBPTInForTokenOut(amount, poolPairData); const feeFactor = _feeFactor(balances, tokenIndexOut, swapFee); balances[tokenIndexOut] = balances[tokenIndexOut].minus( @@ -997,7 +1019,7 @@ export function _derivativeSpotPriceAfterSwapExactBPTInForTokenOut( let bnumBalanceIn = bnum(formatFixed(balanceIn, decimalsIn)); bnumBalanceIn = bnumBalanceIn.minus(amount); const ans = _poolDerivativesBPT( - amp, + A, balances, bnumBalanceIn, tokenIndexOut, diff --git a/src/pools/phantomStablePool/phantomStablePool.ts b/src/pools/phantomStablePool/phantomStablePool.ts index dad84329..e6e819c1 100644 --- a/src/pools/phantomStablePool/phantomStablePool.ts +++ b/src/pools/phantomStablePool/phantomStablePool.ts @@ -22,6 +22,7 @@ import { import * as phantomStableMath from '../phantomStablePool/phantomStableMath'; import { MetaStablePoolPairData } from '../metaStablePool/metaStablePool'; import cloneDeep from 'lodash.clonedeep'; +import { universalNormalizedLiquidity } from '../liquidity'; enum PairTypes { BptToToken, @@ -40,7 +41,7 @@ export type PhantomStablePoolPairData = MetaStablePoolPairData & { virtualBptSupply: BigNumber; }; -export class PhantomStablePool implements PoolBase { +export class PhantomStablePool implements PoolBase { poolType: PoolTypes = PoolTypes.MetaStable; id: string; address: string; @@ -131,7 +132,7 @@ export class PhantomStablePool implements PoolBase { // Get all token balances const allBalances = this.tokens.map(({ balance, priceRate }) => - bnum(balance).times(priceRate) + bnum(balance).times(bnum(priceRate)) ); const allBalancesScaled = this.tokens.map(({ balance, priceRate }) => parseFixed(balance, 18).mul(parseFixed(priceRate, 18)).div(ONE) @@ -181,11 +182,10 @@ export class PhantomStablePool implements PoolBase { getNormalizedLiquidity( poolPairData: PhantomStablePoolPairData ): OldBigNumber { - // This is an approximation as the actual normalized liquidity is a lot more complicated to calculate - return bnum( - formatFixed( - poolPairData.balanceOut.mul(poolPairData.amp), - poolPairData.decimalsOut + PhantomStablePool.AMP_DECIMALS + return universalNormalizedLiquidity( + this._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + ZERO ) ); } @@ -425,11 +425,15 @@ export class PhantomStablePool implements PoolBase { poolPairData: PhantomStablePoolPairData, amount: OldBigNumber ): OldBigNumber { - const priceRateIn = formatFixed(poolPairData.tokenInPriceRate, 18); - const priceRateOut = formatFixed(poolPairData.tokenOutPriceRate, 18); - const amountConverted = amount.times( + const priceRateIn = bnum( formatFixed(poolPairData.tokenInPriceRate, 18) ); + const priceRateOut = bnum( + formatFixed(poolPairData.tokenOutPriceRate, 18) + ); + const amountConverted = amount.times( + bnum(formatFixed(poolPairData.tokenInPriceRate, 18)) + ); let result: OldBigNumber; if (poolPairData.pairType === PairTypes.TokenToBpt) { result = phantomStableMath._spotPriceAfterSwapExactTokenInForBPTOut( @@ -456,8 +460,12 @@ export class PhantomStablePool implements PoolBase { poolPairData: PhantomStablePoolPairData, amount: OldBigNumber ): OldBigNumber { - const priceRateIn = formatFixed(poolPairData.tokenInPriceRate, 18); - const priceRateOut = formatFixed(poolPairData.tokenOutPriceRate, 18); + const priceRateIn = bnum( + formatFixed(poolPairData.tokenInPriceRate, 18) + ); + const priceRateOut = bnum( + formatFixed(poolPairData.tokenOutPriceRate, 18) + ); const amountConverted = amount.times( formatFixed(poolPairData.tokenOutPriceRate, 18) ); @@ -486,7 +494,9 @@ export class PhantomStablePool implements PoolBase { poolPairData: PhantomStablePoolPairData, amount: OldBigNumber ): OldBigNumber { - const priceRateOut = formatFixed(poolPairData.tokenOutPriceRate, 18); + const priceRateOut = bnum( + formatFixed(poolPairData.tokenOutPriceRate, 18) + ); const amountConverted = amount.times( formatFixed(poolPairData.tokenInPriceRate, 18) ); @@ -517,8 +527,12 @@ export class PhantomStablePool implements PoolBase { poolPairData: PhantomStablePoolPairData, amount: OldBigNumber ): OldBigNumber { - const priceRateIn = formatFixed(poolPairData.tokenInPriceRate, 18); - const priceRateOut = formatFixed(poolPairData.tokenOutPriceRate, 18); + const priceRateIn = bnum( + formatFixed(poolPairData.tokenInPriceRate, 18) + ); + const priceRateOut = bnum( + formatFixed(poolPairData.tokenOutPriceRate, 18) + ); const amountConverted = amount.times( formatFixed(poolPairData.tokenOutPriceRate, 18) ); diff --git a/src/pools/stablePool/stableMath.ts b/src/pools/stablePool/stableMath.ts index 2fc94320..018fcf1e 100644 --- a/src/pools/stablePool/stableMath.ts +++ b/src/pools/stablePool/stableMath.ts @@ -21,7 +21,7 @@ import { StablePoolPairData } from './stablePool'; // n = number of tokens // **********************************************************************************************/ export function _invariant( - amp: BigNumber, // amp + A: BigNumber, balances: OldBigNumber[] // balances ): OldBigNumber { let sum = ZERO; @@ -35,9 +35,9 @@ export function _invariant( let prevInv = ZERO; let inv = sum; - // amp is passed as an ethers bignumber while maths uses bignumber.js - const ampAdjusted = bnum(formatFixed(amp, 3)); - const ampTimesNpowN = ampAdjusted.times(totalCoins ** totalCoins); // A*n^n + // A is passed as an ethers bignumber while maths uses bignumber.js + const AAdjusted = bnum(formatFixed(A, 3)); + const ATimesNpowN = AAdjusted.times(totalCoins ** totalCoins); // A*n^n for (let i = 0; i < 255; i++) { let P_D = bnum(totalCoins).times(balances[0]); @@ -50,11 +50,11 @@ export function _invariant( inv = bnum(totalCoins) .times(inv) .times(inv) - .plus(ampTimesNpowN.times(sum).times(P_D)) + .plus(ATimesNpowN.times(sum).times(P_D)) .div( bnum(totalCoins + 1) .times(inv) - .plus(ampTimesNpowN.minus(1).times(P_D)) + .plus(ATimesNpowN.minus(1).times(P_D)) ); // Equality with the precision of 1 if (inv.gt(prevInv)) { @@ -92,13 +92,15 @@ export function _exactTokenInForTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); let tokenAmountIn = amount; tokenAmountIn = tokenAmountIn .times(EONE.sub(swapFee).toString()) .div(EONE.toString()); //Invariant is rounded up - const inv = _invariant(amp, balances); + const inv = _invariant(A, balances); let p = inv; let sum = ZERO; const totalCoins = bnum(balances.length); @@ -120,7 +122,7 @@ export function _exactTokenInForTokenOut( } //Calculate out balance - const y = _solveAnalyticalBalance(sum, inv, amp, n_pow_n, p); + const y = _solveAnalyticalBalance(sum, inv, A, n_pow_n, p); //Result is rounded down // return balances[tokenIndexOut] > y ? balances[tokenIndexOut].minus(y) : 0; @@ -150,9 +152,11 @@ export function _tokenInForExactTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const tokenAmountOut = amount; //Invariant is rounded up - const inv = _invariant(amp, balances); + const inv = _invariant(A, balances); let p = inv; let sum = ZERO; const totalCoins = bnum(balances.length); @@ -174,7 +178,7 @@ export function _tokenInForExactTokenOut( } //Calculate in balance - const y = _solveAnalyticalBalance(sum, inv, amp, n_pow_n, p); + const y = _solveAnalyticalBalance(sum, inv, A, n_pow_n, p); //Result is rounded up return y @@ -184,36 +188,22 @@ export function _tokenInForExactTokenOut( } //This function calcuates the analytical solution to find the balance required -function _solveAnalyticalBalance( +export function _solveAnalyticalBalance( sum: OldBigNumber, inv: OldBigNumber, - amp: BigNumber, + A: BigNumber, n_pow_n: OldBigNumber, p: OldBigNumber ): OldBigNumber { - // amp is passed as an ethers bignumber while maths uses bignumber.js - const oldBN_amp = bnum(formatFixed(amp, 3)); + // A is passed as an ethers bignumber while maths uses bignumber.js + const oldBN_A = bnum(formatFixed(A, 3)); //Round up p - p = p.times(inv).div(oldBN_amp.times(n_pow_n).times(n_pow_n)); + p = p.times(inv).div(oldBN_A.times(n_pow_n).times(n_pow_n)); //Round down b - const b = sum.plus(inv.div(oldBN_amp.times(n_pow_n))); - //Round up c - // let c = inv >= b - // ? inv.minus(b).plus(Math.sqrtUp(inv.minus(b).times(inv.minus(b)).plus(p.times(4)))) - // : Math.sqrtUp(b.minus(inv).times(b.minus(inv)).plus(p.times(4))).minus(b.minus(inv)); - let c; - if (inv.gte(b)) { - c = inv - .minus(b) - .plus(inv.minus(b).times(inv.minus(b)).plus(p.times(4)).sqrt()); - } else { - c = b - .minus(inv) - .times(b.minus(inv)) - .plus(p.times(4)) - .sqrt() - .minus(b.minus(inv)); - } + const b = sum.plus(inv.div(oldBN_A.times(n_pow_n))); + const c = inv + .minus(b) + .plus(inv.minus(b).times(inv.minus(b)).plus(p.times(4)).sqrt()); //Round up y return c.div(2); } @@ -223,7 +213,7 @@ function _solveAnalyticalBalance( ////////////////////// function _poolDerivatives( - amp: BigNumber, + A: BigNumber, balances: OldBigNumber[], tokenIndexIn: number, tokenIndexOut: number, @@ -231,7 +221,7 @@ function _poolDerivatives( wrt_out: boolean ): OldBigNumber { const totalCoins = balances.length; - const D = _invariant(amp, balances); + const D = _invariant(A, balances); let S = ZERO; for (let i = 0; i < totalCoins; i++) { if (i != tokenIndexIn && i != tokenIndexOut) { @@ -240,9 +230,9 @@ function _poolDerivatives( } const x = balances[tokenIndexIn]; const y = balances[tokenIndexOut]; - // amp is passed as an ethers bignumber while maths uses bignumber.js - const ampAdjusted = bnum(formatFixed(amp, 3)); - const a = ampAdjusted.times(totalCoins ** totalCoins); // = ampTimesNpowN + // A is passed as an ethers bignumber while maths uses bignumber.js + const AAdjusted = bnum(formatFixed(A, 3)); + const a = AAdjusted.times(totalCoins ** totalCoins); // = ATimesNpowN const b = S.minus(D).times(a).plus(D); const twoaxy = bnum(2).times(a).times(x).times(y); const partial_x = twoaxy.plus(a.times(y).times(y)).plus(b.times(y)); @@ -282,6 +272,8 @@ export function _spotPriceAfterSwapExactTokenInForTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); balances[tokenIndexIn] = balances[tokenIndexIn].plus( amount.times(EONE.sub(swapFee).toString()).div(EONE.toString()) ); @@ -289,7 +281,7 @@ export function _spotPriceAfterSwapExactTokenInForTokenOut( _exactTokenInForTokenOut(amount, poolPairData) ); let ans = _poolDerivatives( - amp, + A, balances, tokenIndexIn, tokenIndexOut, @@ -309,13 +301,15 @@ export function _spotPriceAfterSwapTokenInForExactTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const _in = _tokenInForExactTokenOut(amount, poolPairData) .times(EONE.sub(swapFee).toString()) .div(EONE.toString()); balances[tokenIndexIn] = balances[tokenIndexIn].plus(_in); balances[tokenIndexOut] = balances[tokenIndexOut].minus(amount); let ans = _poolDerivatives( - amp, + A, balances, tokenIndexIn, tokenIndexOut, @@ -339,6 +333,8 @@ export function _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); balances[tokenIndexIn] = balances[tokenIndexIn].plus( amount.times(EONE.sub(swapFee).toString()).div(EONE.toString()) ); @@ -346,7 +342,7 @@ export function _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( _exactTokenInForTokenOut(amount, poolPairData) ); return _poolDerivatives( - amp, + A, balances, tokenIndexIn, tokenIndexOut, @@ -364,6 +360,8 @@ export function _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const _in = _tokenInForExactTokenOut(amount, poolPairData) .times(EONE.sub(swapFee).toString()) .div(EONE.toString()); @@ -371,7 +369,7 @@ export function _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( balances[tokenIndexOut] = balances[tokenIndexOut].minus(amount); const feeFactor = EONE.div(swapFee).toString(); return _poolDerivatives( - amp, + A, balances, tokenIndexIn, tokenIndexOut, @@ -409,11 +407,13 @@ export function _spotPriceAfterSwapTokenInForExactBPTOut( const { amp, allBalances, balanceOut, tokenIndexIn, decimalsOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const _in = _tokenInForExactBPTOut(amount, poolPairData); const feeFactor = _feeFactor(balances, tokenIndexIn, swapFee); balances[tokenIndexIn] = balances[tokenIndexIn].plus(_in.times(feeFactor)); let ans = _poolDerivativesBPT( - amp, + A, balances, bnum(formatFixed(balanceOut, decimalsOut)).plus(amount), tokenIndexIn, @@ -440,6 +440,8 @@ function _tokenInForExactBPTOut( const { amp, allBalances, tokenIndexIn, tokenIndexOut, swapFee } = poolPairData; const balances = [...allBalances]; + const n = balances.length; + const A = amp.div(n ** (n - 1)); const bptAmountOut = amount; /********************************************************************************************** @@ -447,7 +449,7 @@ function _tokenInForExactBPTOut( **********************************************************************************************/ // Get current invariant - const currentInvariant = _invariant(amp, balances); + const currentInvariant = _invariant(A, balances); // Calculate new invariant const newInvariant = allBalances[tokenIndexOut] .plus(bptAmountOut) @@ -464,7 +466,7 @@ function _tokenInForExactBPTOut( // get amountInAfterFee const newBalanceTokenIndex = _getTokenBalanceGivenInvariantAndAllOtherBalances( - amp, + A, balances, newInvariant, tokenIndexIn @@ -488,7 +490,7 @@ function _tokenInForExactBPTOut( //This function calculates the balance of a given token (tokenIndex) // given all the other balances and the invariant function _getTokenBalanceGivenInvariantAndAllOtherBalances( - amp: BigNumber, + A: BigNumber, balances: OldBigNumber[], inv: OldBigNumber, tokenIndex: number @@ -511,11 +513,11 @@ function _getTokenBalanceGivenInvariantAndAllOtherBalances( } // Calculate token balance - return _solveAnalyticalBalance(sum, inv, amp, nPowN, p); + return _solveAnalyticalBalance(sum, inv, A, nPowN, p); } function _poolDerivativesBPT( - amp: BigNumber, + A: BigNumber, balances: OldBigNumber[], bptSupply: OldBigNumber, tokenIndexIn: number, @@ -524,7 +526,7 @@ function _poolDerivativesBPT( wrt_out: boolean ): OldBigNumber { const totalCoins = balances.length; - const D = _invariant(amp, balances); + const D = _invariant(A, balances); let S = ZERO; let D_P = D.div(totalCoins); for (let i = 0; i < totalCoins; i++) { @@ -534,9 +536,9 @@ function _poolDerivativesBPT( } } const x = balances[tokenIndexIn]; - // amp is passed as an ethers bignumber while maths uses bignumber.js - const ampAdjusted = bnum(formatFixed(amp, 3)); - const alpha = ampAdjusted.times(totalCoins ** totalCoins); // = ampTimesNpowN + // A is passed as an ethers bignumber while maths uses bignumber.js + const AAdjusted = bnum(formatFixed(A, 3)); + const alpha = AAdjusted.times(totalCoins ** totalCoins); // = ATimesNpowN const beta = alpha.times(S); const gamma = ONE.minus(alpha); const partial_x = bnum(2) diff --git a/src/pools/stablePool/stablePool.ts b/src/pools/stablePool/stablePool.ts index 960e16c9..b0eec18e 100644 --- a/src/pools/stablePool/stablePool.ts +++ b/src/pools/stablePool/stablePool.ts @@ -28,6 +28,7 @@ import { _calcBptOutGivenExactTokensIn, _calcTokensOutGivenExactBptIn, } from './stableMathBigInt'; +import { universalNormalizedLiquidity } from '../liquidity'; type StablePoolToken = Pick; @@ -39,7 +40,7 @@ export type StablePoolPairData = PoolPairBase & { tokenIndexOut: number; }; -export class StablePool implements PoolBase { +export class StablePool implements PoolBase { poolType: PoolTypes = PoolTypes.Stable; id: string; address: string; @@ -129,11 +130,10 @@ export class StablePool implements PoolBase { } getNormalizedLiquidity(poolPairData: StablePoolPairData): OldBigNumber { - // This is an approximation as the actual normalized liquidity is a lot more complicated to calculate - return bnum( - formatFixed( - poolPairData.balanceOut.mul(poolPairData.amp), - poolPairData.decimalsOut + StablePool.AMP_DECIMALS + return universalNormalizedLiquidity( + this._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + ZERO ) ); } diff --git a/src/pools/weightedPool/weightedPool.ts b/src/pools/weightedPool/weightedPool.ts index 9ea147bf..1ca516ee 100644 --- a/src/pools/weightedPool/weightedPool.ts +++ b/src/pools/weightedPool/weightedPool.ts @@ -36,8 +36,8 @@ import { } from './weightedMath'; import { BigNumber, formatFixed, parseFixed } from '@ethersproject/bignumber'; import { WeiPerEther as ONE, Zero } from '@ethersproject/constants'; -import { takeToPrecision18 } from '../../router/helpersClass'; import { MathSol } from '../../utils/basicOperations'; +import { universalNormalizedLiquidity } from '../liquidity'; enum PairTypes { BptToToken, @@ -56,7 +56,7 @@ export type WeightedPoolPairData = PoolPairBase & { weightOut: BigNumber; }; -export class WeightedPool implements PoolBase { +export class WeightedPool implements PoolBase { poolType: PoolTypes = PoolTypes.Weighted; id: string; address: string; @@ -160,18 +160,11 @@ export class WeightedPool implements PoolBase { ); } - // Normalized liquidity is an abstract term that can be thought of the - // inverse of the slippage. It is proportional to the token balances in the - // pool but also depends on the shape of the invariant curve. - // As a standard, we define normalized liquidity in tokenOut getNormalizedLiquidity(poolPairData: WeightedPoolPairData): OldBigNumber { - // this should be different if tokenIn or tokenOut are the BPT - return bnum( - formatFixed( - poolPairData.balanceOut - .mul(poolPairData.weightIn) - .div(poolPairData.weightIn.add(poolPairData.weightOut)), - poolPairData.decimalsOut + return universalNormalizedLiquidity( + this._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + ZERO ) ); } @@ -221,13 +214,13 @@ export class WeightedPool implements PoolBase { const amountIn = parseFixed(amount.dp(18, 1).toString(), 18).toBigInt(); const decimalsIn = poolPairData.decimalsIn; const decimalsOut = poolPairData.decimalsOut; - const balanceIn = takeToPrecision18( - poolPairData.balanceIn, - decimalsIn + const balanceIn = parseFixed( + poolPairData.balanceIn.toString(), + 18 - decimalsIn ).toBigInt(); - const balanceOut = takeToPrecision18( - poolPairData.balanceOut, - decimalsOut + const balanceOut = parseFixed( + poolPairData.balanceOut.toString(), + 18 - decimalsOut ).toBigInt(); const normalizedWeightIn = poolPairData.weightIn.toBigInt(); const normalizedWeightOut = poolPairData.weightOut.toBigInt(); @@ -282,13 +275,13 @@ export class WeightedPool implements PoolBase { ).toBigInt(); const decimalsIn = poolPairData.decimalsIn; const decimalsOut = poolPairData.decimalsOut; - const balanceIn = takeToPrecision18( - poolPairData.balanceIn, - decimalsIn + const balanceIn = parseFixed( + poolPairData.balanceIn.toString(), + 18 - decimalsIn ).toBigInt(); - const balanceOut = takeToPrecision18( - poolPairData.balanceOut, - decimalsOut + const balanceOut = parseFixed( + poolPairData.balanceOut.toString(), + 18 - decimalsOut ).toBigInt(); const normalizedWeightIn = poolPairData.weightIn.toBigInt(); const normalizedWeightOut = poolPairData.weightOut.toBigInt(); diff --git a/src/router/helpersClass.ts b/src/router/helpersClass.ts index aa3b0af6..81bc22d7 100644 --- a/src/router/helpersClass.ts +++ b/src/router/helpersClass.ts @@ -401,23 +401,3 @@ export function EVMgetOutputAmountSwap( return returnAmount; } - -export function takeToPrecision18( - amount: BigNumber, - decimals: number -): BigNumber { - for (let i = 0; i < 18 - decimals; i++) { - amount = amount.mul(10); - } - return amount; -} - -export function restorePrecision( - amount: BigNumber, - decimals: number -): BigNumber { - for (let i = 0; i < 18 - decimals; i++) { - amount = amount.div(10); - } - return amount; -} diff --git a/src/types.ts b/src/types.ts index d8c3358d..605344d1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -188,7 +188,7 @@ export enum PoolFilter { GyroE = 'GyroE', } -export interface PoolBase { +export interface PoolBase { poolType: PoolTypes; id: string; address: string; @@ -197,46 +197,43 @@ export interface PoolBase { totalShares: BigNumber; mainIndex?: number; isLBP?: boolean; - parsePoolPairData: (tokenIn: string, tokenOut: string) => PoolPairBase; - getNormalizedLiquidity: (poolPairData: PoolPairBase) => OldBigNumber; - getLimitAmountSwap: ( - poolPairData: PoolPairBase, - swapType: SwapTypes - ) => OldBigNumber; + parsePoolPairData: (tokenIn: string, tokenOut: string) => D; + getNormalizedLiquidity: (poolPairData: D) => OldBigNumber; + getLimitAmountSwap: (poolPairData: D, swapType: SwapTypes) => OldBigNumber; /** * @param {string} token - Address of token. * @param {BigNumber} newBalance - New balance of token. EVM scaled. */ updateTokenBalanceForPool: (token: string, newBalance: BigNumber) => void; _exactTokenInForTokenOut: ( - poolPairData: PoolPairBase, + poolPairData: D, amount: OldBigNumber ) => OldBigNumber; _tokenInForExactTokenOut: ( - poolPairData: PoolPairBase, + poolPairData: D, amount: OldBigNumber ) => OldBigNumber; _calcTokensOutGivenExactBptIn(bptAmountIn: BigNumber): BigNumber[]; _calcBptOutGivenExactTokensIn(amountsIn: BigNumber[]): BigNumber; _spotPriceAfterSwapExactTokenInForTokenOut: ( - poolPairData: PoolPairBase, + poolPairData: D, amount: OldBigNumber ) => OldBigNumber; _spotPriceAfterSwapTokenInForExactTokenOut: ( - poolPairData: PoolPairBase, + poolPairData: D, amount: OldBigNumber ) => OldBigNumber; _derivativeSpotPriceAfterSwapExactTokenInForTokenOut: ( - poolPairData: PoolPairBase, + poolPairData: D, amount: OldBigNumber ) => OldBigNumber; _derivativeSpotPriceAfterSwapTokenInForExactTokenOut: ( - poolPairData: PoolPairBase, + poolPairData: D, amount: OldBigNumber ) => OldBigNumber; } -export interface WeightedPool extends PoolBase { +export interface WeightedPool extends PoolBase { totalWeight: string; } diff --git a/test/composableStablePool.spec.ts b/test/composableStablePool.spec.ts new file mode 100644 index 00000000..8f575005 --- /dev/null +++ b/test/composableStablePool.spec.ts @@ -0,0 +1,71 @@ +// TS_NODE_PROJECT='tsconfig.testing.json' npx mocha -r ts-node/register test/composableStablePool.spec.ts +import { assert } from 'chai'; +import { BigNumber } from '@ethersproject/bignumber'; +import { bnum } from '../src/utils/bignumber'; +import { WeiPerEther as ONE } from '@ethersproject/constants'; +import composableStable from './testData/phantomStablePools/composableStable.json'; +import { PhantomStablePool } from '../src/pools/phantomStablePool/phantomStablePool'; +import * as phantomStableMath from '../src/pools/phantomStablePool/phantomStableMath'; +import * as stableMathBigInt from '../src/pools/stablePool/stableMathBigInt'; +import { ADDRESSES, Network } from './testScripts/constants'; + +describe('composable stable pool', () => { + const oldBN_ONE = bnum(ONE.toString()); + const error = 0.00001; + context('out given in, several consistencies', () => { + const composableStablePool = PhantomStablePool.fromPool( + composableStable.pools[0] + ); + // parsePoolPairData contains pool's allBalances and allBalancesScaled + // both already multiplied by the price rates. + const poolPairData = composableStablePool.parsePoolPairData( + ADDRESSES[Network.MAINNET].bbausdt2.address, + ADDRESSES[Network.MAINNET].bbausd2.address + ); + it('token -> BPT spot price with no rate', () => { + // spot prices + poolPairData.swapFee = BigNumber.from(0); + const spPhantom = + phantomStableMath._spotPriceAfterSwapExactTokenInForBPTOut( + bnum(0), + poolPairData + ); + + const balances = poolPairData.allBalancesScaled.map((balance) => + balance.toBigInt() + ); + const spBigInt = + stableMathBigInt._spotPriceAfterSwapExactTokenInForBPTOut( + poolPairData.amp.toBigInt(), + balances, + poolPairData.tokenIndexIn, + composableStablePool.totalShares.toBigInt(), + BigInt(0) + ); + const bnumSpBigInt = bnum(spBigInt.toString()).div(oldBN_ONE); + assert.approximately( + spPhantom.div(bnumSpBigInt).toNumber(), + 1, + error, + 'wrong result' + ); + }); + it('composableStablePool token -> BPT', () => { + const bptOut = composableStablePool._exactTokenInForTokenOut( + poolPairData, + bnum(1) + ); + const poolSP = + composableStablePool._spotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + bnum(0) + ); + assert.approximately( + poolSP.times(bptOut).toNumber(), + 1, + error, + 'wrong result' + ); + }); + }); +}); diff --git a/test/fullSwaps.spec.ts b/test/fullSwaps.spec.ts index ffd8104d..d478dca9 100644 --- a/test/fullSwaps.spec.ts +++ b/test/fullSwaps.spec.ts @@ -428,7 +428,7 @@ describe('Tests full swaps against known values', () => { swapInfo.tokenAddresses[swapInfo.swaps[0].assetOutIndex], USDC.address ); - assert.equal(swapInfo.swaps[0].amount, '692256505473431402'); + assert.equal(swapInfo.swaps[0].amount, '692256505473389013'); assert.equal( swapInfo.swaps[1].poolId, '0x57755f7dec33320bca83159c26e93751bfd30fbe' @@ -441,7 +441,7 @@ describe('Tests full swaps against known values', () => { swapInfo.tokenAddresses[swapInfo.swaps[1].assetOutIndex], USDC.address ); - assert.equal(swapInfo.swaps[1].amount, '77743494526568598'); + assert.equal(swapInfo.swaps[1].amount, '77743494526610987'); }).timeout(10000); it('should full swap stable & weighted swapExactOut', async () => { @@ -482,7 +482,7 @@ describe('Tests full swaps against known values', () => { }, }); - assert.equal(swapInfo.returnAmount.toString(), '100601647114105022959'); + assert.equal(swapInfo.returnAmount.toString(), '100600365514359821527'); assert.equal(swapInfo.swaps.length, 3); assert.equal( swapInfo.swaps[0].poolId, @@ -496,7 +496,7 @@ describe('Tests full swaps against known values', () => { swapInfo.tokenAddresses[swapInfo.swaps[0].assetOutIndex], USDC.address ); - assert.equal(swapInfo.swaps[0].amount, '82364889'); + assert.equal(swapInfo.swaps[0].amount, '84819610'); assert.equal( swapInfo.swaps[1].poolId, '0x75286e183d923a5f52f52be205e358c5c9101b09' @@ -509,7 +509,7 @@ describe('Tests full swaps against known values', () => { swapInfo.tokenAddresses[swapInfo.swaps[1].assetOutIndex], USDC.address ); - assert.equal(swapInfo.swaps[1].amount, '16512830'); + assert.equal(swapInfo.swaps[1].amount, '14305989'); assert.equal( swapInfo.swaps[2].poolId, '0x57755f7dec33320bca83159c26e93751bfd30fbe' @@ -522,7 +522,7 @@ describe('Tests full swaps against known values', () => { swapInfo.tokenAddresses[swapInfo.swaps[2].assetOutIndex], USDC.address ); - assert.equal(swapInfo.swaps[2].amount, '1854381'); + assert.equal(swapInfo.swaps[2].amount, '1606501'); }).timeout(10000); it('WBTC>MKR2, swapExactIn', async () => { diff --git a/test/gyro3Pool.spec.ts b/test/gyro3Pool.spec.ts index b095a774..7b325a0c 100644 --- a/test/gyro3Pool.spec.ts +++ b/test/gyro3Pool.spec.ts @@ -56,7 +56,7 @@ describe('Gyro3Pool tests USDC > DAI', () => { pool.getNormalizedLiquidity(poolPairData); expect(normalizedLiquidity.toString()).to.equal( - '9478800.379791954566885539' + '9478800.379870785044596699' ); }); }); diff --git a/test/gyroEPool.spec.ts b/test/gyroEPool.spec.ts index aeea721d..8dd8d864 100644 --- a/test/gyroEPool.spec.ts +++ b/test/gyroEPool.spec.ts @@ -74,7 +74,7 @@ describe('gyroEPool tests', () => { POOL.getNormalizedLiquidity(poolPairData); expect(Number(normalizedLiquidity)).to.be.approximately( - 8521784.492644514672267378, + 8521784.473067058, 0.00001 ); }); diff --git a/test/phantomStableMath.spec.ts b/test/phantomStableMath.spec.ts index 26d603f1..4eee9168 100644 --- a/test/phantomStableMath.spec.ts +++ b/test/phantomStableMath.spec.ts @@ -1,20 +1,22 @@ // TS_NODE_PROJECT='tsconfig.testing.json' npx mocha -r ts-node/register test/phantomStableMath.spec.ts import { assert } from 'chai'; -import { formatFixed } from '@ethersproject/bignumber'; +import { BigNumber, formatFixed, parseFixed } from '@ethersproject/bignumber'; import { BigNumber as OldBigNumber } from '../src/utils/bignumber'; import { bnum } from '../src/utils/bignumber'; +import { WeiPerEther as ONE } from '@ethersproject/constants'; import phantomStableStabal3WithPriceRates from './testData/phantomStablePools/phantomStableStabal3WithPriceRates.json'; import { PhantomStablePool, PhantomStablePoolPairData, } from '../src/pools/phantomStablePool/phantomStablePool'; import * as phantomStableMath from '../src/pools/phantomStablePool/phantomStableMath'; +import * as stableMathBigInt from '../src/pools/stablePool/stableMathBigInt'; import { bbaUSD, LINEAR_AUSDT, LINEAR_AUSDC } from './lib/constants'; +const oldBN_ONE = bnum(ONE.toString()); + describe('phantomStable pools tests', () => { - // For the moment we tolerate a moderate relative error until - // more accurate formulas are developed for phantomStable. - const error = 0.005; + const error = 0.00001; context('phantomStable pools', () => { const phantomStablePool = PhantomStablePool.fromPool( @@ -80,39 +82,85 @@ describe('phantomStable pools tests', () => { bbaUSD.address, LINEAR_AUSDC.address ); - const priceRateOut = bnum( - formatFixed(poolPairData.tokenOutPriceRate, 18) - ); // swap outcomes - const { a1, a2 } = getSwapOutcomes( - phantomStablePool, - poolPairData, - 4000 - ); - const b1 = phantomStableMath - ._exactBPTInForTokenOut(bnum(4000), poolPairData) - .div(priceRateOut); - const b2 = phantomStableMath._BPTInForExactTokenOut( - bnum(4000).times(priceRateOut), + // compares phantomStableMath with stableMathBigInt + const amount = 4000; + const outMath = phantomStableMath._exactBPTInForTokenOut( + bnum(amount), poolPairData ); + const outBigInt = stableMathBigInt._calcTokenOutGivenExactBptIn( + poolPairData.amp.toBigInt(), + poolPairData.allBalancesScaled.map((b) => b.toBigInt()), + poolPairData.tokenIndexOut, + ONE.mul(amount).toBigInt(), + poolPairData.virtualBptSupply.toBigInt(), + poolPairData.swapFee.toBigInt() + ); + const bnumOutBigInt = bnum(outBigInt.toString()).div(oldBN_ONE); assert.approximately( - a1.div(b1).toNumber(), + outMath.div(bnumOutBigInt).toNumber(), 1, error, 'wrong result' ); + + const inMath = phantomStableMath._BPTInForExactTokenOut( + bnum(amount), + poolPairData + ); + const amountsOutBigInt = Array( + poolPairData.allBalancesScaled.length + ).fill(BigInt(0)); + amountsOutBigInt[poolPairData.tokenIndexIn] = parseFixed( + amount.toString(), + 18 + ).toBigInt(); + + const inBigInt = stableMathBigInt._calcBptInGivenExactTokensOut( + poolPairData.amp.toBigInt(), + poolPairData.allBalancesScaled.map((b) => b.toBigInt()), + amountsOutBigInt, + poolPairData.virtualBptSupply.toBigInt(), + poolPairData.swapFee.toBigInt() + ); + const bnumInBigInt = bnum(inBigInt.toString()).div(oldBN_ONE); assert.approximately( - a2.div(b2).toNumber(), + inMath.div(bnumInBigInt).toNumber(), 1, error, 'wrong result' ); // spot prices + poolPairData.swapFee = BigNumber.from(0); + const spPhantom = + phantomStableMath._spotPriceAfterSwapExactBPTInForTokenOut( + bnum(0), + poolPairData + ); + + const balances = poolPairData.allBalancesScaled.map((balance) => + balance.toBigInt() + ); + const spBigInt = + stableMathBigInt._spotPriceAfterSwapExactBPTInForTokenOut( + poolPairData.amp.toBigInt(), + balances, + poolPairData.tokenIndexOut, + phantomStablePool.totalShares.toBigInt(), + BigInt(0) + ); + const bnumSpBigInt = bnum(spBigInt.toString()).div(oldBN_ONE); + assert.approximately( + spPhantom.div(bnumSpBigInt).toNumber(), + 1, + error, + 'wrong result' + ); checkPhantomStableSpotPrices( phantomStablePool, poolPairData, - 4000, + amount, error ); }); @@ -121,6 +169,10 @@ describe('phantomStable pools tests', () => { LINEAR_AUSDC.address, bbaUSD.address ); + poolPairData.swapFee = BigNumber.from(0); + + // here we should compare phantomStableMath with stableMathBigInt + // like in the previous case, BPT -> token. const { a1, a2 } = getSwapOutcomes( phantomStablePool, poolPairData, diff --git a/test/stableMath.spec.ts b/test/stableMath.spec.ts index 0c050983..8fffe181 100644 --- a/test/stableMath.spec.ts +++ b/test/stableMath.spec.ts @@ -30,9 +30,8 @@ describe('stable-math tests', () => { ); const amount = 5000000000000; const amtScaled = scale(bnum(amount), 18); - const amp1000 = bnum(stableBptSwapPool.amp.toString()).times(1000); - - const error = 0.004; + const amp = bnum(stableBptSwapPool.amp.toString()); + const error = 0.00001; context('swap outcomes', () => { it('_exactTokenInForTokenOut', () => { @@ -42,7 +41,7 @@ describe('stable-math tests', () => { DAI.address ); let sdkValue = SDK.StableMath._calcOutGivenIn( - amp1000, + amp, allBalancesScaled, poolPairData.tokenIndexIn, poolPairData.tokenIndexOut, @@ -66,7 +65,7 @@ describe('stable-math tests', () => { DAI.address ); let sdkValue = SDK.StableMath._calcInGivenOut( - amp1000, + amp, allBalancesScaled, poolPairData.tokenIndexIn, poolPairData.tokenIndexOut, diff --git a/test/stablePools.spec.ts b/test/stablePools.spec.ts index d327bfe7..0e0e422d 100644 --- a/test/stablePools.spec.ts +++ b/test/stablePools.spec.ts @@ -473,7 +473,7 @@ describe(`Tests for Stable Pools.`, () => { amp ); - expect(bptAmt.toString()).eq('999912236433875470'); + expect(bptAmt.toString()).eq('999212124447530974'); }); it('should test BPTForTokensZeroPriceImpact for single token add + VERY uneven pool', () => { @@ -490,12 +490,12 @@ describe(`Tests for Stable Pools.`, () => { const bptAmt = BPTForTokensZeroPriceImpact( allBalances, decimals, - amounts, // This has to have the same lenght as allBalances + amounts, // This has to have the same length as allBalances bptTotalSupply, amp ); - expect(bptAmt.toString()).eq('997252048236528013'); + expect(bptAmt.toString()).eq('977051100758936668'); }); it('Derivative Bug Case', () => { @@ -516,12 +516,12 @@ describe(`Tests for Stable Pools.`, () => { const bptAmt = BPTForTokensZeroPriceImpact( allBalances, decimals, - amounts, // This has to have the same lenght as allBalances + amounts, // This has to have the same length as allBalances bptTotalSupply, amp ); - expect(bptAmt.toString()).eq('28957866405645758931354'); + expect(bptAmt.toString()).eq('28957866405645758917829'); }); }); }); diff --git a/test/testData/phantomStablePools/composableStable.json b/test/testData/phantomStablePools/composableStable.json new file mode 100644 index 00000000..0c7cd5d5 --- /dev/null +++ b/test/testData/phantomStablePools/composableStable.json @@ -0,0 +1,54 @@ +{ + "pools": [ + { + "address": "0xa13a9247ea42d743238089903570127dda72fe44", + "amp": "1472", + "baseToken": "2", + "expiryTime": 0, + "id": "0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d", + "poolType": "PhantomStable", + "principalToken": "0", + "swapEnabled": true, + "swapFee": "0.00001", + "tokens": [ + { + "address": "0x2f4eb100552ef93840d5adc30560e5513dfffacb", + "balance": "34995795.166123461408475645", + "decimals": 18, + "priceRate": "1.003418516210349185", + "weight": "0" + }, + { + "address": "0x82698aecc9e28e9bb27608bd52cf57f704bd1b83", + "balance": "20640153.811021435406085515", + "decimals": 18, + "priceRate": "1.000994895137510751", + "weight": "0" + }, + { + "address": "0xa13a9247ea42d743238089903570127dda72fe44", + "balance": "2596148353468885.63048080413930515", + "decimals": 18, + "priceRate": "1", + "weight": "0" + }, + { + "address": "0xae37d54ae477268b9997d4161b96b8200755935c", + "balance": "20221176.302769888135275824", + "decimals": 18, + "priceRate": "1.001281524110011433", + "weight": "0" + } + ], + "tokensList": [ + "0x2f4eb100552ef93840d5adc30560e5513dfffacb", + "0x82698aecc9e28e9bb27608bd52cf57f704bd1b83", + "0xa13a9247ea42d743238089903570127dda72fe44", + "0xae37d54ae477268b9997d4161b96b8200755935c" + ], + "totalShares": "75895556.15221652668337493", + "totalWeight": "0", + "unitSeconds": 0 + } + ] +} diff --git a/test/testScripts/constants.ts b/test/testScripts/constants.ts index c9b75f6d..fdb59f0f 100644 --- a/test/testScripts/constants.ts +++ b/test/testScripts/constants.ts @@ -179,6 +179,11 @@ export const ADDRESSES = { decimals: 18, symbol: 'auraBal', }, + FIAT: { + address: '0x586aa273f262909eef8fa02d90ab65f5015e0516', + decimals: 18, + symbol: 'FIAT', + }, USDT: { address: '0xdac17f958d2ee523a2206206994597c13d831ec7', decimals: 6, diff --git a/test/testScripts/utils.ts b/test/testScripts/utils.ts index ebe84375..97d72baf 100644 --- a/test/testScripts/utils.ts +++ b/test/testScripts/utils.ts @@ -183,6 +183,7 @@ export async function printOutput( BigNumber.from('85000') ); const costToSwapScaled = formatFixed(cost, returnDecimals); + console.log(`Spot price: `, swapInfo.marketSp); console.log(`Swaps:`); console.log(swapInfo.swaps); console.log(swapInfo.tokenAddresses); @@ -194,4 +195,5 @@ export async function printOutput( ); console.log(`Cost to swap: ${costToSwapScaled.toString()}`); console.log(`Return Considering Fees: ${returnWithFeesScaled.toString()}`); + console.log('spot price: ', swapInfo.marketSp); } diff --git a/test/updateTokenBalanceTest.spec.ts b/test/updateTokenBalanceTest.spec.ts index 48e20c20..887bd48b 100644 --- a/test/updateTokenBalanceTest.spec.ts +++ b/test/updateTokenBalanceTest.spec.ts @@ -9,7 +9,7 @@ import boostedPools from './testData/boostedPools/multipleBoosted.json'; import { WETH, BAL } from './lib/constants'; import { bnum } from '../src/utils/bignumber'; -describe('debug fails if token balances are not updated after a swap', () => { +describe('fails if token balances are not updated after a swap', () => { const poolsAll = parseToPoolsDict(cloneDeep(boostedPools.pools), 0); const pool = poolsAll['weightedBalWeth']; const path1 = createPath([WETH.address, BAL.address], [pool]); diff --git a/test/weightedPools.spec.ts b/test/weightedPools.spec.ts index 2e72386d..a0a74245 100644 --- a/test/weightedPools.spec.ts +++ b/test/weightedPools.spec.ts @@ -126,7 +126,7 @@ describe(`Tests for Weighted Pools.`, () => { ); assert.equal(swapInfo1.swaps.length, 3, 'Should have 3 swaps'); assert.equal(swapInfo2.swaps.length, 1, 'Should have 1 swap'); - assert.equal(swapInfo1.returnAmount.toString(), '1264585520968'); + assert.equal(swapInfo1.returnAmount.toString(), '1264796479436'); // only using the stable pool returns a lower value: assert.equal(swapInfo2.returnAmount.toString(), '1264579692512'); }); @@ -157,7 +157,7 @@ describe(`Tests for Weighted Pools.`, () => { assert.equal(swapInfo2.swaps.length, 1, 'Should have 1 swap'); assert.equal( swapInfo1.returnAmount.toString(), - '1280000412490447883455427' + '1279783692099680027157971' ); assert.equal( swapInfo2.returnAmount.toString(), diff --git a/yarn.lock b/yarn.lock index ac406a0f..c6ffc6c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3588,11 +3588,9 @@ json-stringify-safe@~5.0.1: integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= json5@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" - integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== - dependencies: - minimist "^1.2.0" + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonfile@^2.1.0: version "2.4.0"