From bf444776633131c4f5e91935576dbc8c09f8e8cc Mon Sep 17 00:00:00 2001 From: andreiashu Date: Thu, 25 May 2023 14:47:39 +0700 Subject: [PATCH 01/56] WIP fxPool rounding error --- src/pools/xaveFxPool/fxPool.ts | 27 +- src/pools/xaveFxPool/fxPoolMath.ts | 401 +++++++++++++++------------- test/xaveFxPool.integration.spec.ts | 23 +- test/xaveFxPool.spec.ts | 38 +-- 4 files changed, 258 insertions(+), 231 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 53c3aee7..bba432f1 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -185,29 +185,32 @@ export class FxPool implements PoolBase { try { const parsedReserves = poolBalancesToNumeraire(poolPairData); - const alphaValue = Number(formatFixed(poolPairData.alpha, 18)); + const alphaValue = bnum(formatFixed(poolPairData.alpha, 18)); - const maxLimit = (1 + alphaValue) * parsedReserves._oGLiq * 0.5; + const maxLimit = alphaValue + .plus(1) + .times(parsedReserves._oGLiq) + .times(0.5); if (swapType === SwapTypes.SwapExactIn) { - const maxLimitAmount = - maxLimit - parsedReserves.tokenInReservesInNumeraire; + const maxLimitAmount = maxLimit.minus( + parsedReserves.tokenInReservesInNumeraire + ); return bnum( viewRawAmount( maxLimitAmount, - poolPairData.tokenInLatestFXPrice.toNumber() + poolPairData.tokenInLatestFXPrice ).toString() ); } else { - const maxLimitAmount = - maxLimit - parsedReserves.tokenOutReservesInNumeraire; + const maxLimitAmount = maxLimit.minus( + parsedReserves.tokenOutReservesInNumeraire + ); - return bnum( - viewRawAmount( - maxLimitAmount, - poolPairData.tokenOutLatestFXPrice.toNumber() - ).toString() + return viewRawAmount( + maxLimitAmount, + poolPairData.tokenOutLatestFXPrice ); } } catch { diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index db12f14a..f10ee984 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -1,6 +1,6 @@ import { BigNumber as OldBigNumber, bnum, scale } from '../../utils/bignumber'; import { FxPoolPairData } from './fxPool'; -import { formatFixed } from '@ethersproject/bignumber'; +import { BigNumber, formatFixed } from '@ethersproject/bignumber'; // Constants export const CURVEMATH_MAX_DIFF = -0.000001000000000000024; @@ -27,23 +27,23 @@ export enum CurveMathRevert { } interface ParsedFxPoolData { - alpha: number; - beta: number; - delta: number; - epsilon: number; - lambda: number; - baseTokenRate: number; - _oGLiq: number; - _nGLiq: number; - _oBals: number[]; - _nBals: number[]; - givenAmountInNumeraire: number; + alpha: OldBigNumber; + beta: OldBigNumber; + delta: OldBigNumber; + epsilon: OldBigNumber; + lambda: OldBigNumber; + baseTokenRate: OldBigNumber; + _oGLiq: OldBigNumber; + _nGLiq: OldBigNumber; + _oBals: OldBigNumber[]; + _nBals: OldBigNumber[]; + givenAmountInNumeraire: OldBigNumber; } interface ReservesInNumeraire { - tokenInReservesInNumeraire: number; - tokenOutReservesInNumeraire: number; - _oGLiq: number; + tokenInReservesInNumeraire: OldBigNumber; + tokenOutReservesInNumeraire: OldBigNumber; + _oGLiq: OldBigNumber; } const isUSDC = (address: string) => { @@ -60,7 +60,7 @@ const isUSDC = (address: string) => { const calculateGivenAmountInNumeraire = ( isOriginSwap: boolean, poolPairData: FxPoolPairData, - amount: number + amount: OldBigNumber ) => { let calculatedNumeraireAmount; @@ -68,13 +68,13 @@ const calculateGivenAmountInNumeraire = ( // tokenIn is given calculatedNumeraireAmount = viewNumeraireAmount( amount, - poolPairData.tokenInLatestFXPrice.toNumber() + poolPairData.tokenInLatestFXPrice ); } else { // tokenOut is given calculatedNumeraireAmount = viewNumeraireAmount( amount, - poolPairData.tokenOutLatestFXPrice.toNumber() + poolPairData.tokenOutLatestFXPrice ); } @@ -83,16 +83,25 @@ const calculateGivenAmountInNumeraire = ( // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const convertToNumber = ( - amount: number, + amount: OldBigNumber, baseDecimal: number | string -) => { +): OldBigNumber => { if (typeof baseDecimal === 'string') { - return Number(bnum(amount).div(bnum(ONE_ETHER))); + return amount.div(bnum(ONE_ETHER)); } else { - return amount / baseDecimal; + return amount.div(baseDecimal); } }; +/** + * Convert from an Ethersjs BigNumber to a bignumber.js BigNumber + * @param amount BigNumber + * @returns OldBigNumber + */ +const EthersBNToOldBn = (amount: BigNumber): OldBigNumber => { + return bnum(amount.toString()); +}; + export const poolBalancesToNumeraire = ( poolPairData: FxPoolPairData ): ReservesInNumeraire => { @@ -101,31 +110,31 @@ export const poolBalancesToNumeraire = ( if (isUSDC(poolPairData.tokenIn)) { tokenInNumeraire = convertToNumber( viewNumeraireAmount( - Number(poolPairData.balanceIn), - poolPairData.tokenInLatestFXPrice.toNumber() + EthersBNToOldBn(poolPairData.balanceIn), + poolPairData.tokenInLatestFXPrice ), getBaseDecimals(poolPairData.decimalsIn) ); tokenOutNumeraire = convertToNumber( viewNumeraireAmount( - Number(poolPairData.balanceOut), - poolPairData.tokenOutLatestFXPrice.toNumber() + EthersBNToOldBn(poolPairData.balanceOut), + poolPairData.tokenOutLatestFXPrice ), getBaseDecimals(poolPairData.decimalsOut) ); } else { tokenInNumeraire = convertToNumber( viewNumeraireAmount( - Number(poolPairData.balanceOut), - poolPairData.tokenOutLatestFXPrice.toNumber() + EthersBNToOldBn(poolPairData.balanceOut), + poolPairData.tokenOutLatestFXPrice ), getBaseDecimals(poolPairData.decimalsOut) ); tokenOutNumeraire = convertToNumber( viewNumeraireAmount( - Number(poolPairData.balanceIn), - poolPairData.tokenInLatestFXPrice.toNumber() + EthersBNToOldBn(poolPairData.balanceIn), + poolPairData.tokenInLatestFXPrice ), getBaseDecimals(poolPairData.decimalsIn) ); @@ -134,7 +143,7 @@ export const poolBalancesToNumeraire = ( return { tokenInReservesInNumeraire: tokenInNumeraire, tokenOutReservesInNumeraire: tokenOutNumeraire, - _oGLiq: tokenInNumeraire + tokenOutNumeraire, + _oGLiq: tokenInNumeraire.plus(tokenOutNumeraire), }; }; // everything is in order of USDC, base token @@ -147,15 +156,15 @@ const getParsedFxPoolData = ( const baseReserves = isUSDC(poolPairData.tokenIn) ? convertToNumber( viewNumeraireAmount( - Number(poolPairData.balanceOut), - poolPairData.tokenOutLatestFXPrice.toNumber() + EthersBNToOldBn(poolPairData.balanceOut), + poolPairData.tokenOutLatestFXPrice ), getBaseDecimals(poolPairData.decimalsOut) ) : convertToNumber( viewNumeraireAmount( - Number(poolPairData.balanceIn), - poolPairData.tokenInLatestFXPrice.toNumber() + EthersBNToOldBn(poolPairData.balanceIn), + poolPairData.tokenInLatestFXPrice ), getBaseDecimals(poolPairData.decimalsIn) ); @@ -164,49 +173,49 @@ const getParsedFxPoolData = ( const usdcReserves = isUSDC(poolPairData.tokenIn) ? convertToNumber( viewNumeraireAmount( - Number(poolPairData.balanceIn), - poolPairData.tokenInLatestFXPrice.toNumber() + EthersBNToOldBn(poolPairData.balanceIn), + poolPairData.tokenInLatestFXPrice ), getBaseDecimals(poolPairData.decimalsIn) ) : convertToNumber( viewNumeraireAmount( - Number(poolPairData.balanceOut), - poolPairData.tokenOutLatestFXPrice.toNumber() + EthersBNToOldBn(poolPairData.balanceOut), + poolPairData.tokenOutLatestFXPrice ), getBaseDecimals(poolPairData.decimalsOut) ); // rate is converted from chainlink to the actual rate in decimals const baseTokenRate = isUSDC(poolPairData.tokenIn) - ? poolPairData.tokenOutLatestFXPrice.toNumber() - : poolPairData.tokenInLatestFXPrice.toNumber(); + ? poolPairData.tokenOutLatestFXPrice + : poolPairData.tokenInLatestFXPrice; // given amount in or out converted to numeraire const givenAmountInNumeraire = calculateGivenAmountInNumeraire( isOriginSwap, poolPairData, - Number(amount.toString()) + amount ); return { - alpha: Number(formatFixed(poolPairData.alpha, 18)), - beta: Number(formatFixed(poolPairData.beta, 18)), - delta: Number(formatFixed(poolPairData.delta, 18)), - epsilon: Number(formatFixed(poolPairData.epsilon, 18)), - lambda: Number(formatFixed(poolPairData.lambda, 18)), + alpha: bnum(formatFixed(poolPairData.alpha, 18)), + beta: bnum(formatFixed(poolPairData.beta, 18)), + delta: bnum(formatFixed(poolPairData.delta, 18)), + epsilon: bnum(formatFixed(poolPairData.epsilon, 18)), + lambda: bnum(formatFixed(poolPairData.lambda, 18)), baseTokenRate: baseTokenRate, - _oGLiq: baseReserves + usdcReserves, - _nGLiq: baseReserves + usdcReserves, + _oGLiq: baseReserves.plus(usdcReserves), + _nGLiq: baseReserves.plus(usdcReserves), _oBals: [usdcReserves, baseReserves], _nBals: isUSDC(poolPairData.tokenIn) ? [ - usdcReserves + givenAmountInNumeraire, - baseReserves - givenAmountInNumeraire, + usdcReserves.plus(givenAmountInNumeraire), + baseReserves.minus(givenAmountInNumeraire), ] : [ - usdcReserves - givenAmountInNumeraire, - baseReserves + givenAmountInNumeraire, + usdcReserves.minus(givenAmountInNumeraire), + baseReserves.plus(givenAmountInNumeraire), ], givenAmountInNumeraire: givenAmountInNumeraire, @@ -237,55 +246,61 @@ export const getBaseDecimals = (decimals: number) => { // Base Assimilator Functions // calculations are from the BaseToUsdAssimilator -export const viewRawAmount = (_amount: number, rate: number): OldBigNumber => { - return bnum(_amount / rate); +export const viewRawAmount = ( + _amount: OldBigNumber, + rate: OldBigNumber +): OldBigNumber => { + return _amount.div(rate); }; -const viewNumeraireAmount = (_amount: number, rate: number): number => { - return _amount * rate; +const viewNumeraireAmount = ( + _amount: OldBigNumber, + rate: OldBigNumber +): OldBigNumber => { + return _amount.times(rate); }; // Curve Math // calculations are from CurveMath.sol const calculateMicroFee = ( - _bal: number, - _ideal: number, - _beta: number, - _delta: number -): number => { + _bal: OldBigNumber, + _ideal: OldBigNumber, + _beta: OldBigNumber, + _delta: OldBigNumber +): OldBigNumber => { let _threshold, _feeMargin; - let fee_ = 0; + let fee_ = bnum(0); - if (_bal < _ideal) { - _threshold = _ideal * (1 - _beta); // CURVEMATH ONE + if (_bal.lt(_ideal)) { + _threshold = _ideal.times(bnum(1).minus(_beta)); // CURVEMATH ONE - if (_bal < _threshold) { - _feeMargin = _threshold - _bal; - fee_ = _feeMargin / _ideal; - fee_ = fee_ * _delta; + if (_bal.lt(_threshold)) { + _feeMargin = _threshold.minus(_bal); + fee_ = bnum(_feeMargin).div(_ideal); + fee_ = fee_.times(_delta); - if (fee_ > CURVEMATH_MAX) { - fee_ = CURVEMATH_MAX; + if (fee_.gt(CURVEMATH_MAX)) { + fee_ = bnum(CURVEMATH_MAX); } - fee_ = fee_ * _feeMargin; + fee_ = fee_.times(_feeMargin); } else { - fee_ = 0; + fee_ = bnum(0); } } else { - _threshold = _ideal * (1 + _beta); // CURVEMATH_ONE + _threshold = _ideal.times(bnum(1).plus(_beta)); // CURVEMATH_ONE - if (_bal > _threshold) { - _feeMargin = _bal - _threshold; + if (_bal.gt(_threshold)) { + _feeMargin = _bal.minus(_threshold); - fee_ = _feeMargin / _ideal; - fee_ = fee_ * _delta; + fee_ = bnum(_feeMargin).div(_ideal); + fee_ = bnum(fee_).times(_delta); - if (fee_ > CURVEMATH_MAX) fee_ = CURVEMATH_MAX; + if (fee_.gt(CURVEMATH_MAX)) fee_ = bnum(CURVEMATH_MAX); - fee_ = fee_ * _feeMargin; + fee_ = fee_.times(_feeMargin); } else { - fee_ = 0; + fee_ = bnum(0); } } @@ -293,20 +308,20 @@ const calculateMicroFee = ( }; const calculateFee = ( - _gLiq: number, - _bals: number[], - _beta: number, - _delta: number, - _weights: number[] -): number => { + _gLiq: OldBigNumber, + _bals: OldBigNumber[], + _beta: OldBigNumber, + _delta: OldBigNumber, + _weights: OldBigNumber[] +): OldBigNumber => { const _length = _bals.length; - let psi_ = 0; + let psi_ = bnum(0); for (let i = 0; i < _length; i++) { - const _ideal = _gLiq * _weights[i]; + const _ideal = _gLiq.times(_weights[i]); // keep away from wei values like how the contract do it - psi_ = psi_ + calculateMicroFee(_bals[i], _ideal, _beta, _delta); + psi_ = psi_.plus(calculateMicroFee(_bals[i], _ideal, _beta, _delta)); } return psi_; @@ -314,45 +329,45 @@ const calculateFee = ( // return outputAmount and ngliq const calculateTrade = ( - _oGLiq: number, - _nGLiq: number, - _oBals: number[], - _nBals: number[], - _inputAmt: number, + _oGLiq: OldBigNumber, + _nGLiq: OldBigNumber, + _oBals: OldBigNumber[], + _nBals: OldBigNumber[], + _inputAmt: OldBigNumber, _outputIndex: number, poolPairData: ParsedFxPoolData -): [number, number] => { +): [OldBigNumber, OldBigNumber] => { let outputAmt_; - const _weights: number[] = [0.5, 0.5]; // const for now since all weights are 0.5 + const _weights: OldBigNumber[] = [bnum('0.5'), bnum('0.5')]; // const for now since all weights are 0.5 const alpha = poolPairData.alpha; const beta = poolPairData.beta; const delta = poolPairData.delta; const lambda = poolPairData.lambda; - outputAmt_ = -_inputAmt; + outputAmt_ = _inputAmt.times(-1); const _omega = calculateFee(_oGLiq, _oBals, beta, delta, _weights); - let _psi: number; + let _psi: OldBigNumber; for (let i = 0; i < 32; i++) { _psi = calculateFee(_nGLiq, _nBals, beta, delta, _weights); const prevAmount = outputAmt_; - outputAmt_ = - _omega < _psi - ? -(_inputAmt + (_omega - _psi)) - : -(_inputAmt + lambda * (_omega - _psi)); + outputAmt_ = _omega.lt(_psi) + ? _inputAmt.plus(_omega.minus(_psi)).times(-1) + : _inputAmt.plus(lambda.times(_omega.minus(_psi))).times(-1); if ( - outputAmt_ / ONE_TO_THE_THIRTEEN_NUM == - prevAmount / ONE_TO_THE_THIRTEEN_NUM + outputAmt_ + .div(ONE_TO_THE_THIRTEEN_NUM) + .eq(prevAmount.div(ONE_TO_THE_THIRTEEN_NUM)) ) { - _nGLiq = _oGLiq + _inputAmt + outputAmt_; + _nGLiq = _oGLiq.plus(_inputAmt).plus(outputAmt_); - _nBals[_outputIndex] = _oBals[_outputIndex] + outputAmt_; + _nBals[_outputIndex] = _oBals[_outputIndex].plus(outputAmt_); // throws error already, removed if statement enforceSwapInvariant(_oGLiq, _omega, _nGLiq, _psi); @@ -360,9 +375,9 @@ const calculateTrade = ( return [outputAmt_, _nGLiq]; } else { - _nGLiq = _oGLiq + _inputAmt + outputAmt_; + _nGLiq = _oGLiq.plus(_inputAmt).plus(outputAmt_); - _nBals[_outputIndex] = _oBals[_outputIndex] + outputAmt_; + _nBals[_outputIndex] = _oBals[_outputIndex].plus(outputAmt_); } } @@ -371,47 +386,47 @@ const calculateTrade = ( // invariant enforcement const enforceHalts = ( - _oGLiq: number, - _nGLiq: number, - _oBals: number[], - _nBals: number[], - _weights: number[], - alpha: number + _oGLiq: OldBigNumber, + _nGLiq: OldBigNumber, + _oBals: OldBigNumber[], + _nBals: OldBigNumber[], + _weights: OldBigNumber[], + alpha: OldBigNumber ): boolean => { const _length = _nBals.length; const _alpha = alpha; for (let i = 0; i < _length; i++) { - const _nIdeal = _nGLiq * _weights[i]; + const _nIdeal = _nGLiq.times(_weights[i]); - if (_nBals[i] > _nIdeal) { - const _upperAlpha = 1 + _alpha; + if (_nBals[i].gt(_nIdeal)) { + const _upperAlpha = _alpha.plus(1); - const _nHalt = _nIdeal * _upperAlpha; + const _nHalt = _nIdeal.times(_upperAlpha); - if (_nBals[i] > _nHalt) { - const _oHalt = _oGLiq * _weights[i] * _upperAlpha; + if (_nBals[i].gt(_nHalt)) { + const _oHalt = _oGLiq.times(_weights[i]).times(_upperAlpha); - if (_oBals[i] < _oHalt) { + if (_oBals[i].lt(_oHalt)) { throw new Error(CurveMathRevert.UpperHalt); } - if (_nBals[i] - _nHalt > _oBals[i] - _oHalt) { + if (_nBals[i].minus(_nHalt).gt(_oBals[i].minus(_oHalt))) { throw new Error(CurveMathRevert.UpperHalt); } } } else { - const _lowerAlpha = 1 - _alpha; + const _lowerAlpha = bnum(1).minus(_alpha); - const _nHalt = _nIdeal * _lowerAlpha; + const _nHalt = _nIdeal.times(_lowerAlpha); - if (_nBals[i] < _nHalt) { - let _oHalt = _oGLiq * _weights[i]; - _oHalt = _oHalt * _lowerAlpha; + if (_nBals[i].lt(_nHalt)) { + let _oHalt = _oGLiq.times(_weights[i]); + _oHalt = _oHalt.times(_lowerAlpha); - if (_oBals[i] > _oHalt) { + if (_oBals[i].gt(_oHalt)) { throw new Error(CurveMathRevert.LowerHalt); } - if (_nHalt - _nBals[i] > _oHalt - _oBals[i]) { + if (_nHalt.minus(_nBals[i]).gt(_oHalt.minus(_oBals[i]))) { throw new Error(CurveMathRevert.LowerHalt); } } @@ -421,19 +436,19 @@ const enforceHalts = ( }; const enforceSwapInvariant = ( - _oGLiq: number, - _omega: number, - _nGLiq: number, - _psi: number + _oGLiq: OldBigNumber, + _omega: OldBigNumber, + _nGLiq: OldBigNumber, + _psi: OldBigNumber ): boolean => { - const _nextUtil = _nGLiq - _psi; + const _nextUtil = _nGLiq.minus(_psi); - const _prevUtil = _oGLiq - _omega; + const _prevUtil = _oGLiq.minus(_omega); - const _diff = _nextUtil - _prevUtil; + const _diff = _nextUtil.minus(_prevUtil); // from int128 private constant MAX_DIFF = -0x10C6F7A0B5EE converted to plain decimals - if (0 < _diff || _diff >= CURVEMATH_MAX_DIFF) { + if (_diff.gt(0) || _diff.gte(CURVEMATH_MAX_DIFF)) { return true; } else { throw new Error(CurveMathRevert.SwapInvariantViolation); @@ -454,7 +469,7 @@ export function _exactTokenInForTokenOut( if (poolPairData.tokenIn === poolPairData.tokenOut) { return viewRawAmount( targetAmountInNumeraire, - poolPairData.tokenInLatestFXPrice.toNumber() + poolPairData.tokenInLatestFXPrice ); // must be the token out } @@ -477,11 +492,11 @@ export function _exactTokenInForTokenOut( throw new Error(CurveMathRevert.CannotSwap); } else { const epsilon = parsedFxPoolData.epsilon; - const _amtWithFee = _amt[0] * (1 - epsilon); // fee retained by the pool + const _amtWithFee = _amt[0].times(bnum(1).minus(epsilon)); // fee retained by the pool return viewRawAmount( - Math.abs(_amtWithFee), - poolPairData.tokenOutLatestFXPrice.toNumber() + _amtWithFee.abs(), + poolPairData.tokenOutLatestFXPrice ); } } @@ -493,13 +508,14 @@ export function _tokenInForExactTokenOut( ): OldBigNumber { // const amountIn = scale(amount, poolPairData.decimalsOut); const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, false); - const targetAmountInNumeraire = -parsedFxPoolData.givenAmountInNumeraire; + const targetAmountInNumeraire = + parsedFxPoolData.givenAmountInNumeraire.times(-1); if (poolPairData.tokenIn === poolPairData.tokenOut) { viewRawAmount( // poolPairData.tokenOut as TokenSymbol, targetAmountInNumeraire, - poolPairData.tokenOutLatestFXPrice.toNumber() + poolPairData.tokenOutLatestFXPrice ); // must be the token out } @@ -521,13 +537,13 @@ export function _tokenInForExactTokenOut( if (_amt === undefined) { throw new Error(CurveMathRevert.CannotSwap); } else { - const epsilon = Number(formatFixed(poolPairData.epsilon, 18)); + const epsilon = bnum(formatFixed(poolPairData.epsilon, 18)); - const _amtWithFee = _amt[0] * (1 + epsilon); // fee retained by the pool + const _amtWithFee = _amt[0].times(epsilon.plus(1)); // fee retained by the pool return viewRawAmount( - Math.abs(_amtWithFee), - poolPairData.tokenInLatestFXPrice.toNumber() + _amtWithFee.abs(), + poolPairData.tokenInLatestFXPrice ); // must be the token out } } @@ -537,7 +553,7 @@ export const spotPriceBeforeSwap = ( poolPairData: FxPoolPairData ): OldBigNumber => { // input amount 1 XSGD to get the output in USDC - const inputAmountInNumeraire = 1; + const inputAmountInNumeraire = bnum(1); const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, true); const _oGLiq = parsedFxPoolData._oGLiq; @@ -550,17 +566,16 @@ export const spotPriceBeforeSwap = ( _nGLiq, // _nGLiq _oBals, // _oBals _nBals, // _nBals - 1, // input amount + bnum(1), // input amount 0, // always output in USDC parsedFxPoolData ); - return bnum( - ((Math.abs(outputAmountInNumeraire[0]) * - (1 - parsedFxPoolData.epsilon)) / - Math.abs(inputAmountInNumeraire)) * - parsedFxPoolData.baseTokenRate - ); + return outputAmountInNumeraire[0] + .abs() + .times(bnum(1).minus(parsedFxPoolData.epsilon)) + .div(inputAmountInNumeraire.abs()) + .times(parsedFxPoolData.baseTokenRate); }; // spot price after origin swap @@ -592,9 +607,9 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( const outputAmount = outputAfterTrade[0]; - const maxBetaLimit: number = (1 + beta) * 0.5 * _oGLiq; + const maxBetaLimit = beta.plus(1).times(0.5).times(_oGLiq); - const minBetaLimit: number = (1 - beta) * 0.5 * _oGLiq; + const minBetaLimit = bnum(1).minus(beta).times(0.5).times(_oGLiq); if (isUSDC(poolPairData.tokenIn)) { // token[0] to token [1] in originswap @@ -602,20 +617,27 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( const oBals1after = _nBals[1]; - if (oBals1after < minBetaLimit && oBals0after > maxBetaLimit) { + if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { // returns 0 because Math.abs(targetAmountInNumeraire)) * currentRate // used that function with a 0 amount to get a market spot price for the pool // which is used in front end display. + // return amount.isZero() + // ? spotPriceBeforeSwap(amount, poolPairData) + // : bnum( + // (Math.abs(outputAmount * (bnum(1).minus(epsilon))) / + // Math.abs(targetAmountInNumeraire)) * + // currentRate + // ); return amount.isZero() ? spotPriceBeforeSwap(amount, poolPairData) - : bnum( - (Math.abs(outputAmount * (1 - epsilon)) / - Math.abs(targetAmountInNumeraire)) * - currentRate - ); + : outputAmount + .times(bnum(1).minus(epsilon)) + .abs() + .div(targetAmountInNumeraire.abs()) + .times(currentRate); } else { - return bnum(currentRate * (1 - epsilon)); + return currentRate.times(bnum(1).minus(epsilon)); } } else { // if usdc is tokenOut @@ -624,17 +646,17 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( const oBals1after = _nBals[0]; - if (oBals1after < minBetaLimit && oBals0after > maxBetaLimit) { + if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { if (amount.isZero()) return spotPriceBeforeSwap(amount, poolPairData); - const ratioOfOutputAndInput = - Math.abs(outputAmount * (1 - epsilon)) / - Math.abs(targetAmountInNumeraire); - - return bnum(ratioOfOutputAndInput * currentRate); + const ratioOfOutputAndInput = outputAmount + .times(bnum(1).minus(epsilon)) + .abs() + .div(targetAmountInNumeraire.abs()); + return ratioOfOutputAndInput.times(currentRate); } else { - return bnum(currentRate * (1 - epsilon)); + return currentRate.times(bnum(1).minus(epsilon)); } } }; @@ -648,7 +670,8 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( ): OldBigNumber => { const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, false); - const targetAmountInNumeraire = -parsedFxPoolData.givenAmountInNumeraire; + const targetAmountInNumeraire = + parsedFxPoolData.givenAmountInNumeraire.times(-1); const _oGLiq = parsedFxPoolData._oGLiq; const _nBals = parsedFxPoolData._nBals; @@ -672,40 +695,38 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( const outputAmount = outputAfterTrade[0]; - const maxBetaLimit: number = (1 + beta) * 0.5 * _oGLiq; + const maxBetaLimit = beta.plus(1).times(0.5).times(_oGLiq); - const minBetaLimit: number = (1 - beta) * 0.5 * _oGLiq; + const minBetaLimit = bnum(1).minus(beta).times(0.5).times(_oGLiq); if (isUSDC(poolPairData.tokenIn)) { // token[0] to token [1] in originswap const oBals0after = _nBals[0]; const oBals1after = _nBals[1]; - if (oBals1after < minBetaLimit && oBals0after > maxBetaLimit) { - return bnum( - (Math.abs(targetAmountInNumeraire) / - Math.abs(outputAmount * (1 + epsilon))) * - currentRate - ); + if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { + return targetAmountInNumeraire + .abs() + .div(outputAmount.times(epsilon.plus(1)).abs()) + .times(currentRate); } else { - return bnum(currentRate * (1 - epsilon)); + return currentRate.times(bnum(1).minus(epsilon)); } } else { // token[1] to token [0] in originswap const oBals0after = _nBals[0]; const oBals1after = _nBals[1]; - const isBeyondMinBeta = oBals0after < minBetaLimit; - const isBeyondMaxBeta = oBals1after > maxBetaLimit; + const isBeyondMinBeta = oBals0after.lt(minBetaLimit); + const isBeyondMaxBeta = oBals1after.gt(maxBetaLimit); if (isBeyondMinBeta && isBeyondMaxBeta) { - return bnum( - (Math.abs(targetAmountInNumeraire) / - Math.abs(outputAmount * (1 + epsilon))) * - currentRate - ); + return targetAmountInNumeraire + .abs() + .div(outputAmount.times(epsilon.plus(1)).abs()) + .times(currentRate); } else { - return bnum(currentRate * (1 - epsilon)); + return currentRate.times(bnum(1).minus(epsilon)); } } }; diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index 2c092452..0e6874fd 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -1,15 +1,19 @@ // yarn test:only test/xaveFxPool.integration.spec.ts import dotenv from 'dotenv'; import { JsonRpcProvider } from '@ethersproject/providers'; -import { bnum, SOR, SubgraphPoolBase, SwapTypes } from '../src'; +import { bnum, OldBigNumber, SOR, SubgraphPoolBase, SwapTypes } from '../src'; import { ADDRESSES, Network, vaultAddr } from './testScripts/constants'; import { parseFixed } from '@ethersproject/bignumber'; import { expect } from 'chai'; import { Vault__factory } from '@balancer-labs/typechain'; import { AddressZero } from '@ethersproject/constants'; import { setUp } from './testScripts/utils'; -import { scale } from '../src/utils/bignumber'; +const debug = require('debug')('xave'); +const x = bnum('0.000000000000000001'); + +debug('x', x.toString()); +debug('x', (-x).toString()); /* * Testing Notes: * - Add infura api key on .env @@ -121,11 +125,9 @@ describe('xaveFxPool: DAI-USDC integration tests', () => { swapInfo.swapAmount.toString() ); - expect( - bnum(queryResult[1].abs().toString()).toNumber() - ).to.be.closeTo( - bnum(swapInfo.returnAmount.toString()).toNumber(), - scale(bnum(inaccuracyLimit), 18).toNumber() + // this is a correct test + expect(queryResult[1].abs().toString()).to.be.eq( + swapInfo.returnAmount.toString() ); }); @@ -147,11 +149,8 @@ describe('xaveFxPool: DAI-USDC integration tests', () => { funds ); - expect( - bnum(queryResult[0].abs().toString()).toNumber() - ).to.be.closeTo( - bnum(swapInfo.returnAmount.toString()).toNumber(), - scale(bnum(inaccuracyLimit), 6).toNumber() + expect(queryResult[0].abs().toString()).to.be.eq( + swapInfo.returnAmount.toString() ); expect(queryResult[1].abs().toString()).to.eq( swapInfo.swapAmount.toString() diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 7f362925..b160bb57 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { formatFixed, parseFixed, BigNumber } from '@ethersproject/bignumber'; import { bnum, ZERO } from '../src/utils/bignumber'; -import { PoolTypes, SwapTypes } from '../src'; +import { OldBigNumber, PoolTypes, SwapTypes } from '../src'; // Add new PoolType import { FxPool, FxPoolPairData } from '../src/pools/xaveFxPool/fxPool'; import { @@ -80,27 +80,31 @@ describe('Test for fxPools', () => { ); const reservesInNumeraire = poolBalancesToNumeraire(poolPairData); - const alphaValue = Number(formatFixed(poolPairData.alpha, 18)); - const maxLimit = - (1 + alphaValue) * reservesInNumeraire._oGLiq * 0.5; - - const maxLimitAmountForTokenIn = - maxLimit - reservesInNumeraire.tokenInReservesInNumeraire; + const alphaValue = bnum(formatFixed(poolPairData.alpha, 18)); + const maxLimit = alphaValue + .plus(1) + .times(reservesInNumeraire._oGLiq) + .times(0.5); + + const maxLimitAmountForTokenIn = maxLimit.minus( + reservesInNumeraire.tokenInReservesInNumeraire + ); - const maxLimitAmountForTokenOut = - maxLimit - reservesInNumeraire.tokenOutReservesInNumeraire; + const maxLimitAmountForTokenOut = maxLimit.times( + reservesInNumeraire.tokenOutReservesInNumeraire + ); const expectedLimitForTokenIn = bnum( viewRawAmount( maxLimitAmountForTokenIn, - poolPairData.tokenInLatestFXPrice.toNumber() + poolPairData.tokenInLatestFXPrice ).toString() ); const expectedLimitForTokenOut = bnum( viewRawAmount( maxLimitAmountForTokenOut, - poolPairData.tokenOutLatestFXPrice.toNumber() + poolPairData.tokenOutLatestFXPrice ).toString() ); @@ -135,8 +139,8 @@ describe('Test for fxPools', () => { ); expect( - newPool.getNormalizedLiquidity(poolPairData).toNumber() - ).to.equals(1 / ALMOST_ZERO); + newPool.getNormalizedLiquidity(poolPairData).toString() + ).to.equals(bnum(1).div(ALMOST_ZERO).toString()); }); }); @@ -186,8 +190,8 @@ describe('Test for fxPools', () => { ); expect(amountOut.toNumber()).to.be.closeTo( viewRawAmount( - Number(testCase.expectedSwapOutput), - poolPairData.tokenOutLatestFXPrice.toNumber() + bnum(testCase.expectedSwapOutput), + poolPairData.tokenOutLatestFXPrice ).toNumber(), 10000 ); // rounded off @@ -241,8 +245,8 @@ describe('Test for fxPools', () => { expect(amountIn.toNumber()).to.be.closeTo( viewRawAmount( - Number(testCase.expectedSwapOutput), - poolPairData.tokenInLatestFXPrice.toNumber() + bnum(testCase.expectedSwapOutput), + poolPairData.tokenInLatestFXPrice ).toNumber(), 2000000 ); // rounded off, decimal adjustment From 37b6ef3a660a613c6bbef87bb4c7df354f8f6aaf Mon Sep 17 00:00:00 2001 From: andreiashu Date: Fri, 26 May 2023 10:35:09 +0700 Subject: [PATCH 02/56] fixed regression in xaveFxPool.spec.ts --- test/xaveFxPool.spec.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index b160bb57..e32ab409 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { formatFixed, parseFixed, BigNumber } from '@ethersproject/bignumber'; import { bnum, ZERO } from '../src/utils/bignumber'; -import { OldBigNumber, PoolTypes, SwapTypes } from '../src'; +import { PoolTypes, SwapTypes } from '../src'; // Add new PoolType import { FxPool, FxPoolPairData } from '../src/pools/xaveFxPool/fxPool'; import { @@ -12,6 +12,7 @@ import { viewRawAmount, _spotPriceAfterSwapExactTokenInForTokenOut, } from '../src/pools/xaveFxPool/fxPoolMath'; +const debug = require('debug')('xave'); // Add new pool test data in Subgraph Schema format import testPools from './testData/fxPool/fxPool.json'; @@ -90,22 +91,18 @@ describe('Test for fxPools', () => { reservesInNumeraire.tokenInReservesInNumeraire ); - const maxLimitAmountForTokenOut = maxLimit.times( + const maxLimitAmountForTokenOut = maxLimit.minus( reservesInNumeraire.tokenOutReservesInNumeraire ); - const expectedLimitForTokenIn = bnum( - viewRawAmount( - maxLimitAmountForTokenIn, - poolPairData.tokenInLatestFXPrice - ).toString() + const expectedLimitForTokenIn = viewRawAmount( + maxLimitAmountForTokenIn, + poolPairData.tokenInLatestFXPrice ); - const expectedLimitForTokenOut = bnum( - viewRawAmount( - maxLimitAmountForTokenOut, - poolPairData.tokenOutLatestFXPrice - ).toString() + const expectedLimitForTokenOut = viewRawAmount( + maxLimitAmountForTokenOut, + poolPairData.tokenOutLatestFXPrice ); let amount = newPool.getLimitAmountSwap( From e9f50e1cc027dc17474e1ba9b29e6b02393412bd Mon Sep 17 00:00:00 2001 From: andreiashu Date: Mon, 29 May 2023 07:35:03 +0700 Subject: [PATCH 03/56] =?UTF-8?q?=F0=9F=8D=9C=20minor=20amends;=20removed?= =?UTF-8?q?=20obsolete=20bnum=20conversions;=20rearranged=20checks=20to=20?= =?UTF-8?q?match=20the=20contreact=20exactly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 12 ++++++------ test/xaveFxPool.integration.spec.ts | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index f10ee984..8d61c159 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -87,7 +87,7 @@ export const convertToNumber = ( baseDecimal: number | string ): OldBigNumber => { if (typeof baseDecimal === 'string') { - return amount.div(bnum(ONE_ETHER)); + return amount.div(ONE_ETHER); } else { return amount.div(baseDecimal); } @@ -276,7 +276,7 @@ const calculateMicroFee = ( if (_bal.lt(_threshold)) { _feeMargin = _threshold.minus(_bal); - fee_ = bnum(_feeMargin).div(_ideal); + fee_ = _feeMargin.div(_ideal); fee_ = fee_.times(_delta); if (fee_.gt(CURVEMATH_MAX)) { @@ -288,13 +288,13 @@ const calculateMicroFee = ( fee_ = bnum(0); } } else { - _threshold = _ideal.times(bnum(1).plus(_beta)); // CURVEMATH_ONE + _threshold = _ideal.times(_beta.plus(1)); // CURVEMATH_ONE if (_bal.gt(_threshold)) { _feeMargin = _bal.minus(_threshold); - fee_ = bnum(_feeMargin).div(_ideal); - fee_ = bnum(fee_).times(_delta); + fee_ = _feeMargin.div(_ideal); + fee_ = fee_.times(_delta); if (fee_.gt(CURVEMATH_MAX)) fee_ = bnum(CURVEMATH_MAX); @@ -370,8 +370,8 @@ const calculateTrade = ( _nBals[_outputIndex] = _oBals[_outputIndex].plus(outputAmt_); // throws error already, removed if statement - enforceSwapInvariant(_oGLiq, _omega, _nGLiq, _psi); enforceHalts(_oGLiq, _nGLiq, _oBals, _nBals, _weights, alpha); + enforceSwapInvariant(_oGLiq, _omega, _nGLiq, _psi); return [outputAmt_, _nGLiq]; } else { diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index 0e6874fd..3e750c99 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -31,8 +31,6 @@ const rpcUrl = 'http://127.0.0.1:8545'; const provider = new JsonRpcProvider(rpcUrl, networkId); const blocknumber = 16797531; -const inaccuracyLimit = 1e-14; - const vault = Vault__factory.connect(vaultAddr, provider); const SWAP_AMOUNT_IN_NUMERAIRE = '10'; From c1f880b67e23ceca7d4722fe25162b653ff12e08 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 31 May 2023 11:20:46 +0700 Subject: [PATCH 04/56] =?UTF-8?q?=F0=9F=8E=90=20wip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index bba432f1..982e3b8a 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -197,11 +197,9 @@ export class FxPool implements PoolBase { parsedReserves.tokenInReservesInNumeraire ); - return bnum( - viewRawAmount( - maxLimitAmount, - poolPairData.tokenInLatestFXPrice - ).toString() + return viewRawAmount( + maxLimitAmount, + poolPairData.tokenInLatestFXPrice ); } else { const maxLimitAmount = maxLimit.minus( From 1ab80f6f818543ceb700ebb914579fc3f2b3babf Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 31 May 2023 13:43:00 +0700 Subject: [PATCH 05/56] wip; broken branch for now --- package.json | 5 +- src/pools/xaveFxPool/fxPool.ts | 14 ++- src/pools/xaveFxPool/fxPoolMath.ts | 152 ++++++++++++++++------------ src/types.ts | 1 + test/xaveFxPool.integration.spec.ts | 71 +++++++------ test/xaveFxPool.spec.ts | 1 - yarn.lock | 2 +- 7 files changed, 145 insertions(+), 101 deletions(-) diff --git a/package.json b/package.json index 773b912e..8e88c08b 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ "scripts": { "build": "rollup -c", "prepack": "yarn build", + "test-xave": "DEBUG=xave TS_NODE_PROJECT='tsconfig.testing.json' nyc mocha --watch -r ts-node/register test/xaveFxPool.integration.spec.ts --timeout 20000", "test": "TS_NODE_PROJECT='tsconfig.testing.json' nyc mocha -r ts-node/register test/*.spec.ts --timeout 20000", "test:only": "TS_NODE_PROJECT='tsconfig.testing.json' npx mocha -r ts-node/register --timeout 20000", - "coverage": "nyc report --reporter=text-lcov | coveralls", "lint": "eslint ./src ./test --ext .ts --max-warnings 0", - "node": "npx hardhat node --tsconfig tsconfig.testing.json --fork $(grep ALCHEMY_URL .env | cut -d '=' -f2) --fork-block-number 14828550" + "node": "npx hardhat node --tsconfig tsconfig.testing.json --fork $(grep RPC_URL .env | cut -d '=' -f2) --fork-block-number 14828550" }, "husky": { "hooks": { @@ -49,6 +49,7 @@ "@typescript-eslint/parser": "^4.29.2", "bignumber.js": "^9.0.1", "chai": "^4.2.0", + "debug": "^4.3.4", "dotenv": "^8.2.0", "eslint": "^7.32.0", "eslint-plugin-mocha-no-only": "^1.1.1", diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 982e3b8a..0fc1e4f8 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -35,7 +35,9 @@ export type FxPoolPairData = PoolPairBase & { delta: BigNumber; epsilon: BigNumber; tokenInLatestFXPrice: OldBigNumber; + tokenInOracleDecimals: OldBigNumber; tokenOutLatestFXPrice: OldBigNumber; + tokenOutOracleDecimals: OldBigNumber; }; export class FxPool implements PoolBase { @@ -137,6 +139,8 @@ export class FxPool implements PoolBase { if (!tO.token?.latestFXPrice || !tI.token?.latestFXPrice) throw 'FX Pool Missing LatestFxPrice'; + if (!tO.token?.oracleDecimals || !tI.token?.oracleDecimals) + throw 'FX Pool Missing tokenIn or tokenOut oracleDecimals'; const poolPairData: FxPoolPairData = { id: this.id, @@ -154,8 +158,14 @@ export class FxPool implements PoolBase { lambda: this.lambda, delta: this.delta, epsilon: this.epsilon, - tokenInLatestFXPrice: bnum(tI.token.latestFXPrice), // decimals is formatted from subgraph in rate we get from the chainlink oracle - tokenOutLatestFXPrice: bnum(tO.token.latestFXPrice), // decimals is formatted from subgraph in rate we get from the chainlink oracle + tokenInLatestFXPrice: bnum(tI.token.latestFXPrice) + .times(bnum(10).pow(tI.token.oracleDecimals)) + .integerValue(OldBigNumber.ROUND_DOWN), // decimals is formatted from subgraph in rate we get from the chainlink oracle + tokenOutLatestFXPrice: bnum(tO.token.latestFXPrice) + .times(bnum(10).pow(tO.token.oracleDecimals)) + .integerValue(OldBigNumber.ROUND_DOWN), // decimals is formatted from subgraph in rate we get from the chainlink oracle + tokenInOracleDecimals: bnum(tI.token.oracleDecimals), + tokenOutOracleDecimals: bnum(tO.token.oracleDecimals), }; return poolPairData; diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 8d61c159..8193feb2 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -17,6 +17,7 @@ export const ONE_TO_THE_THIRTEEN = BigInt(`${ONE_TO_THE_THIRTEEN_NUM}`); export const ONE_ETHER = scale(bnum('1'), 18); export const ALMOST_ZERO = 0.0000000000000000001; // swapping within beta region has no slippage const CURVEMATH_MAX = 0.25; //CURVEMATH MAX from contract +const debug = require('debug')('xave'); export enum CurveMathRevert { LowerHalt = 'CurveMath/lower-halt', @@ -68,29 +69,32 @@ const calculateGivenAmountInNumeraire = ( // tokenIn is given calculatedNumeraireAmount = viewNumeraireAmount( amount, - poolPairData.tokenInLatestFXPrice + bnum(poolPairData.decimalsIn), + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInOracleDecimals ); } else { // tokenOut is given calculatedNumeraireAmount = viewNumeraireAmount( amount, - poolPairData.tokenOutLatestFXPrice + bnum(poolPairData.decimalsOut), + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutOracleDecimals ); } return calculatedNumeraireAmount; }; -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const convertToNumber = ( - amount: OldBigNumber, - baseDecimal: number | string -): OldBigNumber => { - if (typeof baseDecimal === 'string') { - return amount.div(ONE_ETHER); - } else { - return amount.div(baseDecimal); - } +const BNToBigInt = (val: OldBigNumber): bigint => { + try { + // 1 is ROUND_DOWN + return BigInt(val.integerValue(1).toString()); + } catch (e) {} + + throw new Error( + `this platform does not support BigInt. Value: ${val.toString()}` + ); }; /** @@ -108,35 +112,33 @@ export const poolBalancesToNumeraire = ( let tokenInNumeraire, tokenOutNumeraire; if (isUSDC(poolPairData.tokenIn)) { - tokenInNumeraire = convertToNumber( - viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceIn), - poolPairData.tokenInLatestFXPrice - ), - getBaseDecimals(poolPairData.decimalsIn) + // amount * rate / 10^poolPairData.decimalsIn -> rate: (_rate / 10^oracleDecimals) + // _amount.mul(_rate).div(baseOracleDecimals).divu(baseDecimals); + tokenInNumeraire = viewNumeraireAmount( + EthersBNToOldBn(poolPairData.balanceIn), + bnum(poolPairData.decimalsIn), + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInOracleDecimals ); - tokenOutNumeraire = convertToNumber( - viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceOut), - poolPairData.tokenOutLatestFXPrice - ), - getBaseDecimals(poolPairData.decimalsOut) + tokenOutNumeraire = viewNumeraireAmount( + EthersBNToOldBn(poolPairData.balanceOut), + bnum(poolPairData.decimalsOut), + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutOracleDecimals ); } else { - tokenInNumeraire = convertToNumber( - viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceOut), - poolPairData.tokenOutLatestFXPrice - ), - getBaseDecimals(poolPairData.decimalsOut) + tokenInNumeraire = viewNumeraireAmount( + EthersBNToOldBn(poolPairData.balanceOut), + bnum(poolPairData.decimalsOut), + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutOracleDecimals ); - tokenOutNumeraire = convertToNumber( - viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceIn), - poolPairData.tokenInLatestFXPrice - ), - getBaseDecimals(poolPairData.decimalsIn) + tokenOutNumeraire = viewNumeraireAmount( + EthersBNToOldBn(poolPairData.balanceIn), + bnum(poolPairData.decimalsIn), + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInOracleDecimals ); } @@ -154,36 +156,32 @@ const getParsedFxPoolData = ( ): ParsedFxPoolData => { // reserves are in raw amount, they converted to numeraire const baseReserves = isUSDC(poolPairData.tokenIn) - ? convertToNumber( - viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceOut), - poolPairData.tokenOutLatestFXPrice - ), - getBaseDecimals(poolPairData.decimalsOut) + ? viewNumeraireAmount( + EthersBNToOldBn(poolPairData.balanceOut), + bnum(poolPairData.decimalsOut), + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutOracleDecimals ) - : convertToNumber( - viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceIn), - poolPairData.tokenInLatestFXPrice - ), - getBaseDecimals(poolPairData.decimalsIn) + : viewNumeraireAmount( + EthersBNToOldBn(poolPairData.balanceIn), + bnum(poolPairData.decimalsIn), + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInOracleDecimals ); // reserves are not in wei const usdcReserves = isUSDC(poolPairData.tokenIn) - ? convertToNumber( - viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceIn), - poolPairData.tokenInLatestFXPrice - ), - getBaseDecimals(poolPairData.decimalsIn) + ? viewNumeraireAmount( + EthersBNToOldBn(poolPairData.balanceIn), + bnum(poolPairData.decimalsIn), + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInOracleDecimals ) - : convertToNumber( - viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceOut), - poolPairData.tokenOutLatestFXPrice - ), - getBaseDecimals(poolPairData.decimalsOut) + : viewNumeraireAmount( + EthersBNToOldBn(poolPairData.balanceOut), + bnum(poolPairData.decimalsOut), + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutOracleDecimals ); // rate is converted from chainlink to the actual rate in decimals @@ -250,14 +248,25 @@ export const viewRawAmount = ( _amount: OldBigNumber, rate: OldBigNumber ): OldBigNumber => { - return _amount.div(rate); + // solidity code `_amount.mulu(DECIMALS).mul(1e8).div(_rate)` + // `mulu` rounds down + debug(`amount.div(rate): ${_amount.div(rate).toString()}`); + return _amount.div(rate).integerValue(OldBigNumber.ROUND_DOWN); }; const viewNumeraireAmount = ( _amount: OldBigNumber, - rate: OldBigNumber + tokenDecimals: OldBigNumber, + rate: OldBigNumber, + oracleDecimals: OldBigNumber ): OldBigNumber => { - return _amount.times(rate); + // Solidity: _amount.mul(_rate).div(baseOracleDecimals).divu(baseDecimals); + return _amount + .times(rate) + .div(bnum(10).pow(oracleDecimals)) + .integerValue(OldBigNumber.ROUND_DOWN) + .div(bnum(10).pow(tokenDecimals)); + // 167_922_339.1836 }; // Curve Math @@ -317,6 +326,8 @@ const calculateFee = ( const _length = _bals.length; let psi_ = bnum(0); + debug('calculateFee _bals.length', _bals.length); + for (let i = 0; i < _length; i++) { const _ideal = _gLiq.times(_weights[i]); @@ -346,20 +357,27 @@ const calculateTrade = ( const lambda = poolPairData.lambda; outputAmt_ = _inputAmt.times(-1); + debug(`[calculateTrade] _inputAmt: ${_inputAmt.toString()}`); + debug(`[calculateTrade] outputAmt_: ${outputAmt_.toString()}`); const _omega = calculateFee(_oGLiq, _oBals, beta, delta, _weights); + debug(`[calculateTrade] omega: ${_omega.toString()}`); let _psi: OldBigNumber; for (let i = 0; i < 32; i++) { _psi = calculateFee(_nGLiq, _nBals, beta, delta, _weights); + debug(`[calculateTrade] psi: ${_psi.toString()}`); + const prevAmount = outputAmt_; outputAmt_ = _omega.lt(_psi) ? _inputAmt.plus(_omega.minus(_psi)).times(-1) : _inputAmt.plus(lambda.times(_omega.minus(_psi))).times(-1); + debug(`[calculateTrade] outputAmt_: ${outputAmt_.toString()}`); + if ( outputAmt_ .div(ONE_TO_THE_THIRTEEN_NUM) @@ -367,6 +385,8 @@ const calculateTrade = ( ) { _nGLiq = _oGLiq.plus(_inputAmt).plus(outputAmt_); + debug(`[calculateTrade] 1st if nGLiq: ${_nGLiq.toString()}`); + _nBals[_outputIndex] = _oBals[_outputIndex].plus(outputAmt_); // throws error already, removed if statement @@ -376,6 +396,7 @@ const calculateTrade = ( return [outputAmt_, _nGLiq]; } else { _nGLiq = _oGLiq.plus(_inputAmt).plus(outputAmt_); + debug(`[calculateTrade] 2nd if nGLiq: ${_nGLiq.toString()}`); _nBals[_outputIndex] = _oBals[_outputIndex].plus(outputAmt_); } @@ -462,11 +483,13 @@ export function _exactTokenInForTokenOut( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber { + debug('_exactTokenInForTokenOut', amount.toString()); const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, true); const targetAmountInNumeraire = parsedFxPoolData.givenAmountInNumeraire; if (poolPairData.tokenIn === poolPairData.tokenOut) { + // solidity code `_amount.mulu(DECIMALS).mul(1e8).div(_rate)` return viewRawAmount( targetAmountInNumeraire, poolPairData.tokenInLatestFXPrice @@ -493,6 +516,9 @@ export function _exactTokenInForTokenOut( } else { const epsilon = parsedFxPoolData.epsilon; const _amtWithFee = _amt[0].times(bnum(1).minus(epsilon)); // fee retained by the pool + const testR = bnum('9.98689715').div( + poolPairData.tokenOutLatestFXPrice + ); return viewRawAmount( _amtWithFee.abs(), diff --git a/src/types.ts b/src/types.ts index bc9c62b0..9a73b73a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -135,6 +135,7 @@ export type SubgraphToken = { export type SubgraphTokenData = { latestFXPrice?: string; + oracleDecimals?: number; }; export interface SwapV2 { diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index 3e750c99..4e856402 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -1,19 +1,19 @@ // yarn test:only test/xaveFxPool.integration.spec.ts import dotenv from 'dotenv'; import { JsonRpcProvider } from '@ethersproject/providers'; -import { bnum, OldBigNumber, SOR, SubgraphPoolBase, SwapTypes } from '../src'; +import { SOR, SubgraphPoolBase, SwapTypes } from '../src'; import { ADDRESSES, Network, vaultAddr } from './testScripts/constants'; import { parseFixed } from '@ethersproject/bignumber'; import { expect } from 'chai'; import { Vault__factory } from '@balancer-labs/typechain'; import { AddressZero } from '@ethersproject/constants'; import { setUp } from './testScripts/utils'; + const debug = require('debug')('xave'); -const x = bnum('0.000000000000000001'); +// 361.628895632722 - Solidity +// 361.62889581632257725 -debug('x', x.toString()); -debug('x', (-x).toString()); /* * Testing Notes: * - Add infura api key on .env @@ -26,7 +26,7 @@ dotenv.config(); let sor: SOR; const networkId = Network.MAINNET; -const jsonRpcUrl = 'https://mainnet.infura.io/v3/' + process.env.INFURA; +const jsonRpcUrl = process.env.RPC_URL; const rpcUrl = 'http://127.0.0.1:8545'; const provider = new JsonRpcProvider(rpcUrl, networkId); const blocknumber = 16797531; @@ -55,16 +55,19 @@ const xaveFxPoolDAI_USDC_MAINNET: SubgraphPoolBase = { weight: null, token: { latestFXPrice: '0.99980000', // roundId 92233720368547774306 + oracleDecimals: 8, }, }, { address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - balance: '167.890447', + // 167890440 + balance: '167.890440', decimals: 6, priceRate: '1', weight: null, token: { latestFXPrice: '1.00019000', // roundId 36893488147419104088 + oracleDecimals: 8, }, }, ], @@ -112,6 +115,11 @@ describe('xaveFxPool: DAI-USDC integration tests', () => { swapAmount ); + debug('swapType: ', swapType); + debug('swaps: ', swapInfo.swaps); + debug('swapInfo.tokenAddresses: ', swapInfo.tokenAddresses); + debug('funds: ', funds); + const queryResult = await vault.callStatic.queryBatchSwap( swapType, swapInfo.swaps, @@ -123,36 +131,35 @@ describe('xaveFxPool: DAI-USDC integration tests', () => { swapInfo.swapAmount.toString() ); - // this is a correct test expect(queryResult[1].abs().toString()).to.be.eq( swapInfo.returnAmount.toString() ); }); - it('ExactOut', async () => { - const swapType = SwapTypes.SwapExactOut; - // swapAmount is tokenOut, expect tokenIn - const swapAmount = parseFixed(SWAP_AMOUNT_IN_NUMERAIRE, 18); - const swapInfo = await sor.getSwaps( - tokenIn, - tokenOut, - swapType, - swapAmount - ); - - const queryResult = await vault.callStatic.queryBatchSwap( - swapType, - swapInfo.swaps, - swapInfo.tokenAddresses, - funds - ); - - expect(queryResult[0].abs().toString()).to.be.eq( - swapInfo.returnAmount.toString() - ); - expect(queryResult[1].abs().toString()).to.eq( - swapInfo.swapAmount.toString() - ); - }); + // it('ExactOut', async () => { + // const swapType = SwapTypes.SwapExactOut; + // // swapAmount is tokenOut, expect tokenIn + // const swapAmount = parseFixed(SWAP_AMOUNT_IN_NUMERAIRE, 18); + // const swapInfo = await sor.getSwaps( + // tokenIn, + // tokenOut, + // swapType, + // swapAmount + // ); + + // const queryResult = await vault.callStatic.queryBatchSwap( + // swapType, + // swapInfo.swaps, + // swapInfo.tokenAddresses, + // funds + // ); + + // expect(queryResult[0].abs().toString()).to.be.eq( + // swapInfo.returnAmount.toString() + // ); + // expect(queryResult[1].abs().toString()).to.eq( + // swapInfo.swapAmount.toString() + // ); + // }); }); }); diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index e32ab409..605d478d 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -12,7 +12,6 @@ import { viewRawAmount, _spotPriceAfterSwapExactTokenInForTokenOut, } from '../src/pools/xaveFxPool/fxPoolMath'; -const debug = require('debug')('xave'); // Add new pool test data in Subgraph Schema format import testPools from './testData/fxPool/fxPool.json'; diff --git a/yarn.lock b/yarn.lock index d910a209..e3fa7ed8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2163,7 +2163,7 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" -debug@4, debug@^4.3.3: +debug@4, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== From a44635b7688272b69d82448cacdabaee58c28b56 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Fri, 2 Jun 2023 15:13:05 +0700 Subject: [PATCH 06/56] =?UTF-8?q?=E2=9B=BD=20fixed=2010=20wei=20precision?= =?UTF-8?q?=20issue;=20still=20wip;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/index.ts | 1 + src/pools/xaveFxPool/big-number.ts | 26 +++++++ src/pools/xaveFxPool/fxPool.ts | 81 +++++++++++++------- src/pools/xaveFxPool/fxPoolMath.ts | 112 +++++++++++++--------------- src/types.ts | 2 +- test/xaveFxPool.integration.spec.ts | 69 ++++++++--------- test/xaveFxPool.spec.ts | 53 +++++++------ tsconfig.json | 4 +- 8 files changed, 198 insertions(+), 150 deletions(-) create mode 100644 src/pools/xaveFxPool/big-number.ts diff --git a/src/pools/index.ts b/src/pools/index.ts index 6315385b..7e83d1f2 100644 --- a/src/pools/index.ts +++ b/src/pools/index.ts @@ -85,6 +85,7 @@ export function parseNewPool( } } catch (err) { console.error(`parseNewPool: ${err.message}`); + throw err; return undefined; } return newPool; diff --git a/src/pools/xaveFxPool/big-number.ts b/src/pools/xaveFxPool/big-number.ts new file mode 100644 index 00000000..d48cccbd --- /dev/null +++ b/src/pools/xaveFxPool/big-number.ts @@ -0,0 +1,26 @@ +import { BigNumber } from 'bignumber.js'; + +// needed in order for the curve params (epsilon, alpha etc) to +// have enough precision to as the ABDK 64.64 fixed point library +// in the smart contract +BigNumber.config({ + EXPONENTIAL_AT: [-100, 100], + ROUNDING_MODE: 1, + DECIMAL_PLACES: 36, +}); + +export const ZERO = bnum(0); +export const ONE = bnum(1); +export const INFINITY = bnum('Infinity'); + +export function scale(input: BigNumber, decimalPlaces: number): BigNumber { + const scalePow = new BigNumber(decimalPlaces.toString()); + const scaleMul = new BigNumber(10).pow(scalePow); + return input.times(scaleMul); +} + +export function bnum(val: string | number | BigNumber): BigNumber { + return new BigNumber(val.toString()); +} + +export { BigNumber }; diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 0fc1e4f8..df1e5604 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -1,7 +1,7 @@ import { getAddress } from '@ethersproject/address'; import { BigNumber, formatFixed, parseFixed } from '@ethersproject/bignumber'; import { Zero } from '@ethersproject/constants'; -import { BigNumber as OldBigNumber, ZERO, bnum } from '../../utils/bignumber'; +import { BigNumber as OldBigNumber, ZERO, bnum, scale } from './big-number'; import { isSameAddress } from '../../utils'; import { universalNormalizedLiquidity } from '../liquidity'; import { @@ -21,6 +21,7 @@ import { _spotPriceAfterSwapExactTokenInForTokenOut, _spotPriceAfterSwapTokenInForExactTokenOut, _tokenInForExactTokenOut, + ONE_ETHER, } from './fxPoolMath'; type FxPoolToken = Pick< @@ -28,16 +29,24 @@ type FxPoolToken = Pick< 'address' | 'balance' | 'decimals' | 'token' >; +// replicates ` (_lambda + 1).divu(1e18)` operation from the smart contract +// ( 10000n << 64n ) / (10n**18n) * 10n**18n >> 64n == 9999n +const parseFixedCurveParam = (param: string): OldBigNumber => { + // `alpha * 1e18 + 1) + const param64 = (((BigInt(parseFixed(param, 18).toString()) + 1n) << 64n) / 10n ** 18n) * 10n ** 36n >> 64n; + return bnum(param64.toString()).div(bnum(10).pow(18)); +} + export type FxPoolPairData = PoolPairBase & { - alpha: BigNumber; - beta: BigNumber; - lambda: BigNumber; - delta: BigNumber; - epsilon: BigNumber; + alpha: OldBigNumber; + beta: OldBigNumber; + lambda: OldBigNumber; + delta: OldBigNumber; + epsilon: OldBigNumber; tokenInLatestFXPrice: OldBigNumber; - tokenInOracleDecimals: OldBigNumber; + tokenInfxOracleDecimals: OldBigNumber; tokenOutLatestFXPrice: OldBigNumber; - tokenOutOracleDecimals: OldBigNumber; + tokenOutfxOracleDecimals: OldBigNumber; }; export class FxPool implements PoolBase { @@ -48,11 +57,11 @@ export class FxPool implements PoolBase { totalShares: BigNumber; tokens: FxPoolToken[]; tokensList: string[]; - alpha: BigNumber; - beta: BigNumber; - lambda: BigNumber; - delta: BigNumber; - epsilon: BigNumber; + alpha: OldBigNumber; + beta: OldBigNumber; + lambda: OldBigNumber; + delta: OldBigNumber; + epsilon: OldBigNumber; static fromPool(pool: SubgraphPoolBase): FxPool { if ( @@ -91,17 +100,27 @@ export class FxPool implements PoolBase { delta: string, epsilon: string ) { + /** + 64.64 fixed point value -> decimal (including precision error) + alpha 14757395258967641311 -> 0.800000000000000000987 + beta 7747632510958011697 -> 0.420000000000000000991 + delta 5534023222112865502 -> 0.30000000000000000093 + epsilon 27670116110564345 -> 0.001500000000000000953 + lambda 5534023222112865503 -> 0.300000000000000000987 + */ + this.id = id; this.address = address; this.swapFee = parseFixed(swapFee, 18); this.totalShares = parseFixed(totalShares, 18); this.tokens = tokens; this.tokensList = tokensList; - this.alpha = parseFixed(alpha, 18); - this.beta = parseFixed(beta, 18); - this.lambda = parseFixed(lambda, 18); - this.delta = parseFixed(delta, 18); - this.epsilon = parseFixed(epsilon, 18); + this.alpha = parseFixedCurveParam(alpha).decimalPlaces(3, OldBigNumber.ROUND_UP); + this.beta = parseFixedCurveParam(beta).decimalPlaces(3, OldBigNumber.ROUND_UP); + this.lambda = parseFixedCurveParam(lambda).decimalPlaces(3, OldBigNumber.ROUND_UP); + this.delta = parseFixedCurveParam(delta).decimalPlaces(3, OldBigNumber.ROUND_UP); + + this.epsilon = parseFixedCurveParam(epsilon).decimalPlaces(3, OldBigNumber.ROUND_UP); } updateTotalShares: (newTotalShares: BigNumber) => void; mainIndex?: number | undefined; @@ -139,8 +158,8 @@ export class FxPool implements PoolBase { if (!tO.token?.latestFXPrice || !tI.token?.latestFXPrice) throw 'FX Pool Missing LatestFxPrice'; - if (!tO.token?.oracleDecimals || !tI.token?.oracleDecimals) - throw 'FX Pool Missing tokenIn or tokenOut oracleDecimals'; + if (!tO.token?.fxOracleDecimals || !tI.token?.fxOracleDecimals) + throw 'FX Pool Missing tokenIn or tokenOut fxOracleDecimals'; const poolPairData: FxPoolPairData = { id: this.id, @@ -159,13 +178,13 @@ export class FxPool implements PoolBase { delta: this.delta, epsilon: this.epsilon, tokenInLatestFXPrice: bnum(tI.token.latestFXPrice) - .times(bnum(10).pow(tI.token.oracleDecimals)) + .times(bnum(10).pow(tI.token.fxOracleDecimals)) .integerValue(OldBigNumber.ROUND_DOWN), // decimals is formatted from subgraph in rate we get from the chainlink oracle tokenOutLatestFXPrice: bnum(tO.token.latestFXPrice) - .times(bnum(10).pow(tO.token.oracleDecimals)) + .times(bnum(10).pow(tO.token.fxOracleDecimals)) .integerValue(OldBigNumber.ROUND_DOWN), // decimals is formatted from subgraph in rate we get from the chainlink oracle - tokenInOracleDecimals: bnum(tI.token.oracleDecimals), - tokenOutOracleDecimals: bnum(tO.token.oracleDecimals), + tokenInfxOracleDecimals: bnum(tI.token.fxOracleDecimals), + tokenOutfxOracleDecimals: bnum(tO.token.fxOracleDecimals), }; return poolPairData; @@ -195,7 +214,7 @@ export class FxPool implements PoolBase { try { const parsedReserves = poolBalancesToNumeraire(poolPairData); - const alphaValue = bnum(formatFixed(poolPairData.alpha, 18)); + const alphaValue = poolPairData.alpha.div(bnum(10).pow(18)); const maxLimit = alphaValue .plus(1) @@ -209,8 +228,10 @@ export class FxPool implements PoolBase { return viewRawAmount( maxLimitAmount, - poolPairData.tokenInLatestFXPrice - ); + bnum(poolPairData.decimalsIn), + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInfxOracleDecimals + ).div(bnum(10).pow(poolPairData.decimalsIn)); } else { const maxLimitAmount = maxLimit.minus( parsedReserves.tokenOutReservesInNumeraire @@ -218,8 +239,10 @@ export class FxPool implements PoolBase { return viewRawAmount( maxLimitAmount, - poolPairData.tokenOutLatestFXPrice - ); + bnum(poolPairData.decimalsOut), + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutfxOracleDecimals + ).div(bnum(10).pow(poolPairData.decimalsOut)); } } catch { return ZERO; diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 8193feb2..7a9d1dfa 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -17,7 +17,6 @@ export const ONE_TO_THE_THIRTEEN = BigInt(`${ONE_TO_THE_THIRTEEN_NUM}`); export const ONE_ETHER = scale(bnum('1'), 18); export const ALMOST_ZERO = 0.0000000000000000001; // swapping within beta region has no slippage const CURVEMATH_MAX = 0.25; //CURVEMATH MAX from contract -const debug = require('debug')('xave'); export enum CurveMathRevert { LowerHalt = 'CurveMath/lower-halt', @@ -68,18 +67,18 @@ const calculateGivenAmountInNumeraire = ( if (isOriginSwap) { // tokenIn is given calculatedNumeraireAmount = viewNumeraireAmount( - amount, + amount.times(bnum(10).pow(poolPairData.decimalsIn)), bnum(poolPairData.decimalsIn), poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInOracleDecimals + poolPairData.tokenInfxOracleDecimals ); } else { // tokenOut is given calculatedNumeraireAmount = viewNumeraireAmount( - amount, + amount.times(bnum(10).pow(poolPairData.decimalsOut)), bnum(poolPairData.decimalsOut), poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutOracleDecimals + poolPairData.tokenOutfxOracleDecimals ); } @@ -112,33 +111,33 @@ export const poolBalancesToNumeraire = ( let tokenInNumeraire, tokenOutNumeraire; if (isUSDC(poolPairData.tokenIn)) { - // amount * rate / 10^poolPairData.decimalsIn -> rate: (_rate / 10^oracleDecimals) - // _amount.mul(_rate).div(baseOracleDecimals).divu(baseDecimals); + // amount * rate / 10^poolPairData.decimalsIn -> rate: (_rate / 10^fxOracleDecimals) + // _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); tokenInNumeraire = viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceIn), bnum(poolPairData.decimalsIn), poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInOracleDecimals + poolPairData.tokenInfxOracleDecimals ); tokenOutNumeraire = viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceOut), bnum(poolPairData.decimalsOut), poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutOracleDecimals - ); + poolPairData.tokenOutfxOracleDecimals + ).decimalPlaces(15, OldBigNumber.ROUND_DOWN); } else { tokenInNumeraire = viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceOut), bnum(poolPairData.decimalsOut), poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutOracleDecimals + poolPairData.tokenOutfxOracleDecimals ); tokenOutNumeraire = viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceIn), bnum(poolPairData.decimalsIn), poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInOracleDecimals + poolPairData.tokenInfxOracleDecimals ); } @@ -160,13 +159,13 @@ const getParsedFxPoolData = ( EthersBNToOldBn(poolPairData.balanceOut), bnum(poolPairData.decimalsOut), poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutOracleDecimals + poolPairData.tokenOutfxOracleDecimals ) : viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceIn), bnum(poolPairData.decimalsIn), poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInOracleDecimals + poolPairData.tokenInfxOracleDecimals ); // reserves are not in wei @@ -175,13 +174,13 @@ const getParsedFxPoolData = ( EthersBNToOldBn(poolPairData.balanceIn), bnum(poolPairData.decimalsIn), poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInOracleDecimals + poolPairData.tokenInfxOracleDecimals ) : viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceOut), bnum(poolPairData.decimalsOut), poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutOracleDecimals + poolPairData.tokenOutfxOracleDecimals ); // rate is converted from chainlink to the actual rate in decimals @@ -197,11 +196,11 @@ const getParsedFxPoolData = ( ); return { - alpha: bnum(formatFixed(poolPairData.alpha, 18)), - beta: bnum(formatFixed(poolPairData.beta, 18)), - delta: bnum(formatFixed(poolPairData.delta, 18)), - epsilon: bnum(formatFixed(poolPairData.epsilon, 18)), - lambda: bnum(formatFixed(poolPairData.lambda, 18)), + alpha: poolPairData.alpha.div(bnum(10).pow(18)), + beta: poolPairData.beta.div(bnum(10).pow(18)), + delta: poolPairData.delta.div(bnum(10).pow(18)), + epsilon: poolPairData.epsilon.div(bnum(10).pow(18)), + lambda: poolPairData.lambda.div(bnum(10).pow(18)), baseTokenRate: baseTokenRate, _oGLiq: baseReserves.plus(usdcReserves), _nGLiq: baseReserves.plus(usdcReserves), @@ -246,24 +245,29 @@ export const getBaseDecimals = (decimals: number) => { // calculations are from the BaseToUsdAssimilator export const viewRawAmount = ( _amount: OldBigNumber, - rate: OldBigNumber + tokenDecimals: OldBigNumber, + rate: OldBigNumber, + fxOracleDecimals: OldBigNumber ): OldBigNumber => { - // solidity code `_amount.mulu(DECIMALS).mul(1e8).div(_rate)` - // `mulu` rounds down - debug(`amount.div(rate): ${_amount.div(rate).toString()}`); - return _amount.div(rate).integerValue(OldBigNumber.ROUND_DOWN); + // solidity code `_amount.mulu(baseDecimals).mul(baseOracleDecimals).div(_rate); + + return _amount + .times(bnum(10).pow(tokenDecimals)) + .integerValue(OldBigNumber.ROUND_DOWN) // `mulu` rounds down + .times(bnum(10).pow(fxOracleDecimals)) + .div(rate).integerValue(OldBigNumber.ROUND_DOWN); }; const viewNumeraireAmount = ( _amount: OldBigNumber, tokenDecimals: OldBigNumber, rate: OldBigNumber, - oracleDecimals: OldBigNumber + fxOracleDecimals: OldBigNumber ): OldBigNumber => { - // Solidity: _amount.mul(_rate).div(baseOracleDecimals).divu(baseDecimals); + // Solidity: _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); return _amount .times(rate) - .div(bnum(10).pow(oracleDecimals)) + .div(bnum(10).pow(fxOracleDecimals)) .integerValue(OldBigNumber.ROUND_DOWN) .div(bnum(10).pow(tokenDecimals)); // 167_922_339.1836 @@ -326,8 +330,6 @@ const calculateFee = ( const _length = _bals.length; let psi_ = bnum(0); - debug('calculateFee _bals.length', _bals.length); - for (let i = 0; i < _length; i++) { const _ideal = _gLiq.times(_weights[i]); @@ -357,27 +359,20 @@ const calculateTrade = ( const lambda = poolPairData.lambda; outputAmt_ = _inputAmt.times(-1); - debug(`[calculateTrade] _inputAmt: ${_inputAmt.toString()}`); - debug(`[calculateTrade] outputAmt_: ${outputAmt_.toString()}`); const _omega = calculateFee(_oGLiq, _oBals, beta, delta, _weights); - debug(`[calculateTrade] omega: ${_omega.toString()}`); let _psi: OldBigNumber; for (let i = 0; i < 32; i++) { _psi = calculateFee(_nGLiq, _nBals, beta, delta, _weights); - debug(`[calculateTrade] psi: ${_psi.toString()}`); - const prevAmount = outputAmt_; outputAmt_ = _omega.lt(_psi) ? _inputAmt.plus(_omega.minus(_psi)).times(-1) : _inputAmt.plus(lambda.times(_omega.minus(_psi))).times(-1); - debug(`[calculateTrade] outputAmt_: ${outputAmt_.toString()}`); - if ( outputAmt_ .div(ONE_TO_THE_THIRTEEN_NUM) @@ -385,19 +380,13 @@ const calculateTrade = ( ) { _nGLiq = _oGLiq.plus(_inputAmt).plus(outputAmt_); - debug(`[calculateTrade] 1st if nGLiq: ${_nGLiq.toString()}`); - _nBals[_outputIndex] = _oBals[_outputIndex].plus(outputAmt_); - // throws error already, removed if statement enforceHalts(_oGLiq, _nGLiq, _oBals, _nBals, _weights, alpha); enforceSwapInvariant(_oGLiq, _omega, _nGLiq, _psi); - return [outputAmt_, _nGLiq]; } else { _nGLiq = _oGLiq.plus(_inputAmt).plus(outputAmt_); - debug(`[calculateTrade] 2nd if nGLiq: ${_nGLiq.toString()}`); - _nBals[_outputIndex] = _oBals[_outputIndex].plus(outputAmt_); } } @@ -483,17 +472,17 @@ export function _exactTokenInForTokenOut( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber { - debug('_exactTokenInForTokenOut', amount.toString()); const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, true); const targetAmountInNumeraire = parsedFxPoolData.givenAmountInNumeraire; if (poolPairData.tokenIn === poolPairData.tokenOut) { - // solidity code `_amount.mulu(DECIMALS).mul(1e8).div(_rate)` return viewRawAmount( targetAmountInNumeraire, - poolPairData.tokenInLatestFXPrice - ); // must be the token out + bnum(poolPairData.decimalsIn), + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInfxOracleDecimals + ).div(bnum(10).pow(poolPairData.decimalsIn)); // must be the token out } const _oGLiq = parsedFxPoolData._oGLiq; @@ -515,15 +504,14 @@ export function _exactTokenInForTokenOut( throw new Error(CurveMathRevert.CannotSwap); } else { const epsilon = parsedFxPoolData.epsilon; - const _amtWithFee = _amt[0].times(bnum(1).minus(epsilon)); // fee retained by the pool - const testR = bnum('9.98689715').div( - poolPairData.tokenOutLatestFXPrice - ); - + const _amtWithFee = _amt[0].times(bnum(1).minus(epsilon)); // fee retained by the pool // @TODO this results in a 1 wei less in solidity + // -10.0019 * (1-0.0015) return viewRawAmount( _amtWithFee.abs(), - poolPairData.tokenOutLatestFXPrice - ); + bnum(poolPairData.decimalsOut), + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutfxOracleDecimals + ).div(bnum(10).pow(poolPairData.decimalsOut)); } } @@ -541,8 +529,10 @@ export function _tokenInForExactTokenOut( viewRawAmount( // poolPairData.tokenOut as TokenSymbol, targetAmountInNumeraire, - poolPairData.tokenOutLatestFXPrice - ); // must be the token out + bnum(poolPairData.decimalsOut), + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutfxOracleDecimals + ).div(bnum(10).pow(poolPairData.decimalsOut)); // must be the token out } const _oGLiq = parsedFxPoolData._oGLiq; @@ -563,14 +553,16 @@ export function _tokenInForExactTokenOut( if (_amt === undefined) { throw new Error(CurveMathRevert.CannotSwap); } else { - const epsilon = bnum(formatFixed(poolPairData.epsilon, 18)); + const epsilon = poolPairData.epsilon.div(bnum(10).pow(18)); const _amtWithFee = _amt[0].times(epsilon.plus(1)); // fee retained by the pool return viewRawAmount( _amtWithFee.abs(), - poolPairData.tokenInLatestFXPrice - ); // must be the token out + bnum(poolPairData.decimalsIn), + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInfxOracleDecimals + ).div(bnum(10).pow(poolPairData.decimalsIn)); // must be the token out } } diff --git a/src/types.ts b/src/types.ts index 9a73b73a..6af0d070 100644 --- a/src/types.ts +++ b/src/types.ts @@ -135,7 +135,7 @@ export type SubgraphToken = { export type SubgraphTokenData = { latestFXPrice?: string; - oracleDecimals?: number; + fxOracleDecimals?: number; }; export interface SwapV2 { diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index 4e856402..b890d9a5 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -9,11 +9,6 @@ import { Vault__factory } from '@balancer-labs/typechain'; import { AddressZero } from '@ethersproject/constants'; import { setUp } from './testScripts/utils'; -const debug = require('debug')('xave'); - -// 361.628895632722 - Solidity -// 361.62889581632257725 - /* * Testing Notes: * - Add infura api key on .env @@ -55,7 +50,7 @@ const xaveFxPoolDAI_USDC_MAINNET: SubgraphPoolBase = { weight: null, token: { latestFXPrice: '0.99980000', // roundId 92233720368547774306 - oracleDecimals: 8, + fxOracleDecimals: 8, }, }, { @@ -67,7 +62,7 @@ const xaveFxPoolDAI_USDC_MAINNET: SubgraphPoolBase = { weight: null, token: { latestFXPrice: '1.00019000', // roundId 36893488147419104088 - oracleDecimals: 8, + fxOracleDecimals: 8, }, }, ], @@ -75,6 +70,11 @@ const xaveFxPoolDAI_USDC_MAINNET: SubgraphPoolBase = { beta: '0.42', lambda: '0.3', delta: '0.3', + // precise value is `0.001500000000000000953` + // but right now we have parseFixed(epsilon, 18) across our code + // so had to trim the value to 18 decimals + // but the correct code should be parseFixed(epsilon, 21) + // epsilon: '0.001500000000000001', epsilon: '0.0015', }; @@ -115,11 +115,6 @@ describe('xaveFxPool: DAI-USDC integration tests', () => { swapAmount ); - debug('swapType: ', swapType); - debug('swaps: ', swapInfo.swaps); - debug('swapInfo.tokenAddresses: ', swapInfo.tokenAddresses); - debug('funds: ', funds); - const queryResult = await vault.callStatic.queryBatchSwap( swapType, swapInfo.swaps, @@ -136,30 +131,30 @@ describe('xaveFxPool: DAI-USDC integration tests', () => { ); }); - // it('ExactOut', async () => { - // const swapType = SwapTypes.SwapExactOut; - // // swapAmount is tokenOut, expect tokenIn - // const swapAmount = parseFixed(SWAP_AMOUNT_IN_NUMERAIRE, 18); - // const swapInfo = await sor.getSwaps( - // tokenIn, - // tokenOut, - // swapType, - // swapAmount - // ); - - // const queryResult = await vault.callStatic.queryBatchSwap( - // swapType, - // swapInfo.swaps, - // swapInfo.tokenAddresses, - // funds - // ); - - // expect(queryResult[0].abs().toString()).to.be.eq( - // swapInfo.returnAmount.toString() - // ); - // expect(queryResult[1].abs().toString()).to.eq( - // swapInfo.swapAmount.toString() - // ); - // }); + it('ExactOut', async () => { + const swapType = SwapTypes.SwapExactOut; + // swapAmount is tokenOut, expect tokenIn + const swapAmount = parseFixed(SWAP_AMOUNT_IN_NUMERAIRE, 18); + const swapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmount + ); + + const queryResult = await vault.callStatic.queryBatchSwap( + swapType, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + + expect(queryResult[0].abs().toString()).to.be.eq( + swapInfo.returnAmount.toString() + ); + expect(queryResult[1].abs().toString()).to.eq( + swapInfo.swapAmount.toString() + ); + }); }); }); diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 605d478d..0336c9cf 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -1,7 +1,8 @@ // yarn test:only test/xaveFxPool.spec.ts import { expect } from 'chai'; import { formatFixed, parseFixed, BigNumber } from '@ethersproject/bignumber'; -import { bnum, ZERO } from '../src/utils/bignumber'; +import { BigNumber as OldBigNumber, ZERO, bnum } from '../src/utils/bignumber'; + import { PoolTypes, SwapTypes } from '../src'; // Add new PoolType import { FxPool, FxPoolPairData } from '../src/pools/xaveFxPool/fxPool'; @@ -48,20 +49,20 @@ describe('Test for fxPools', () => { expect(poolPairData.id).to.eq(poolData.id); expect(poolPairData.poolType).to.eq(PoolTypes.Fx); - expect(poolPairData.alpha._hex).to.eq( - parseFixed(poolData.alpha, 18)._hex + expect(poolPairData.alpha.toString()).to.eq( + poolData.alpha ); - expect(poolPairData.beta._hex).to.eq( - parseFixed(poolData.beta, 18)._hex + expect(poolPairData.beta.toString()).to.eq( + poolData.beta ); - expect(poolPairData.lambda._hex).to.eq( - parseFixed(poolData.lambda, 18)._hex + expect(poolPairData.lambda.toString()).to.eq( + poolData.lambda ); - expect(poolPairData.delta._hex).to.eq( - parseFixed(poolData.delta, 18)._hex + expect(poolPairData.delta.toString()).to.eq( + poolData.delta ); - expect(poolPairData.epsilon._hex).to.eq( - parseFixed(poolData.epsilon, 18)._hex + expect(poolPairData.epsilon.toString()).to.eq( + poolData.epsilon ); }); }); @@ -80,7 +81,7 @@ describe('Test for fxPools', () => { ); const reservesInNumeraire = poolBalancesToNumeraire(poolPairData); - const alphaValue = bnum(formatFixed(poolPairData.alpha, 18)); + const alphaValue = poolPairData.alpha.div(bnum(10).pow(18)); const maxLimit = alphaValue .plus(1) .times(reservesInNumeraire._oGLiq) @@ -96,12 +97,16 @@ describe('Test for fxPools', () => { const expectedLimitForTokenIn = viewRawAmount( maxLimitAmountForTokenIn, - poolPairData.tokenInLatestFXPrice + bnum(poolPairData.decimalsIn), + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInfxOracleDecimals ); const expectedLimitForTokenOut = viewRawAmount( maxLimitAmountForTokenOut, - poolPairData.tokenOutLatestFXPrice + bnum(poolPairData.decimalsOut), + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutfxOracleDecimals ); let amount = newPool.getLimitAmountSwap( @@ -187,7 +192,9 @@ describe('Test for fxPools', () => { expect(amountOut.toNumber()).to.be.closeTo( viewRawAmount( bnum(testCase.expectedSwapOutput), - poolPairData.tokenOutLatestFXPrice + bnum(poolPairData.decimalsOut), + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutfxOracleDecimals ).toNumber(), 10000 ); // rounded off @@ -242,7 +249,9 @@ describe('Test for fxPools', () => { expect(amountIn.toNumber()).to.be.closeTo( viewRawAmount( bnum(testCase.expectedSwapOutput), - poolPairData.tokenInLatestFXPrice + bnum(poolPairData.decimalsIn), + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInfxOracleDecimals ).toNumber(), 2000000 ); // rounded off, decimal adjustment @@ -298,13 +307,15 @@ describe('Test for fxPools', () => { balanceIn: BigNumber.from('0xbf24ffac00'), balanceOut: BigNumber.from('0x59bbba58b6'), swapFee: BigNumber.from('0x25'), - alpha: BigNumber.from('0x0b1a2bc2ec500000'), - beta: BigNumber.from('0x06a94d74f4300000'), - lambda: BigNumber.from('0x0429d069189e0000'), - delta: BigNumber.from('0x03cb71f51fc55800'), - epsilon: BigNumber.from('0x01c6bf52634000'), + alpha: bnum('0x0b1a2bc2ec500000'), + beta: bnum('0x06a94d74f4300000'), + lambda: bnum('0x0429d069189e0000'), + delta: bnum('0x03cb71f51fc55800'), + epsilon: bnum('0x01c6bf52634000'), tokenInLatestFXPrice: bnum('99963085000000'), tokenOutLatestFXPrice: bnum('74200489000000'), + tokenInfxOracleDecimals: bnum(8), + tokenOutfxOracleDecimals: bnum(8), }; const sp = _spotPriceAfterSwapExactTokenInForTokenOut( poolPairData, diff --git a/tsconfig.json b/tsconfig.json index c9120c5c..a161d5a1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { "module": "esnext", - "target": "es2016", + "target": "es2020", "declaration": true, - "lib": ["es2019", "es2018.promise"], + "lib": ["es2020", "es2018.promise"], "outDir": "./dist/", "baseUrl": "src", "esModuleInterop": true, From f0b35162001abbcc92ff053c17870fc51e602e8f Mon Sep 17 00:00:00 2001 From: aplki Date: Wed, 7 Jun 2023 04:11:35 +0800 Subject: [PATCH 07/56] breaking polygon test --- test/xaveFxPool-polygon.integration.spec.ts | 153 ++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 test/xaveFxPool-polygon.integration.spec.ts diff --git a/test/xaveFxPool-polygon.integration.spec.ts b/test/xaveFxPool-polygon.integration.spec.ts new file mode 100644 index 00000000..4cdf25f1 --- /dev/null +++ b/test/xaveFxPool-polygon.integration.spec.ts @@ -0,0 +1,153 @@ +// yarn test:only test/xaveFxPool-polygon.integration.spec.ts +import dotenv from 'dotenv'; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { SOR, SubgraphPoolBase, SwapTypes } from '../src'; +import { ADDRESSES, Network, vaultAddr } from './testScripts/constants'; +import { parseFixed } from '@ethersproject/bignumber'; +import { expect } from 'chai'; +import { Vault__factory } from '@balancer-labs/typechain'; +import { AddressZero } from '@ethersproject/constants'; +import { setUp } from './testScripts/utils'; + +/* + * Testing Notes: + * - Add polygon RPC_URL + * - Change hardhat chain id to 137 when testing + * - Run node on terminal: yarn run node-polygon + */ + +dotenv.config(); + +let sor: SOR; +const networkId = Network.POLYGON; +const jsonRpcUrl = process.env.RPC_URL; +const rpcUrl = 'http://127.0.0.1:8545'; +const provider = new JsonRpcProvider(rpcUrl, networkId); +const blocknumber = 38546978; + +const vault = Vault__factory.connect(vaultAddr, provider); +const SWAP_AMOUNT_IN_NUMERAIRE = '10000'; + +const xaveFxPoolXSGD_USDC_POLYGON: SubgraphPoolBase = { + id: '0x726e324c29a1e49309672b244bdc4ff62a270407000200000000000000000702', + address: '0x726e324c29a1e49309672b244bdc4ff62a270407', + poolType: 'FX', + swapFee: '0', + swapEnabled: true, + totalWeight: '0', + totalShares: '1187294', + tokensList: [ + '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', + '0xdc3326e71d45186f113a2f448984ca0e8d201995', + ], + tokens: [ + { + address: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', + balance: '731837.486297', + decimals: 6, + priceRate: '1', + weight: null, + token: { + latestFXPrice: '1.00000000', + fxOracleDecimals: 8, + }, + }, + { + address: '0xdc3326e71d45186f113a2f448984ca0e8d201995', + balance: '639986.37244', + decimals: 6, + priceRate: '1', + weight: null, + token: { + latestFXPrice: '0.74376600', + fxOracleDecimals: 8, + }, + }, + ], + alpha: '0.8', + beta: '0.48', + lambda: '0.3', + delta: '0.2734375', + epsilon: '0.0005', +}; + +describe('xaveFxPool: DAI-USDC integration tests', () => { + context('test swaps vs queryBatchSwap', () => { + // Setup chain + before(async function () { + sor = await setUp( + networkId, + provider, + [xaveFxPoolXSGD_USDC_POLYGON], + jsonRpcUrl as string, + blocknumber + ); + + await sor.fetchPools(); + }); + + const tokenIn = ADDRESSES[Network.POLYGON].USDC.address; + const tokenOut = ADDRESSES[Network.POLYGON].XSGD.address; + + const funds = { + sender: AddressZero, + recipient: AddressZero, + fromInternalBalance: false, + toInternalBalance: false, + }; + + it('ExactIn', async () => { + const swapType = SwapTypes.SwapExactIn; + // swapAmount is tokenIn, expect tokenOut + const swapAmount = parseFixed(SWAP_AMOUNT_IN_NUMERAIRE, 6); + + const swapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmount + ); + + const queryResult = await vault.callStatic.queryBatchSwap( + swapType, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + + expect(queryResult[0].toString()).to.eq( + swapInfo.swapAmount.toString() + ); + + expect(queryResult[1].abs().toString()).to.be.eq( + swapInfo.returnAmount.toString() + ); + }); + + it('ExactOut', async () => { + const swapType = SwapTypes.SwapExactOut; + // swapAmount is tokenOut, expect tokenIn + const swapAmount = parseFixed(SWAP_AMOUNT_IN_NUMERAIRE, 6); + const swapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmount + ); + + const queryResult = await vault.callStatic.queryBatchSwap( + swapType, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + + expect(queryResult[0].abs().toString()).to.be.eq( + swapInfo.returnAmount.toString() + ); + expect(queryResult[1].abs().toString()).to.eq( + swapInfo.swapAmount.toString() + ); + }); + }); +}); From 519d41815e76e2dec941d4aac5ff8a48539a165f Mon Sep 17 00:00:00 2001 From: andreiashu Date: Thu, 8 Jun 2023 13:24:17 +0700 Subject: [PATCH 08/56] =?UTF-8?q?=F0=9F=8D=85=20fix=20the=20subgraph=20val?= =?UTF-8?q?ues.=20Tests=20passing=20on=20Polygon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/xaveFxPool-polygon.integration.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/xaveFxPool-polygon.integration.spec.ts b/test/xaveFxPool-polygon.integration.spec.ts index 4cdf25f1..1724bd85 100644 --- a/test/xaveFxPool-polygon.integration.spec.ts +++ b/test/xaveFxPool-polygon.integration.spec.ts @@ -43,23 +43,23 @@ const xaveFxPoolXSGD_USDC_POLYGON: SubgraphPoolBase = { tokens: [ { address: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', - balance: '731837.486297', + balance: '831428.276274', decimals: 6, priceRate: '1', weight: null, token: { - latestFXPrice: '1.00000000', + latestFXPrice: '1.00010000', fxOracleDecimals: 8, }, }, { address: '0xdc3326e71d45186f113a2f448984ca0e8d201995', - balance: '639986.37244', + balance: '377081.466659', decimals: 6, priceRate: '1', weight: null, token: { - latestFXPrice: '0.74376600', + latestFXPrice: '0.76220000', fxOracleDecimals: 8, }, }, @@ -71,7 +71,7 @@ const xaveFxPoolXSGD_USDC_POLYGON: SubgraphPoolBase = { epsilon: '0.0005', }; -describe('xaveFxPool: DAI-USDC integration tests', () => { +describe('xaveFxPool: XSGD-USDC integration tests', () => { context('test swaps vs queryBatchSwap', () => { // Setup chain before(async function () { From 29f16c1b56325fefa319d2e5d2fa5edb7e4e3b1b Mon Sep 17 00:00:00 2001 From: aplki Date: Thu, 8 Jun 2023 16:47:49 +0800 Subject: [PATCH 09/56] added new values for subgraph return --- test/xaveFxPool-polygon.integration.spec.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/xaveFxPool-polygon.integration.spec.ts b/test/xaveFxPool-polygon.integration.spec.ts index 1724bd85..e8ab35b4 100644 --- a/test/xaveFxPool-polygon.integration.spec.ts +++ b/test/xaveFxPool-polygon.integration.spec.ts @@ -23,10 +23,11 @@ const networkId = Network.POLYGON; const jsonRpcUrl = process.env.RPC_URL; const rpcUrl = 'http://127.0.0.1:8545'; const provider = new JsonRpcProvider(rpcUrl, networkId); -const blocknumber = 38546978; +const blocknumber = 43667355; const vault = Vault__factory.connect(vaultAddr, provider); -const SWAP_AMOUNT_IN_NUMERAIRE = '10000'; +// const SWAP_AMOUNT_IN_NUMERAIRE = '400000'; +const SWAP_AMOUNT_IN_NUMERAIRE = '600000'; const xaveFxPoolXSGD_USDC_POLYGON: SubgraphPoolBase = { id: '0x726e324c29a1e49309672b244bdc4ff62a270407000200000000000000000702', @@ -35,7 +36,7 @@ const xaveFxPoolXSGD_USDC_POLYGON: SubgraphPoolBase = { swapFee: '0', swapEnabled: true, totalWeight: '0', - totalShares: '1187294', + totalShares: '1860106.756724251739208277', // subgraph blocknumber 43667355 tokensList: [ '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', '0xdc3326e71d45186f113a2f448984ca0e8d201995', @@ -43,23 +44,23 @@ const xaveFxPoolXSGD_USDC_POLYGON: SubgraphPoolBase = { tokens: [ { address: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', - balance: '831428.276274', + balance: '640405.311822', decimals: 6, priceRate: '1', weight: null, token: { - latestFXPrice: '1.00010000', + latestFXPrice: '0.99997703', fxOracleDecimals: 8, }, }, { address: '0xdc3326e71d45186f113a2f448984ca0e8d201995', - balance: '377081.466659', + balance: '1533442.592483', decimals: 6, priceRate: '1', weight: null, token: { - latestFXPrice: '0.76220000', + latestFXPrice: '0.74226380', fxOracleDecimals: 8, }, }, @@ -71,7 +72,7 @@ const xaveFxPoolXSGD_USDC_POLYGON: SubgraphPoolBase = { epsilon: '0.0005', }; -describe('xaveFxPool: XSGD-USDC integration tests', () => { +describe('xaveFxPool: DAI-USDC integration tests', () => { context('test swaps vs queryBatchSwap', () => { // Setup chain before(async function () { From 7b753bd98bf9c73617874e09eeb0b8d8848b1588 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Sat, 10 Jun 2023 10:57:52 +0700 Subject: [PATCH 10/56] =?UTF-8?q?=F0=9F=8F=88=20code=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 40 +++++++++++++++++++++--------- src/pools/xaveFxPool/fxPoolMath.ts | 24 ++++++------------ test/xaveFxPool.spec.ts | 24 ++++++------------ 3 files changed, 42 insertions(+), 46 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index df1e5604..945aff71 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -1,7 +1,7 @@ import { getAddress } from '@ethersproject/address'; import { BigNumber, formatFixed, parseFixed } from '@ethersproject/bignumber'; import { Zero } from '@ethersproject/constants'; -import { BigNumber as OldBigNumber, ZERO, bnum, scale } from './big-number'; +import { BigNumber as OldBigNumber, ZERO, bnum } from './big-number'; import { isSameAddress } from '../../utils'; import { universalNormalizedLiquidity } from '../liquidity'; import { @@ -21,7 +21,6 @@ import { _spotPriceAfterSwapExactTokenInForTokenOut, _spotPriceAfterSwapTokenInForExactTokenOut, _tokenInForExactTokenOut, - ONE_ETHER, } from './fxPoolMath'; type FxPoolToken = Pick< @@ -30,12 +29,14 @@ type FxPoolToken = Pick< >; // replicates ` (_lambda + 1).divu(1e18)` operation from the smart contract -// ( 10000n << 64n ) / (10n**18n) * 10n**18n >> 64n == 9999n const parseFixedCurveParam = (param: string): OldBigNumber => { - // `alpha * 1e18 + 1) - const param64 = (((BigInt(parseFixed(param, 18).toString()) + 1n) << 64n) / 10n ** 18n) * 10n ** 36n >> 64n; - return bnum(param64.toString()).div(bnum(10).pow(18)); -} + const param64 = + ((((BigInt(parseFixed(param, 18).toString()) + 1n) << 64n) / + 10n ** 18n) * + 10n ** 36n) >> + 64n; + return bnum(param64.toString()).div(bnum(10).pow(18)); +}; export type FxPoolPairData = PoolPairBase & { alpha: OldBigNumber; @@ -115,12 +116,27 @@ export class FxPool implements PoolBase { this.totalShares = parseFixed(totalShares, 18); this.tokens = tokens; this.tokensList = tokensList; - this.alpha = parseFixedCurveParam(alpha).decimalPlaces(3, OldBigNumber.ROUND_UP); - this.beta = parseFixedCurveParam(beta).decimalPlaces(3, OldBigNumber.ROUND_UP); - this.lambda = parseFixedCurveParam(lambda).decimalPlaces(3, OldBigNumber.ROUND_UP); - this.delta = parseFixedCurveParam(delta).decimalPlaces(3, OldBigNumber.ROUND_UP); + this.alpha = parseFixedCurveParam(alpha).decimalPlaces( + 3, + OldBigNumber.ROUND_UP + ); + this.beta = parseFixedCurveParam(beta).decimalPlaces( + 3, + OldBigNumber.ROUND_UP + ); + this.lambda = parseFixedCurveParam(lambda).decimalPlaces( + 3, + OldBigNumber.ROUND_UP + ); + this.delta = parseFixedCurveParam(delta).decimalPlaces( + 3, + OldBigNumber.ROUND_UP + ); - this.epsilon = parseFixedCurveParam(epsilon).decimalPlaces(3, OldBigNumber.ROUND_UP); + this.epsilon = parseFixedCurveParam(epsilon).decimalPlaces( + 3, + OldBigNumber.ROUND_UP + ); } updateTotalShares: (newTotalShares: BigNumber) => void; mainIndex?: number | undefined; diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 7a9d1dfa..578d44a2 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -1,6 +1,6 @@ import { BigNumber as OldBigNumber, bnum, scale } from '../../utils/bignumber'; import { FxPoolPairData } from './fxPool'; -import { BigNumber, formatFixed } from '@ethersproject/bignumber'; +import { BigNumber } from '@ethersproject/bignumber'; // Constants export const CURVEMATH_MAX_DIFF = -0.000001000000000000024; @@ -85,17 +85,6 @@ const calculateGivenAmountInNumeraire = ( return calculatedNumeraireAmount; }; -const BNToBigInt = (val: OldBigNumber): bigint => { - try { - // 1 is ROUND_DOWN - return BigInt(val.integerValue(1).toString()); - } catch (e) {} - - throw new Error( - `this platform does not support BigInt. Value: ${val.toString()}` - ); -}; - /** * Convert from an Ethersjs BigNumber to a bignumber.js BigNumber * @param amount BigNumber @@ -252,10 +241,11 @@ export const viewRawAmount = ( // solidity code `_amount.mulu(baseDecimals).mul(baseOracleDecimals).div(_rate); return _amount - .times(bnum(10).pow(tokenDecimals)) - .integerValue(OldBigNumber.ROUND_DOWN) // `mulu` rounds down - .times(bnum(10).pow(fxOracleDecimals)) - .div(rate).integerValue(OldBigNumber.ROUND_DOWN); + .times(bnum(10).pow(tokenDecimals)) + .integerValue(OldBigNumber.ROUND_DOWN) // `mulu` rounds down + .times(bnum(10).pow(fxOracleDecimals)) + .div(rate) + .integerValue(OldBigNumber.ROUND_DOWN); }; const viewNumeraireAmount = ( @@ -505,7 +495,7 @@ export function _exactTokenInForTokenOut( } else { const epsilon = parsedFxPoolData.epsilon; const _amtWithFee = _amt[0].times(bnum(1).minus(epsilon)); // fee retained by the pool // @TODO this results in a 1 wei less in solidity - // -10.0019 * (1-0.0015) + // -10.0019 * (1-0.0015) return viewRawAmount( _amtWithFee.abs(), bnum(poolPairData.decimalsOut), diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 0336c9cf..66aaf6cd 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -1,7 +1,7 @@ // yarn test:only test/xaveFxPool.spec.ts import { expect } from 'chai'; -import { formatFixed, parseFixed, BigNumber } from '@ethersproject/bignumber'; -import { BigNumber as OldBigNumber, ZERO, bnum } from '../src/utils/bignumber'; +import { BigNumber } from '@ethersproject/bignumber'; +import { ZERO, bnum } from '../src/utils/bignumber'; import { PoolTypes, SwapTypes } from '../src'; // Add new PoolType @@ -49,21 +49,11 @@ describe('Test for fxPools', () => { expect(poolPairData.id).to.eq(poolData.id); expect(poolPairData.poolType).to.eq(PoolTypes.Fx); - expect(poolPairData.alpha.toString()).to.eq( - poolData.alpha - ); - expect(poolPairData.beta.toString()).to.eq( - poolData.beta - ); - expect(poolPairData.lambda.toString()).to.eq( - poolData.lambda - ); - expect(poolPairData.delta.toString()).to.eq( - poolData.delta - ); - expect(poolPairData.epsilon.toString()).to.eq( - poolData.epsilon - ); + expect(poolPairData.alpha.toString()).to.eq(poolData.alpha); + expect(poolPairData.beta.toString()).to.eq(poolData.beta); + expect(poolPairData.lambda.toString()).to.eq(poolData.lambda); + expect(poolPairData.delta.toString()).to.eq(poolData.delta); + expect(poolPairData.epsilon.toString()).to.eq(poolData.epsilon); }); }); From fdeb41b964b25384bc75c47fed68153d809bb171 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Sat, 10 Jun 2023 11:06:16 +0700 Subject: [PATCH 11/56] =?UTF-8?q?=F0=9F=8D=BF=20minor=20changes:=20code=20?= =?UTF-8?q?reuse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 945aff71..63fefd17 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -35,7 +35,9 @@ const parseFixedCurveParam = (param: string): OldBigNumber => { 10n ** 18n) * 10n ** 36n) >> 64n; - return bnum(param64.toString()).div(bnum(10).pow(18)); + return bnum(param64.toString()) + .div(bnum(10).pow(18)) + .decimalPlaces(3, OldBigNumber.ROUND_UP); }; export type FxPoolPairData = PoolPairBase & { @@ -116,27 +118,12 @@ export class FxPool implements PoolBase { this.totalShares = parseFixed(totalShares, 18); this.tokens = tokens; this.tokensList = tokensList; - this.alpha = parseFixedCurveParam(alpha).decimalPlaces( - 3, - OldBigNumber.ROUND_UP - ); - this.beta = parseFixedCurveParam(beta).decimalPlaces( - 3, - OldBigNumber.ROUND_UP - ); - this.lambda = parseFixedCurveParam(lambda).decimalPlaces( - 3, - OldBigNumber.ROUND_UP - ); - this.delta = parseFixedCurveParam(delta).decimalPlaces( - 3, - OldBigNumber.ROUND_UP - ); + this.alpha = parseFixedCurveParam(alpha); + this.beta = parseFixedCurveParam(beta); + this.lambda = parseFixedCurveParam(lambda); + this.delta = parseFixedCurveParam(delta); - this.epsilon = parseFixedCurveParam(epsilon).decimalPlaces( - 3, - OldBigNumber.ROUND_UP - ); + this.epsilon = parseFixedCurveParam(epsilon); } updateTotalShares: (newTotalShares: BigNumber) => void; mainIndex?: number | undefined; From 7a79166112280ddb58cbc338c9a336a9202d9cb5 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Sat, 10 Jun 2023 11:20:20 +0700 Subject: [PATCH 12/56] =?UTF-8?q?=F0=9F=A6=84=20added=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 63fefd17..28692681 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -28,7 +28,27 @@ type FxPoolToken = Pick< 'address' | 'balance' | 'decimals' | 'token' >; -// replicates ` (_lambda + 1).divu(1e18)` operation from the smart contract +/** + * Replicates the conversion operation to 64.64 fixed point numbers (ABDK library) + * that occurs in the smart contract. This is done to replicate the loss of precision + * from the smart contract. + * + * For example: in 1e18 decimals, when converting _epsilon_ `0.0015` from `uint256` + * to a 64.64 fixed point number (`(_epsilon + 1).divu(1e18)`) there is a loss + * of precision. In 64.64 fixed point, epsilon is stored as 0.001500000000000000953. + * This is the value that is used in calculations in the smart contract. + * + * When converted from 64.64 fixed point back to `uint256` the value is + * 0.001500000000000000 which is the same as the original value of 0.0015. + * This is what the graph is seeing. + * + * This function is used to replicate the same loss of precision that occurs + * in the smart contract so that we work with an epsilon value of + * 0.001500000000000000953 instead of 0.0015. + * + * @param param any of the pool's curve parameters like alpha, beta, lambda, delta, epsilon + * @returns OldBigNumber with the same loss of precision as the smart contract + */ const parseFixedCurveParam = (param: string): OldBigNumber => { const param64 = ((((BigInt(parseFixed(param, 18).toString()) + 1n) << 64n) / From c1f4c35b9af08d2a455b91fc91eb8cdebb9a3d16 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Sun, 11 Jun 2023 11:56:55 +0700 Subject: [PATCH 13/56] Removed obsolete documentation --- test/xaveFxPool.integration.spec.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index b890d9a5..8564a92a 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -70,11 +70,6 @@ const xaveFxPoolDAI_USDC_MAINNET: SubgraphPoolBase = { beta: '0.42', lambda: '0.3', delta: '0.3', - // precise value is `0.001500000000000000953` - // but right now we have parseFixed(epsilon, 18) across our code - // so had to trim the value to 18 decimals - // but the correct code should be parseFixed(epsilon, 21) - // epsilon: '0.001500000000000001', epsilon: '0.0015', }; From 91a307627530782daa3c0a2557745eaadf372d4d Mon Sep 17 00:00:00 2001 From: andreiashu Date: Mon, 12 Jun 2023 07:47:15 +0700 Subject: [PATCH 14/56] =?UTF-8?q?=E2=9B=91=20switched=20back=20to=20`ALCHE?= =?UTF-8?q?MY=5FURL`;=20fixed=20one=20test=20in=20`xaveFxPool.spec.ts`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- src/pools/xaveFxPool/fxPool.ts | 28 +++++++--------- test/testData/fxPool/fxPool.json | 6 ++-- test/xaveFxPool-polygon.integration.spec.ts | 2 +- test/xaveFxPool.integration.spec.ts | 2 +- test/xaveFxPool.spec.ts | 37 +++++++++++++++++---- 6 files changed, 51 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 8e88c08b..36da4d3c 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,9 @@ "test-xave": "DEBUG=xave TS_NODE_PROJECT='tsconfig.testing.json' nyc mocha --watch -r ts-node/register test/xaveFxPool.integration.spec.ts --timeout 20000", "test": "TS_NODE_PROJECT='tsconfig.testing.json' nyc mocha -r ts-node/register test/*.spec.ts --timeout 20000", "test:only": "TS_NODE_PROJECT='tsconfig.testing.json' npx mocha -r ts-node/register --timeout 20000", + "coverage": "nyc report --reporter=text-lcov | coveralls", "lint": "eslint ./src ./test --ext .ts --max-warnings 0", - "node": "npx hardhat node --tsconfig tsconfig.testing.json --fork $(grep RPC_URL .env | cut -d '=' -f2) --fork-block-number 14828550" + "node": "npx hardhat node --tsconfig tsconfig.testing.json --fork $(grep ALCHEMY_URL .env | cut -d '=' -f2)" }, "husky": { "hooks": { diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 28692681..da668e65 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -123,15 +123,6 @@ export class FxPool implements PoolBase { delta: string, epsilon: string ) { - /** - 64.64 fixed point value -> decimal (including precision error) - alpha 14757395258967641311 -> 0.800000000000000000987 - beta 7747632510958011697 -> 0.420000000000000000991 - delta 5534023222112865502 -> 0.30000000000000000093 - epsilon 27670116110564345 -> 0.001500000000000000953 - lambda 5534023222112865503 -> 0.300000000000000000987 - */ - this.id = id; this.address = address; this.swapFee = parseFixed(swapFee, 18); @@ -142,7 +133,6 @@ export class FxPool implements PoolBase { this.beta = parseFixedCurveParam(beta); this.lambda = parseFixedCurveParam(lambda); this.delta = parseFixedCurveParam(delta); - this.epsilon = parseFixedCurveParam(epsilon); } updateTotalShares: (newTotalShares: BigNumber) => void; @@ -200,12 +190,18 @@ export class FxPool implements PoolBase { lambda: this.lambda, delta: this.delta, epsilon: this.epsilon, - tokenInLatestFXPrice: bnum(tI.token.latestFXPrice) - .times(bnum(10).pow(tI.token.fxOracleDecimals)) - .integerValue(OldBigNumber.ROUND_DOWN), // decimals is formatted from subgraph in rate we get from the chainlink oracle - tokenOutLatestFXPrice: bnum(tO.token.latestFXPrice) - .times(bnum(10).pow(tO.token.fxOracleDecimals)) - .integerValue(OldBigNumber.ROUND_DOWN), // decimals is formatted from subgraph in rate we get from the chainlink oracle + tokenInLatestFXPrice: bnum( + parseFixed( + tI.token.latestFXPrice, + tI.token.fxOracleDecimals + ).toString() + ), // decimals is formatted from subgraph in rate we get from the chainlink oracle + tokenOutLatestFXPrice: bnum( + parseFixed( + tO.token.latestFXPrice, + tO.token.fxOracleDecimals + ).toString() + ), // decimals is formatted from subgraph in rate we get from the chainlink oracle tokenInfxOracleDecimals: bnum(tI.token.fxOracleDecimals), tokenOutfxOracleDecimals: bnum(tO.token.fxOracleDecimals), }; diff --git a/test/testData/fxPool/fxPool.json b/test/testData/fxPool/fxPool.json index 384a48e1..35ab7aa0 100644 --- a/test/testData/fxPool/fxPool.json +++ b/test/testData/fxPool/fxPool.json @@ -16,7 +16,8 @@ "priceRate": "1", "weight": null, "token": { - "latestFXPrice": "1.00000000" + "latestFXPrice": "1.00000000", + "fxOracleDecimals": 8 } }, { @@ -27,7 +28,8 @@ "priceRate": "1", "weight": null, "token": { - "latestFXPrice": "0.74376600" + "latestFXPrice": "0.74376600", + "fxOracleDecimals": 8 } } ], diff --git a/test/xaveFxPool-polygon.integration.spec.ts b/test/xaveFxPool-polygon.integration.spec.ts index e8ab35b4..01e0a0f1 100644 --- a/test/xaveFxPool-polygon.integration.spec.ts +++ b/test/xaveFxPool-polygon.integration.spec.ts @@ -20,7 +20,7 @@ dotenv.config(); let sor: SOR; const networkId = Network.POLYGON; -const jsonRpcUrl = process.env.RPC_URL; +const jsonRpcUrl = process.env.ALCHEMY_URL; const rpcUrl = 'http://127.0.0.1:8545'; const provider = new JsonRpcProvider(rpcUrl, networkId); const blocknumber = 43667355; diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index 8564a92a..7ca0780a 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -21,7 +21,7 @@ dotenv.config(); let sor: SOR; const networkId = Network.MAINNET; -const jsonRpcUrl = process.env.RPC_URL; +const jsonRpcUrl = process.env.ALCHEMY_URL; const rpcUrl = 'http://127.0.0.1:8545'; const provider = new JsonRpcProvider(rpcUrl, networkId); const blocknumber = 16797531; diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 66aaf6cd..49479e67 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -1,6 +1,6 @@ // yarn test:only test/xaveFxPool.spec.ts import { expect } from 'chai'; -import { BigNumber } from '@ethersproject/bignumber'; +import { BigNumber, parseFixed } from '@ethersproject/bignumber'; import { ZERO, bnum } from '../src/utils/bignumber'; import { PoolTypes, SwapTypes } from '../src'; @@ -49,11 +49,36 @@ describe('Test for fxPools', () => { expect(poolPairData.id).to.eq(poolData.id); expect(poolPairData.poolType).to.eq(PoolTypes.Fx); - expect(poolPairData.alpha.toString()).to.eq(poolData.alpha); - expect(poolPairData.beta.toString()).to.eq(poolData.beta); - expect(poolPairData.lambda.toString()).to.eq(poolData.lambda); - expect(poolPairData.delta.toString()).to.eq(poolData.delta); - expect(poolPairData.epsilon.toString()).to.eq(poolData.epsilon); + expect( + poolPairData.alpha + .div(bnum(10).pow(18)) + .decimalPlaces(8) + .toString() + ).to.eq(poolData.alpha); + expect( + poolPairData.beta + .div(bnum(10).pow(18)) + .decimalPlaces(8) + .toString() + ).to.eq(poolData.beta); + expect( + poolPairData.lambda + .div(bnum(10).pow(18)) + .decimalPlaces(8) + .toString() + ).to.eq(poolData.lambda); + expect( + poolPairData.delta + .div(bnum(10).pow(18)) + .decimalPlaces(8) + .toString() + ).to.eq(poolData.delta); + expect( + poolPairData.epsilon + .div(bnum(10).pow(18)) + .decimalPlaces(8) + .toString() + ).to.eq(poolData.epsilon); }); }); From b9d6c907c06decc07d86058cf41ca4004b616806 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 14 Jun 2023 09:25:00 +0700 Subject: [PATCH 15/56] =?UTF-8?q?=F0=9F=8E=A3=20fixed=20several=20test=20c?= =?UTF-8?q?ases;=20still=20wip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 34 +++- test/testData/fxPool/fxPoolTestCases.json | 16 +- test/xaveFxPool-polygon.integration.spec.ts | 2 +- test/xaveFxPool.integration.spec.ts | 36 +++++ test/xaveFxPool.spec.ts | 162 ++++++++------------ 5 files changed, 133 insertions(+), 117 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 578d44a2..63598af6 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -174,8 +174,12 @@ const getParsedFxPoolData = ( // rate is converted from chainlink to the actual rate in decimals const baseTokenRate = isUSDC(poolPairData.tokenIn) - ? poolPairData.tokenOutLatestFXPrice - : poolPairData.tokenInLatestFXPrice; + ? poolPairData.tokenOutLatestFXPrice.div( + bnum(10).pow(poolPairData.tokenOutfxOracleDecimals) + ) + : poolPairData.tokenInLatestFXPrice.div( + bnum(10).pow(poolPairData.tokenInfxOracleDecimals) + ); // given amount in or out converted to numeraire const givenAmountInNumeraire = calculateGivenAmountInNumeraire( @@ -232,10 +236,19 @@ export const getBaseDecimals = (decimals: number) => { // Base Assimilator Functions // calculations are from the BaseToUsdAssimilator + +/** + * + * @param _amount in numeraire + * @param tokenDecimals + * @param rate in wei + * @param fxOracleDecimals + * @returns amount in wei + */ export const viewRawAmount = ( - _amount: OldBigNumber, + _amount: OldBigNumber, // numeraire tokenDecimals: OldBigNumber, - rate: OldBigNumber, + rate: OldBigNumber, // wei fxOracleDecimals: OldBigNumber ): OldBigNumber => { // solidity code `_amount.mulu(baseDecimals).mul(baseOracleDecimals).div(_rate); @@ -248,10 +261,17 @@ export const viewRawAmount = ( .integerValue(OldBigNumber.ROUND_DOWN); }; -const viewNumeraireAmount = ( - _amount: OldBigNumber, +/** + * @param _amount in wei + * @param tokenDecimals + * @param rate in wei + * @param fxOracleDecimals + * @returns amount in numeraire (ie. user friendly decimals) + */ +export const viewNumeraireAmount = ( + _amount: OldBigNumber, // wei tokenDecimals: OldBigNumber, - rate: OldBigNumber, + rate: OldBigNumber, // wei fxOracleDecimals: OldBigNumber ): OldBigNumber => { // Solidity: _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); diff --git a/test/testData/fxPool/fxPoolTestCases.json b/test/testData/fxPool/fxPoolTestCases.json index dba58766..61dc3226 100644 --- a/test/testData/fxPool/fxPoolTestCases.json +++ b/test/testData/fxPool/fxPoolTestCases.json @@ -9,7 +9,7 @@ "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", "expectedSwapOutput": "99950.00", - "expectedDerivativeSpotPriceAfterSwap": "0" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, { "testNo": "2", @@ -21,7 +21,7 @@ "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", "expectedSwapOutput": "100050", - "expectedDerivativeSpotPriceAfterSwap": "0" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, { "testNo": "3", @@ -33,7 +33,7 @@ "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", "expectedSwapOutput": "74339.41", - "expectedDerivativeSpotPriceAfterSwap": "0" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, { "testNo": "4", @@ -45,7 +45,7 @@ "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", "expectedSwapOutput": "74413.79", - "expectedDerivativeSpotPriceAfterSwap": "0" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, { "testNo": "5", @@ -55,7 +55,7 @@ "tokenIn": "USDC", "tokenOut": "XSGD", "expectedSpotPriceBeforeSwap": "0.743394117", - "expectedSpotPriceAfterSwap": "0.7387621895", + "expectedSpotPriceAfterSwap": "0.73876219", "expectedSwapOutput": "198654.47", "expectedDerivativeSpotPriceAfterSwap": "0.006230783064" }, @@ -69,7 +69,7 @@ "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", "expectedSwapOutput": "200100", - "expectedDerivativeSpotPriceAfterSwap": "0" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, { "testNo": "7", @@ -81,7 +81,7 @@ "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", "expectedSwapOutput": "200716.41", - "expectedDerivativeSpotPriceAfterSwap": "0" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, { "testNo": "8", @@ -91,7 +91,7 @@ "tokenIn": "USDC", "tokenOut": "XSGD", "expectedSpotPriceBeforeSwap": "0.743394117", - "expectedSpotPriceAfterSwap": "0.7382737807", + "expectedSpotPriceAfterSwap": "0.73827378", "expectedSwapOutput": "202310.75", "expectedDerivativeSpotPriceAfterSwap": "0.006887781537" }, diff --git a/test/xaveFxPool-polygon.integration.spec.ts b/test/xaveFxPool-polygon.integration.spec.ts index 01e0a0f1..abe25d0e 100644 --- a/test/xaveFxPool-polygon.integration.spec.ts +++ b/test/xaveFxPool-polygon.integration.spec.ts @@ -72,7 +72,7 @@ const xaveFxPoolXSGD_USDC_POLYGON: SubgraphPoolBase = { epsilon: '0.0005', }; -describe('xaveFxPool: DAI-USDC integration tests', () => { +describe('[POLYGON] xaveFxPool: DAI-USDC integration tests', () => { context('test swaps vs queryBatchSwap', () => { // Setup chain before(async function () { diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index 7ca0780a..aeb41b4f 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -9,6 +9,15 @@ import { Vault__factory } from '@balancer-labs/typechain'; import { AddressZero } from '@ethersproject/constants'; import { setUp } from './testScripts/utils'; +import { ZERO, bnum } from '../src/utils/bignumber'; +import { + ALMOST_ZERO, + poolBalancesToNumeraire, + spotPriceBeforeSwap, + viewRawAmount, + viewNumeraireAmount, + _spotPriceAfterSwapExactTokenInForTokenOut, +} from '../src/pools/xaveFxPool/fxPoolMath'; /* * Testing Notes: * - Add infura api key on .env @@ -74,6 +83,33 @@ const xaveFxPoolDAI_USDC_MAINNET: SubgraphPoolBase = { }; describe('xaveFxPool: DAI-USDC integration tests', () => { + context('test fxMath functions', () => { + const tokenDecimals = bnum(6); + const tokenFxRateDecimals = bnum(8); + const rate = bnum('74376600'); // 0.743766 + it(`should correctly return 'viewRawAmount'`, async () => { + const rawAmount = viewRawAmount( + bnum('10000'), + tokenDecimals, + rate, + tokenFxRateDecimals + ); + const expected = '13445088912'; + expect(rawAmount.toString()).to.eq(expected); + }); + + it(`should correctly return 'viewNumeraireAmount' values`, async () => { + const numerarieAmount = viewNumeraireAmount( + bnum('13445088912'), + tokenDecimals, + rate, + tokenFxRateDecimals + ); + const expected = '9999.999999'; + expect(numerarieAmount.toString()).to.eq(expected); + }); + }); + context('test swaps vs queryBatchSwap', () => { // Setup chain before(async function () { diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 49479e67..1b37a8a4 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -3,7 +3,7 @@ import { expect } from 'chai'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; import { ZERO, bnum } from '../src/utils/bignumber'; -import { PoolTypes, SwapTypes } from '../src'; +import { OldBigNumber, PoolTypes, SwapTypes } from '../src'; // Add new PoolType import { FxPool, FxPoolPairData } from '../src/pools/xaveFxPool/fxPool'; import { @@ -95,52 +95,12 @@ describe('Test for fxPools', () => { newPool.tokens[1].address // tokenOut ); - const reservesInNumeraire = poolBalancesToNumeraire(poolPairData); - const alphaValue = poolPairData.alpha.div(bnum(10).pow(18)); - const maxLimit = alphaValue - .plus(1) - .times(reservesInNumeraire._oGLiq) - .times(0.5); - - const maxLimitAmountForTokenIn = maxLimit.minus( - reservesInNumeraire.tokenInReservesInNumeraire - ); - - const maxLimitAmountForTokenOut = maxLimit.minus( - reservesInNumeraire.tokenOutReservesInNumeraire - ); - - const expectedLimitForTokenIn = viewRawAmount( - maxLimitAmountForTokenIn, - bnum(poolPairData.decimalsIn), - poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInfxOracleDecimals - ); - - const expectedLimitForTokenOut = viewRawAmount( - maxLimitAmountForTokenOut, - bnum(poolPairData.decimalsOut), - poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutfxOracleDecimals - ); - let amount = newPool.getLimitAmountSwap( poolPairData, SwapTypes.SwapExactIn ); - expect(amount.toString()).to.equals( - expectedLimitForTokenIn.toString() - ); - - amount = newPool.getLimitAmountSwap( - poolPairData, - SwapTypes.SwapExactOut - ); - - expect(amount.toString()).to.equals( - expectedLimitForTokenOut.toString() - ); + expect(amount.toString()).to.equals('355216.345225'); }); }); @@ -183,10 +143,15 @@ describe('Test for fxPools', () => { const spotPriceBeforeSwapValue = spotPriceBeforeSwap( ONE_NUMERAIRE, poolPairData - ).toNumber(); + ); - expect(spotPriceBeforeSwapValue.toFixed(9)).to.equals( - testCase.expectedSpotPriceBeforeSwap + expect( + spotPriceBeforeSwapValue + .decimalPlaces(9, OldBigNumber.ROUND_UP) + .toString() + ).to.equals( + testCase.expectedSpotPriceBeforeSwap, + 'spotPriceBeforeSwapValue' ); if (testCase.swapType === 'OriginSwap') { @@ -198,21 +163,26 @@ describe('Test for fxPools', () => { poolPairData, givenAmount ); - expect(amountOut).to.eq(ZERO); + expect(amountOut.toString()).to.eq( + '0', + 'amountOut' + ); } else { amountOut = newPool._exactTokenInForTokenOut( poolPairData, givenAmount ); - expect(amountOut.toNumber()).to.be.closeTo( - viewRawAmount( - bnum(testCase.expectedSwapOutput), - bnum(poolPairData.decimalsOut), - poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutfxOracleDecimals - ).toNumber(), - 10000 - ); // rounded off + + // this doesn't make much sense now + // expect(amountOut.toString()).to.be.equal( + // viewRawAmount( + // bnum(testCase.expectedSwapOutput), + // bnum(poolPairData.decimalsOut), + // poolPairData.tokenOutLatestFXPrice, + // poolPairData.tokenOutfxOracleDecimals + // ).toString(), + // '_exactTokenInForTokenOut vs rawAmount' + // ); const _spotPriceAfterSwapExactTokenInForTokenOut = newPool._spotPriceAfterSwapExactTokenInForTokenOut( @@ -221,28 +191,23 @@ describe('Test for fxPools', () => { ); expect( - Number( - _spotPriceAfterSwapExactTokenInForTokenOut - .toNumber() - .toFixed(9) - ) - ).to.be.closeTo( - Number(testCase.expectedSpotPriceAfterSwap), - 0.01 // adjusted for test 11 + _spotPriceAfterSwapExactTokenInForTokenOut + .decimalPlaces(9, OldBigNumber.ROUND_UP) + .toString() + ).to.equals( + testCase.expectedSpotPriceAfterSwap, + 'expectedSpotPriceAfterSwap' ); - const derivative = newPool - ._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + const derivative = + newPool._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( poolPairData, givenAmount - ) - .toNumber(); - - expect(derivative).to.be.closeTo( - Number( - testCase.expectedDerivativeSpotPriceAfterSwap - ), - 0.001 // adjustment + ); + + expect(derivative.toFixed(20)).to.be.equal( + testCase.expectedDerivativeSpotPriceAfterSwap, + 'derivative' ); } } else { @@ -254,22 +219,22 @@ describe('Test for fxPools', () => { poolPairData, givenAmount ); - expect(amountIn).to.eq(ZERO); + expect(amountIn.toString()).to.eq('0'); } else { amountIn = newPool._tokenInForExactTokenOut( poolPairData, givenAmount ); - expect(amountIn.toNumber()).to.be.closeTo( - viewRawAmount( - bnum(testCase.expectedSwapOutput), - bnum(poolPairData.decimalsIn), - poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInfxOracleDecimals - ).toNumber(), - 2000000 - ); // rounded off, decimal adjustment + // expect(amountIn.toString()).to.be.eq( + // viewRawAmount( + // bnum(testCase.expectedSwapOutput), + // bnum(poolPairData.decimalsIn), + // poolPairData.tokenInLatestFXPrice, + // poolPairData.tokenInfxOracleDecimals + // ).toString(), + // '_tokenInForExactTokenOut vs rawAmount' + // ); const _spotPriceAfterSwapTokenInForExactTokenOut = newPool._spotPriceAfterSwapTokenInForExactTokenOut( @@ -278,28 +243,23 @@ describe('Test for fxPools', () => { ); expect( - Number( - _spotPriceAfterSwapTokenInForExactTokenOut - .toNumber() - .toFixed(9) - ) - ).to.be.closeTo( - Number(testCase.expectedSpotPriceAfterSwap), - 0.00001 // adjusted for test number 11 + _spotPriceAfterSwapTokenInForExactTokenOut + .decimalPlaces(9, OldBigNumber.ROUND_UP) + .toString() + ).to.equal( + testCase.expectedSpotPriceAfterSwap, + 'expectedSpotPriceAfterSwap' ); - const derivative = newPool - ._derivativeSpotPriceAfterSwapTokenInForExactTokenOut( + const derivative = + newPool._derivativeSpotPriceAfterSwapTokenInForExactTokenOut( poolPairData, givenAmount - ) - .toNumber(); - - expect(derivative).to.be.closeTo( - Number( - testCase.expectedDerivativeSpotPriceAfterSwap - ), - 0.001 // adjustment + ); + + expect(derivative.toFixed(20)).to.be.equal( + testCase.expectedDerivativeSpotPriceAfterSwap, + 'derivative' ); } } From 6e350425b0228ed8520f02a867cdd64d3acc7d57 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 14 Jun 2023 13:35:43 +0700 Subject: [PATCH 16/56] =?UTF-8?q?=F0=9F=8E=A3=20added=20tests=20for=20expe?= =?UTF-8?q?ctedSwapOutput=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 1 - test/testData/fxPool/fxPoolTestCases.json | 20 ++++++++++---------- test/xaveFxPool.spec.ts | 5 +++++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 63598af6..b6356d1b 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -280,7 +280,6 @@ export const viewNumeraireAmount = ( .div(bnum(10).pow(fxOracleDecimals)) .integerValue(OldBigNumber.ROUND_DOWN) .div(bnum(10).pow(tokenDecimals)); - // 167_922_339.1836 }; // Curve Math diff --git a/test/testData/fxPool/fxPoolTestCases.json b/test/testData/fxPool/fxPoolTestCases.json index 61dc3226..12f17590 100644 --- a/test/testData/fxPool/fxPoolTestCases.json +++ b/test/testData/fxPool/fxPoolTestCases.json @@ -8,7 +8,7 @@ "tokenOut": "XSGD", "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", - "expectedSwapOutput": "99950.00", + "expectedSwapOutput": "134383.663677", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, { @@ -20,7 +20,7 @@ "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", - "expectedSwapOutput": "100050", + "expectedSwapOutput": "134518.114568", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, { @@ -32,7 +32,7 @@ "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", - "expectedSwapOutput": "74339.41", + "expectedSwapOutput": "74339.411699", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, { @@ -44,7 +44,7 @@ "tokenOut": "XSGD", "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", - "expectedSwapOutput": "74413.79", + "expectedSwapOutput": "74413.7883", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, { @@ -56,7 +56,7 @@ "tokenOut": "XSGD", "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.73876219", - "expectedSwapOutput": "198654.47", + "expectedSwapOutput": "267092.696444", "expectedDerivativeSpotPriceAfterSwap": "0.006230783064" }, { @@ -68,7 +68,7 @@ "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", - "expectedSwapOutput": "200100", + "expectedSwapOutput": "269036.229136", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, { @@ -80,7 +80,7 @@ "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", - "expectedSwapOutput": "200716.41", + "expectedSwapOutput": "200715.668195", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, { @@ -92,7 +92,7 @@ "tokenOut": "XSGD", "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.73827378", - "expectedSwapOutput": "202310.75", + "expectedSwapOutput": "202308.166563", "expectedDerivativeSpotPriceAfterSwap": "0.006887781537" }, @@ -117,7 +117,7 @@ "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.7418224557", - "expectedSwapOutput": "451178.98", + "expectedSwapOutput": "606613.029263", "expectedDerivativeSpotPriceAfterSwap": "0.002114169664" }, { @@ -129,7 +129,7 @@ "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.743394117", "expectedSpotPriceAfterSwap": "0.743394117", - "expectedSwapOutput": "452358.00907339100", + "expectedSwapOutput": "452358.009073", "expectedDerivativeSpotPriceAfterSwap": "0.002453086836" }, { diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 1b37a8a4..31bd17a4 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -14,6 +14,8 @@ import { _spotPriceAfterSwapExactTokenInForTokenOut, } from '../src/pools/xaveFxPool/fxPoolMath'; +const debug = require('debug')('xave'); + // Add new pool test data in Subgraph Schema format import testPools from './testData/fxPool/fxPool.json'; import testCases from './testData/fxPool/fxPoolTestCases.json'; @@ -173,6 +175,8 @@ describe('Test for fxPools', () => { givenAmount ); + expect(testCase.expectedSwapOutput).to.be.equal(amountOut.toString(), 'amountOut vs. expectedSwapOutput'); + // this doesn't make much sense now // expect(amountOut.toString()).to.be.equal( // viewRawAmount( @@ -225,6 +229,7 @@ describe('Test for fxPools', () => { poolPairData, givenAmount ); + expect(testCase.expectedSwapOutput).to.be.equal(amountIn.toString(), 'amountIn vs. expectedSwapOutput'); // expect(amountIn.toString()).to.be.eq( // viewRawAmount( From 4e736d8d75544ed87ae42abba4405c7e658bfed2 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 14 Jun 2023 13:43:31 +0700 Subject: [PATCH 17/56] =?UTF-8?q?=F0=9F=8E=A4=20minor=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/xaveFxPool.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 31bd17a4..2e817b08 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -14,8 +14,6 @@ import { _spotPriceAfterSwapExactTokenInForTokenOut, } from '../src/pools/xaveFxPool/fxPoolMath'; -const debug = require('debug')('xave'); - // Add new pool test data in Subgraph Schema format import testPools from './testData/fxPool/fxPool.json'; import testCases from './testData/fxPool/fxPoolTestCases.json'; @@ -175,7 +173,7 @@ describe('Test for fxPools', () => { givenAmount ); - expect(testCase.expectedSwapOutput).to.be.equal(amountOut.toString(), 'amountOut vs. expectedSwapOutput'); + expect(amountOut.toString()).to.be.equal(testCase.expectedSwapOutput, 'amountOut vs. expectedSwapOutput'); // this doesn't make much sense now // expect(amountOut.toString()).to.be.equal( @@ -229,7 +227,7 @@ describe('Test for fxPools', () => { poolPairData, givenAmount ); - expect(testCase.expectedSwapOutput).to.be.equal(amountIn.toString(), 'amountIn vs. expectedSwapOutput'); + expect(amountIn.toString()).to.be.equal(testCase.expectedSwapOutput, 'amountIn vs. expectedSwapOutput'); // expect(amountIn.toString()).to.be.eq( // viewRawAmount( From ba76227981261c011a0d054108cdd64c220b7ffc Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 14 Jun 2023 19:11:17 +0700 Subject: [PATCH 18/56] =?UTF-8?q?=E2=9A=93=20added=20=5FinHigherPrecision?= =?UTF-8?q?=20function=20to=20workaround=20the=20SOR=20limitation=20to=20o?= =?UTF-8?q?nly=2018=20decimals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/big-number.ts | 26 -- src/pools/xaveFxPool/fxPool.ts | 98 ++++--- src/pools/xaveFxPool/parseFixedCurveParam.ts | 37 +++ test/xaveFxPoolBug.integration.spec.ts | 255 +++++++++++++++++++ 4 files changed, 350 insertions(+), 66 deletions(-) delete mode 100644 src/pools/xaveFxPool/big-number.ts create mode 100644 src/pools/xaveFxPool/parseFixedCurveParam.ts create mode 100644 test/xaveFxPoolBug.integration.spec.ts diff --git a/src/pools/xaveFxPool/big-number.ts b/src/pools/xaveFxPool/big-number.ts deleted file mode 100644 index d48cccbd..00000000 --- a/src/pools/xaveFxPool/big-number.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { BigNumber } from 'bignumber.js'; - -// needed in order for the curve params (epsilon, alpha etc) to -// have enough precision to as the ABDK 64.64 fixed point library -// in the smart contract -BigNumber.config({ - EXPONENTIAL_AT: [-100, 100], - ROUNDING_MODE: 1, - DECIMAL_PLACES: 36, -}); - -export const ZERO = bnum(0); -export const ONE = bnum(1); -export const INFINITY = bnum('Infinity'); - -export function scale(input: BigNumber, decimalPlaces: number): BigNumber { - const scalePow = new BigNumber(decimalPlaces.toString()); - const scaleMul = new BigNumber(10).pow(scalePow); - return input.times(scaleMul); -} - -export function bnum(val: string | number | BigNumber): BigNumber { - return new BigNumber(val.toString()); -} - -export { BigNumber }; diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index da668e65..3c93a5e4 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -1,7 +1,9 @@ import { getAddress } from '@ethersproject/address'; import { BigNumber, formatFixed, parseFixed } from '@ethersproject/bignumber'; import { Zero } from '@ethersproject/constants'; -import { BigNumber as OldBigNumber, ZERO, bnum } from './big-number'; +import { BigNumber as OldBigNumber, ZERO, bnum } from '../../utils/bignumber'; + +import { parseFixedCurveParam } from './parseFixedCurveParam'; import { isSameAddress } from '../../utils'; import { universalNormalizedLiquidity } from '../liquidity'; import { @@ -28,38 +30,6 @@ type FxPoolToken = Pick< 'address' | 'balance' | 'decimals' | 'token' >; -/** - * Replicates the conversion operation to 64.64 fixed point numbers (ABDK library) - * that occurs in the smart contract. This is done to replicate the loss of precision - * from the smart contract. - * - * For example: in 1e18 decimals, when converting _epsilon_ `0.0015` from `uint256` - * to a 64.64 fixed point number (`(_epsilon + 1).divu(1e18)`) there is a loss - * of precision. In 64.64 fixed point, epsilon is stored as 0.001500000000000000953. - * This is the value that is used in calculations in the smart contract. - * - * When converted from 64.64 fixed point back to `uint256` the value is - * 0.001500000000000000 which is the same as the original value of 0.0015. - * This is what the graph is seeing. - * - * This function is used to replicate the same loss of precision that occurs - * in the smart contract so that we work with an epsilon value of - * 0.001500000000000000953 instead of 0.0015. - * - * @param param any of the pool's curve parameters like alpha, beta, lambda, delta, epsilon - * @returns OldBigNumber with the same loss of precision as the smart contract - */ -const parseFixedCurveParam = (param: string): OldBigNumber => { - const param64 = - ((((BigInt(parseFixed(param, 18).toString()) + 1n) << 64n) / - 10n ** 18n) * - 10n ** 36n) >> - 64n; - return bnum(param64.toString()) - .div(bnum(10).pow(18)) - .decimalPlaces(3, OldBigNumber.ROUND_UP); -}; - export type FxPoolPairData = PoolPairBase & { alpha: OldBigNumber; beta: OldBigNumber; @@ -229,6 +199,17 @@ export class FxPool implements PoolBase { getLimitAmountSwap( poolPairData: FxPoolPairData, swapType: SwapTypes + ): OldBigNumber { + return this._inHigherPrecision( + this._getLimitAmountSwap, + poolPairData, + swapType + ); + } + + _getLimitAmountSwap( + poolPairData: FxPoolPairData, + swapType: SwapTypes ): OldBigNumber { try { const parsedReserves = poolBalancesToNumeraire(poolPairData); @@ -286,8 +267,12 @@ export class FxPool implements PoolBase { amount: OldBigNumber ): OldBigNumber { try { - return _exactTokenInForTokenOut(amount, poolPairData); - } catch { + return this._inHigherPrecision( + _exactTokenInForTokenOut, + amount, + poolPairData + ); + } catch (e) { return ZERO; } } @@ -297,7 +282,11 @@ export class FxPool implements PoolBase { amount: OldBigNumber ): OldBigNumber { try { - return _tokenInForExactTokenOut(amount, poolPairData); + return this._inHigherPrecision( + _tokenInForExactTokenOut, + amount, + poolPairData + ); } catch { return ZERO; } @@ -308,7 +297,8 @@ export class FxPool implements PoolBase { amount: OldBigNumber ): OldBigNumber { try { - return _spotPriceAfterSwapExactTokenInForTokenOut( + return this._inHigherPrecision( + _spotPriceAfterSwapExactTokenInForTokenOut, poolPairData, amount ); @@ -322,7 +312,8 @@ export class FxPool implements PoolBase { amount: OldBigNumber ): OldBigNumber { try { - return _spotPriceAfterSwapTokenInForExactTokenOut( + return this._inHigherPrecision( + _spotPriceAfterSwapTokenInForExactTokenOut, poolPairData, amount ); @@ -336,7 +327,8 @@ export class FxPool implements PoolBase { amount: OldBigNumber ): OldBigNumber { try { - return _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + return this._inHigherPrecision( + _derivativeSpotPriceAfterSwapExactTokenInForTokenOut, amount, poolPairData ); @@ -350,7 +342,8 @@ export class FxPool implements PoolBase { amount: OldBigNumber ): OldBigNumber { try { - return _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( + return this._inHigherPrecision( + _derivativeSpotPriceAfterSwapTokenInForExactTokenOut, amount, poolPairData ); @@ -358,4 +351,29 @@ export class FxPool implements PoolBase { return ZERO; } } + + /** + * Runs the given function with the BigNumber config set to 36 decimals. + * This is needed since in the Solidity code we use 64.64 fixed point numbers + * for the curve math operations. This makes the SOR default of 18 decimals + * not enough. + * + * @param funcName + * @param args + * @returns + */ + _inHigherPrecision(funcName: Function, ...args) { + const prevDecimalPlaces = OldBigNumber.config({}).DECIMAL_PLACES; + OldBigNumber.config({ + DECIMAL_PLACES: 36, + }); + + const val = funcName.apply(this, args); + + OldBigNumber.config({ + DECIMAL_PLACES: prevDecimalPlaces, + }); + + return val; + } } diff --git a/src/pools/xaveFxPool/parseFixedCurveParam.ts b/src/pools/xaveFxPool/parseFixedCurveParam.ts new file mode 100644 index 00000000..1e5af7c7 --- /dev/null +++ b/src/pools/xaveFxPool/parseFixedCurveParam.ts @@ -0,0 +1,37 @@ +import { BigNumber as OldBigNumber, bnum, scale } from '../../utils/bignumber'; +import { parseFixed } from '@ethersproject/bignumber'; + +/** + * Replicates the conversion operation to 64.64 fixed point numbers (ABDK library) + * that occurs in the smart contract. This is done to replicate the loss of precision + * from the smart contract. + * + * For example: in 1e18 decimals, when converting _epsilon_ `0.0015` from `uint256` + * to a 64.64 fixed point number (`(_epsilon + 1).divu(1e18)`) there is a loss + * of precision. In 64.64 fixed point, epsilon is stored as 0.001500000000000000953. + * This is the value that is used in calculations in the smart contract. + * + * When converted from 64.64 fixed point back to `uint256` the value is + * 0.001500000000000000 which is the same as the original value of 0.0015. + * This is what the graph is showing. + * + * This function is used to replicate the same loss of precision that occurs + * in the smart contract so that we work with an epsilon value of + * 0.001500000000000000953 instead of 0.0015. + * + * @param param any of the pool's curve parameters like alpha, beta, lambda, delta, epsilon + * @returns OldBigNumber with the same loss of precision as the smart contract + */ +export const parseFixedCurveParam = (param: string): OldBigNumber => { + const param64 = + ((((BigInt(parseFixed(param, 18).toString()) + 1n) << 64n) / + 10n ** 18n) * + 10n ** 36n) >> + 64n; + + const val = bnum(param64.toString()) + .div(bnum(10).pow(18)) + .decimalPlaces(3, OldBigNumber.ROUND_UP); + + return val; +}; diff --git a/test/xaveFxPoolBug.integration.spec.ts b/test/xaveFxPoolBug.integration.spec.ts new file mode 100644 index 00000000..2aa530f9 --- /dev/null +++ b/test/xaveFxPoolBug.integration.spec.ts @@ -0,0 +1,255 @@ +// yarn test:only test/xaveFxPoolBug.integration.spec.ts +import dotenv from 'dotenv'; +import { expect } from 'chai'; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { parseFixed, BigNumber } from '@ethersproject/bignumber'; +import { AddressZero } from '@ethersproject/constants'; +import { Vault__factory } from '@balancer-labs/typechain'; + +import { SOR, SubgraphPoolBase, SwapTypes } from '../src'; + +import { ADDRESSES, Network, vaultAddr } from './testScripts/constants'; +import { setUp } from './testScripts/utils'; + +dotenv.config(); + +let sor: SOR; +const networkId = Network.MAINNET; +const jsonRpcUrl = process.env.ALCHEMY_URL; +const rpcUrl = 'http://127.0.0.1:8545'; +const provider = new JsonRpcProvider(rpcUrl, networkId); +const blocknumber = 17129117; +const gasPrice = BigNumber.from('14000000000'); +const maxPools = 4; + +const stablePool: SubgraphPoolBase[] = [ + { + id: '0x79c58f70905f734641735bc61e45c19dd9ad60bc0000000000000000000004e7', + address: '0x79c58f70905f734641735bc61e45c19dd9ad60bc', + poolType: 'ComposableStable', + swapFee: '0.00005', + totalShares: '7651512.416461224268218705', + tokens: [ + { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + balance: '2701150.521361511914031515', + decimals: 18, + weight: null, + priceRate: '1', + token: { + latestFXPrice: '0.9999', + fxOracleDecimals: 8, + }, + }, + { + address: '0x79c58f70905f734641735bc61e45c19dd9ad60bc', + balance: '2596148429287188.592625712831077281', + decimals: 18, + weight: null, + priceRate: '1', + token: { + latestFXPrice: undefined, + fxOracleDecimals: 18, + }, + }, + { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + balance: '2875806.872139', + decimals: 6, + weight: null, + priceRate: '1', + token: { + latestFXPrice: '1.00012638', + fxOracleDecimals: 8, + }, + }, + { + address: '0xdac17f958d2ee523a2206206994597c13d831ec7', + balance: '2076136.817258', + decimals: 6, + weight: null, + priceRate: '1', + token: { + latestFXPrice: undefined, + fxOracleDecimals: 8, + }, + }, + ], + tokensList: [ + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0x79c58f70905f734641735bc61e45c19dd9ad60bc', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + ], + totalWeight: '0', + amp: '2000', + expiryTime: undefined, + unitSeconds: undefined, + principalToken: undefined, + baseToken: undefined, + swapEnabled: true, + wrappedIndex: undefined, + mainIndex: undefined, + lowerTarget: undefined, + upperTarget: undefined, + sqrtAlpha: undefined, + sqrtBeta: undefined, + root3Alpha: undefined, + alpha: undefined, + beta: undefined, + c: undefined, + s: undefined, + lambda: undefined, + delta: undefined, + epsilon: undefined, + tauAlphaX: undefined, + tauAlphaY: undefined, + tauBetaX: undefined, + tauBetaY: undefined, + u: undefined, + v: undefined, + w: undefined, + z: undefined, + dSq: undefined, + }, +]; + +const xavePool: SubgraphPoolBase[] = [ + { + id: '0x66bb9d104c55861feb3ec3559433f01f6373c9660002000000000000000003cf', + address: '0x66bb9d104c55861feb3ec3559433f01f6373c966', + poolType: 'FX', + swapFee: '0', + totalShares: '361.723192785679497072', + tokens: [ + { + address: '0x6b175474e89094c44da98b954eedeac495271d0f', + balance: '259.784438376175039967', + decimals: 18, + weight: null, + priceRate: '1', + token: { + latestFXPrice: '0.99999000', + fxOracleDecimals: 8, + }, + }, + { + address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + balance: '105.392447', + decimals: 6, + weight: null, + priceRate: '1', + token: { + latestFXPrice: '1.00012638', + fxOracleDecimals: 8, + }, + }, + ], + tokensList: [ + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + ], + totalWeight: '0', + amp: undefined, + expiryTime: undefined, + unitSeconds: undefined, + principalToken: undefined, + baseToken: undefined, + swapEnabled: true, + wrappedIndex: undefined, + mainIndex: undefined, + lowerTarget: undefined, + upperTarget: undefined, + sqrtAlpha: undefined, + sqrtBeta: undefined, + root3Alpha: undefined, + alpha: '0.8', + beta: '0.42', + c: undefined, + s: undefined, + lambda: '0.3', + delta: '0.3', + epsilon: '0.0015', + tauAlphaX: undefined, + tauAlphaY: undefined, + tauBetaX: undefined, + tauBetaY: undefined, + u: undefined, + v: undefined, + w: undefined, + z: undefined, + dSq: undefined, + }, +]; + +const tokenIn = ADDRESSES[Network.MAINNET].DAI.address; +const tokenOut = ADDRESSES[Network.MAINNET].USDC.address; +const swapType = SwapTypes.SwapExactIn; +const swapAmount = parseFixed('10', 18); + +describe('Replicating FX Pool Issue, DAI>USDC', () => { + context('Stable pool only', () => { + before(async function () { + sor = await setUp( + networkId, + provider, + stablePool, + jsonRpcUrl as string, + blocknumber + ); + + await sor.fetchPools(); + }); + + it('should return swap', async () => { + await testSwap(); + }); + }); + + context('Stable + FX pools', () => { + before(async function () { + sor = await setUp( + networkId, + provider, + [...stablePool, ...xavePool], + jsonRpcUrl as string, + blocknumber, + ); + + await sor.fetchPools(); + }); + + it('should return swap', async () => { + await testSwap(); + }); + }); +}); + +async function testSwap(): Promise { + const vault = Vault__factory.connect(vaultAddr, provider); + + const funds = { + sender: AddressZero, + recipient: AddressZero, + fromInternalBalance: false, + toInternalBalance: false, + }; + const swapInfo = await sor.getSwaps( + tokenIn, + tokenOut, + swapType, + swapAmount, + { gasPrice, maxPools } + ); + + const queryResult = await vault.callStatic.queryBatchSwap( + swapType, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + + expect(swapInfo.returnAmount.gt(0)).to.be.true; + expect(queryResult[0].toString()).to.eq(swapInfo.swapAmount.toString()); + expect(queryResult[1].abs().toString()).to.eq(swapInfo.returnAmount.toString()); +} From 820bf11f7d9427af7c9323fa05774bb52bb81b8c Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 14 Jun 2023 19:40:21 +0700 Subject: [PATCH 19/56] =?UTF-8?q?=F0=9F=8F=88=20lint=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 3 ++- src/pools/xaveFxPool/parseFixedCurveParam.ts | 2 +- test/xaveFxPool.integration.spec.ts | 4 ---- test/xaveFxPool.spec.ts | 18 +++++++++++------- test/xaveFxPoolBug.integration.spec.ts | 14 ++++++++------ 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 3c93a5e4..27426422 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -362,7 +362,8 @@ export class FxPool implements PoolBase { * @param args * @returns */ - _inHigherPrecision(funcName: Function, ...args) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + _inHigherPrecision(funcName: Function, ...args: any[]): OldBigNumber { const prevDecimalPlaces = OldBigNumber.config({}).DECIMAL_PLACES; OldBigNumber.config({ DECIMAL_PLACES: 36, diff --git a/src/pools/xaveFxPool/parseFixedCurveParam.ts b/src/pools/xaveFxPool/parseFixedCurveParam.ts index 1e5af7c7..5d1f582d 100644 --- a/src/pools/xaveFxPool/parseFixedCurveParam.ts +++ b/src/pools/xaveFxPool/parseFixedCurveParam.ts @@ -1,4 +1,4 @@ -import { BigNumber as OldBigNumber, bnum, scale } from '../../utils/bignumber'; +import { BigNumber as OldBigNumber, bnum } from '../../utils/bignumber'; import { parseFixed } from '@ethersproject/bignumber'; /** diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index 20658a74..4fb48f31 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -10,12 +10,8 @@ import { AddressZero } from '@ethersproject/constants'; import { setUp } from './testScripts/utils'; import { - ALMOST_ZERO, - poolBalancesToNumeraire, - spotPriceBeforeSwap, viewRawAmount, viewNumeraireAmount, - _spotPriceAfterSwapExactTokenInForTokenOut, } from '../src/pools/xaveFxPool/fxPoolMath'; /* * Testing Notes: diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 2e817b08..141709e8 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -1,16 +1,14 @@ // yarn test:only test/xaveFxPool.spec.ts import { expect } from 'chai'; -import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import { ZERO, bnum } from '../src/utils/bignumber'; +import { BigNumber } from '@ethersproject/bignumber'; +import { bnum } from '../src/utils/bignumber'; import { OldBigNumber, PoolTypes, SwapTypes } from '../src'; // Add new PoolType import { FxPool, FxPoolPairData } from '../src/pools/xaveFxPool/fxPool'; import { ALMOST_ZERO, - poolBalancesToNumeraire, spotPriceBeforeSwap, - viewRawAmount, _spotPriceAfterSwapExactTokenInForTokenOut, } from '../src/pools/xaveFxPool/fxPoolMath'; @@ -95,7 +93,7 @@ describe('Test for fxPools', () => { newPool.tokens[1].address // tokenOut ); - let amount = newPool.getLimitAmountSwap( + const amount = newPool.getLimitAmountSwap( poolPairData, SwapTypes.SwapExactIn ); @@ -173,7 +171,10 @@ describe('Test for fxPools', () => { givenAmount ); - expect(amountOut.toString()).to.be.equal(testCase.expectedSwapOutput, 'amountOut vs. expectedSwapOutput'); + expect(amountOut.toString()).to.be.equal( + testCase.expectedSwapOutput, + 'amountOut vs. expectedSwapOutput' + ); // this doesn't make much sense now // expect(amountOut.toString()).to.be.equal( @@ -227,7 +228,10 @@ describe('Test for fxPools', () => { poolPairData, givenAmount ); - expect(amountIn.toString()).to.be.equal(testCase.expectedSwapOutput, 'amountIn vs. expectedSwapOutput'); + expect(amountIn.toString()).to.be.equal( + testCase.expectedSwapOutput, + 'amountIn vs. expectedSwapOutput' + ); // expect(amountIn.toString()).to.be.eq( // viewRawAmount( diff --git a/test/xaveFxPoolBug.integration.spec.ts b/test/xaveFxPoolBug.integration.spec.ts index 2aa530f9..7f7ad7a4 100644 --- a/test/xaveFxPoolBug.integration.spec.ts +++ b/test/xaveFxPoolBug.integration.spec.ts @@ -39,7 +39,7 @@ const stablePool: SubgraphPoolBase[] = [ token: { latestFXPrice: '0.9999', fxOracleDecimals: 8, - }, + }, }, { address: '0x79c58f70905f734641735bc61e45c19dd9ad60bc', @@ -50,7 +50,7 @@ const stablePool: SubgraphPoolBase[] = [ token: { latestFXPrice: undefined, fxOracleDecimals: 18, - }, + }, }, { address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', @@ -61,7 +61,7 @@ const stablePool: SubgraphPoolBase[] = [ token: { latestFXPrice: '1.00012638', fxOracleDecimals: 8, - }, + }, }, { address: '0xdac17f958d2ee523a2206206994597c13d831ec7', @@ -131,7 +131,7 @@ const xavePool: SubgraphPoolBase[] = [ token: { latestFXPrice: '0.99999000', fxOracleDecimals: 8, - }, + }, }, { address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', @@ -213,7 +213,7 @@ describe('Replicating FX Pool Issue, DAI>USDC', () => { provider, [...stablePool, ...xavePool], jsonRpcUrl as string, - blocknumber, + blocknumber ); await sor.fetchPools(); @@ -251,5 +251,7 @@ async function testSwap(): Promise { expect(swapInfo.returnAmount.gt(0)).to.be.true; expect(queryResult[0].toString()).to.eq(swapInfo.swapAmount.toString()); - expect(queryResult[1].abs().toString()).to.eq(swapInfo.returnAmount.toString()); + expect(queryResult[1].abs().toString()).to.eq( + swapInfo.returnAmount.toString() + ); } From adc7836622f2a7fc44cb220943cf40967f7d903c Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 14 Jun 2023 20:09:54 +0700 Subject: [PATCH 20/56] =?UTF-8?q?=F0=9F=8D=81=20added=20xaveFxPool.math.sp?= =?UTF-8?q?ec.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/xaveFxPool.math.spec.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 test/xaveFxPool.math.spec.ts diff --git a/test/xaveFxPool.math.spec.ts b/test/xaveFxPool.math.spec.ts new file mode 100644 index 00000000..348fef05 --- /dev/null +++ b/test/xaveFxPool.math.spec.ts @@ -0,0 +1,36 @@ +// TS_NODE_PROJECT='tsconfig.testing.json' npx mocha -r ts-node/register test/xaveFxPool.math.spec.ts +require('dotenv').config(); +import { expect } from 'chai'; +import { bnum } from '../src/utils/bignumber'; + +import { + viewRawAmount, + viewNumeraireAmount, +} from '../src/pools/xaveFxPool/fxPoolMath'; + +context('test fxMath functions', () => { + const tokenDecimals = bnum(6); + const tokenFxRateDecimals = bnum(8); + const rate = bnum('74376600'); // 0.743766 + it(`should correctly return 'viewRawAmount'`, async () => { + const rawAmount = viewRawAmount( + bnum('10000'), + tokenDecimals, + rate, + tokenFxRateDecimals + ); + const expected = '13445088912'; + expect(rawAmount.toString()).to.eq(expected); + }); + + it(`should correctly return 'viewNumeraireAmount' values`, async () => { + const numerarieAmount = viewNumeraireAmount( + bnum('13445088912'), + tokenDecimals, + rate, + tokenFxRateDecimals + ); + const expected = '9999.999999'; + expect(numerarieAmount.toString()).to.eq(expected); + }); +}); From c33c58cd044dc6cc9beb3b165de3642d40b16fb0 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 14 Jun 2023 20:12:08 +0700 Subject: [PATCH 21/56] =?UTF-8?q?=F0=9F=8C=B3=20removed=20debug=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index ed3ea1ce..86922259 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,6 @@ "@typescript-eslint/parser": "^4.29.2", "bignumber.js": "^9.0.1", "chai": "^4.2.0", - "debug": "^4.3.4", "dotenv": "^8.2.0", "eslint": "^7.32.0", "eslint-plugin-mocha-no-only": "^1.1.1", From e464e2403d3c687e475741edc334469b285f3117 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 14 Jun 2023 20:13:24 +0700 Subject: [PATCH 22/56] =?UTF-8?q?=F0=9F=8C=B3=20removed=20debug=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index e3fa7ed8..d910a209 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2163,7 +2163,7 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" -debug@4, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@^4.3.3: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== From 19ad8bea8cab994f63e579845412c89bd0e56ee8 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 14 Jun 2023 20:26:37 +0700 Subject: [PATCH 23/56] =?UTF-8?q?=F0=9F=9A=97=20removed=20FXPoolMath=20tes?= =?UTF-8?q?ts=20from=20integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 7 ----- src/pools/xaveFxPool/parseFixedCurveParam.ts | 19 +++++------ test/xaveFxPool.integration.spec.ts | 33 +------------------- test/xaveFxPool.math.spec.ts | 1 + 4 files changed, 9 insertions(+), 51 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index b6356d1b..28b86f98 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -649,13 +649,6 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( // used that function with a 0 amount to get a market spot price for the pool // which is used in front end display. - // return amount.isZero() - // ? spotPriceBeforeSwap(amount, poolPairData) - // : bnum( - // (Math.abs(outputAmount * (bnum(1).minus(epsilon))) / - // Math.abs(targetAmountInNumeraire)) * - // currentRate - // ); return amount.isZero() ? spotPriceBeforeSwap(amount, poolPairData) : outputAmount diff --git a/src/pools/xaveFxPool/parseFixedCurveParam.ts b/src/pools/xaveFxPool/parseFixedCurveParam.ts index 5d1f582d..e6ef9265 100644 --- a/src/pools/xaveFxPool/parseFixedCurveParam.ts +++ b/src/pools/xaveFxPool/parseFixedCurveParam.ts @@ -4,20 +4,15 @@ import { parseFixed } from '@ethersproject/bignumber'; /** * Replicates the conversion operation to 64.64 fixed point numbers (ABDK library) * that occurs in the smart contract. This is done to replicate the loss of precision - * from the smart contract. + * that the fixed point number conversion exhibits. * - * For example: in 1e18 decimals, when converting _epsilon_ `0.0015` from `uint256` - * to a 64.64 fixed point number (`(_epsilon + 1).divu(1e18)`) there is a loss - * of precision. In 64.64 fixed point, epsilon is stored as 0.001500000000000000953. - * This is the value that is used in calculations in the smart contract. + * For example: + * - `uint256(0.0005 * 1e18).divu(1e18)` is 9223372036854775 which is 0.000499999999999999956 + * - `uint256(0.0005 * 1e18 + 1).divu(1e18)` is 9223372036854794 which is 0.00050000000000000099 * - * When converted from 64.64 fixed point back to `uint256` the value is - * 0.001500000000000000 which is the same as the original value of 0.0015. - * This is what the graph is showing. - * - * This function is used to replicate the same loss of precision that occurs - * in the smart contract so that we work with an epsilon value of - * 0.001500000000000000953 instead of 0.0015. + * Most but one of the parameters (`delta`) use the formula `uint256(0.0005 * 1e18 + 1).divu(1e18)` + * when converting to fixed point precision. This is the value that is used in calculations + * in the smart contract. * * @param param any of the pool's curve parameters like alpha, beta, lambda, delta, epsilon * @returns OldBigNumber with the same loss of precision as the smart contract diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index 4fb48f31..f6dbf958 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -1,7 +1,7 @@ // yarn test:only test/xaveFxPool.integration.spec.ts import dotenv from 'dotenv'; import { JsonRpcProvider } from '@ethersproject/providers'; -import { bnum, PoolFilter, SOR, SubgraphPoolBase, SwapTypes } from '../src'; +import { PoolFilter, SOR, SubgraphPoolBase, SwapTypes } from '../src'; import { ADDRESSES, Network, vaultAddr } from './testScripts/constants'; import { parseFixed } from '@ethersproject/bignumber'; import { expect } from 'chai'; @@ -9,10 +9,6 @@ import { Vault__factory } from '@balancer-labs/typechain'; import { AddressZero } from '@ethersproject/constants'; import { setUp } from './testScripts/utils'; -import { - viewRawAmount, - viewNumeraireAmount, -} from '../src/pools/xaveFxPool/fxPoolMath'; /* * Testing Notes: * - Add infura api key on .env @@ -80,33 +76,6 @@ const xaveFxPoolDAI_USDC_MAINNET: SubgraphPoolBase = { const test = 'FX' in PoolFilter; describe('xaveFxPool: DAI-USDC integration tests', () => { - context('test fxMath functions', () => { - const tokenDecimals = bnum(6); - const tokenFxRateDecimals = bnum(8); - const rate = bnum('74376600'); // 0.743766 - it(`should correctly return 'viewRawAmount'`, async () => { - const rawAmount = viewRawAmount( - bnum('10000'), - tokenDecimals, - rate, - tokenFxRateDecimals - ); - const expected = '13445088912'; - expect(rawAmount.toString()).to.eq(expected); - }); - - it(`should correctly return 'viewNumeraireAmount' values`, async () => { - const numerarieAmount = viewNumeraireAmount( - bnum('13445088912'), - tokenDecimals, - rate, - tokenFxRateDecimals - ); - const expected = '9999.999999'; - expect(numerarieAmount.toString()).to.eq(expected); - }); - }); - context('test swaps vs queryBatchSwap', () => { // Setup chain before(async function () { diff --git a/test/xaveFxPool.math.spec.ts b/test/xaveFxPool.math.spec.ts index 348fef05..e9b58f4d 100644 --- a/test/xaveFxPool.math.spec.ts +++ b/test/xaveFxPool.math.spec.ts @@ -1,4 +1,5 @@ // TS_NODE_PROJECT='tsconfig.testing.json' npx mocha -r ts-node/register test/xaveFxPool.math.spec.ts +// eslint-disable-next-line @typescript-eslint/no-var-requires require('dotenv').config(); import { expect } from 'chai'; import { bnum } from '../src/utils/bignumber'; From 49094af2a78a659003ba9845ba9c34ea275ca9ef Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 14 Jun 2023 20:28:11 +0700 Subject: [PATCH 24/56] =?UTF-8?q?=F0=9F=90=99=20removed=20code=20change=20?= =?UTF-8?q?from=20`src/pools/index.ts`=20(oops)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pools/index.ts b/src/pools/index.ts index b2207a58..45db5f49 100644 --- a/src/pools/index.ts +++ b/src/pools/index.ts @@ -98,7 +98,6 @@ export function parseNewPool( } } catch (err) { console.error(`parseNewPool: ${err.message}`); - throw err; return undefined; } return newPool; From 09253c67b22510bbcf32daed1fe0fc74352620d3 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Thu, 15 Jun 2023 12:54:12 +0700 Subject: [PATCH 25/56] =?UTF-8?q?=F0=9F=8D=B3=20enabled=20FX=20pool=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 146c660a..a24029a8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -212,7 +212,7 @@ export enum PoolFilter { SiloLinear = 'SiloLinear', TetuLinear = 'TetuLinear', YearnLinear = 'YearnLinear', - // FX = 'FX', + FX = 'FX', } export interface PoolBase { From a9653438deb3be6f802f2d5b3a41699b20ee40d2 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Thu, 15 Jun 2023 14:16:35 +0700 Subject: [PATCH 26/56] =?UTF-8?q?=F0=9F=8D=B5=20xavePool=20run=20tests=20a?= =?UTF-8?q?gainst=20polygon=20fork;=20better=20organize=20test=20files;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/xaveFxPool.integration.spec.ts | 2 +- test/xaveFxPool.math.spec.ts | 2 +- ...ation.spec.ts => xaveFxPool.polygon.integration.spec.ts} | 6 +++--- test/xaveFxPool.spec.ts | 2 +- ...ation.spec.ts => xaveFxPool.wStable.integration.spec.ts} | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) rename test/{xaveFxPool-polygon.integration.spec.ts => xaveFxPool.polygon.integration.spec.ts} (96%) rename test/{xaveFxPoolBug.integration.spec.ts => xaveFxPool.wStable.integration.spec.ts} (98%) diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index f6dbf958..50bd3069 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -75,7 +75,7 @@ const xaveFxPoolDAI_USDC_MAINNET: SubgraphPoolBase = { const test = 'FX' in PoolFilter; -describe('xaveFxPool: DAI-USDC integration tests', () => { +describe('xaveFxPool: DAI-USDC integration (Mainnet) tests', () => { context('test swaps vs queryBatchSwap', () => { // Setup chain before(async function () { diff --git a/test/xaveFxPool.math.spec.ts b/test/xaveFxPool.math.spec.ts index e9b58f4d..a32ca833 100644 --- a/test/xaveFxPool.math.spec.ts +++ b/test/xaveFxPool.math.spec.ts @@ -9,7 +9,7 @@ import { viewNumeraireAmount, } from '../src/pools/xaveFxPool/fxPoolMath'; -context('test fxMath functions', () => { +context('xaveFxPool: fxMath functions', () => { const tokenDecimals = bnum(6); const tokenFxRateDecimals = bnum(8); const rate = bnum('74376600'); // 0.743766 diff --git a/test/xaveFxPool-polygon.integration.spec.ts b/test/xaveFxPool.polygon.integration.spec.ts similarity index 96% rename from test/xaveFxPool-polygon.integration.spec.ts rename to test/xaveFxPool.polygon.integration.spec.ts index abe25d0e..ad750b10 100644 --- a/test/xaveFxPool-polygon.integration.spec.ts +++ b/test/xaveFxPool.polygon.integration.spec.ts @@ -20,8 +20,8 @@ dotenv.config(); let sor: SOR; const networkId = Network.POLYGON; -const jsonRpcUrl = process.env.ALCHEMY_URL; -const rpcUrl = 'http://127.0.0.1:8545'; +const jsonRpcUrl = process.env.ALCHEMY_URL_POLYGON; +const rpcUrl = 'http://127.0.0.1:8137'; const provider = new JsonRpcProvider(rpcUrl, networkId); const blocknumber = 43667355; @@ -72,7 +72,7 @@ const xaveFxPoolXSGD_USDC_POLYGON: SubgraphPoolBase = { epsilon: '0.0005', }; -describe('[POLYGON] xaveFxPool: DAI-USDC integration tests', () => { +describe('xaveFxPool: DAI-USDC integration (Polygon) tests', () => { context('test swaps vs queryBatchSwap', () => { // Setup chain before(async function () { diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 141709e8..109fb65c 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -31,7 +31,7 @@ type TestCaseType = { const ONE_NUMERAIRE = bnum(1); -describe('Test for fxPools', () => { +describe('xaveFxPool: fxPools stub test', () => { context('parsePoolPairData', () => { it(`should correctly parse token > token`, async () => { // It's useful to use tokens with <18 decimals for some tests to make sure scaling is ok diff --git a/test/xaveFxPoolBug.integration.spec.ts b/test/xaveFxPool.wStable.integration.spec.ts similarity index 98% rename from test/xaveFxPoolBug.integration.spec.ts rename to test/xaveFxPool.wStable.integration.spec.ts index 7f7ad7a4..e2136405 100644 --- a/test/xaveFxPoolBug.integration.spec.ts +++ b/test/xaveFxPool.wStable.integration.spec.ts @@ -187,7 +187,7 @@ const tokenOut = ADDRESSES[Network.MAINNET].USDC.address; const swapType = SwapTypes.SwapExactIn; const swapAmount = parseFixed('10', 18); -describe('Replicating FX Pool Issue, DAI>USDC', () => { +describe('xaveFxPool: Stable Pool + FX Pool integration (Mainnet), DAI-USDC', () => { context('Stable pool only', () => { before(async function () { sor = await setUp( From c578d1713eaae0bb2df22c9f0af5654448db98d6 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Thu, 15 Jun 2023 14:45:54 +0700 Subject: [PATCH 27/56] =?UTF-8?q?=F0=9F=90=9F=20fixed=20bug=20where=20OldB?= =?UTF-8?q?igNumber.config.DECIMAL=5FPLACES=20was=20left=20as=2036=20in=20?= =?UTF-8?q?case=20of=20an=20exception=20in=20FXPool=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 21 ++++++++++------ test/xaveFxPool.spec.ts | 44 +++++++++++++++++----------------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 27426422..ea733c14 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -369,12 +369,19 @@ export class FxPool implements PoolBase { DECIMAL_PLACES: 36, }); - const val = funcName.apply(this, args); - - OldBigNumber.config({ - DECIMAL_PLACES: prevDecimalPlaces, - }); - - return val; + try { + const val = funcName.apply(this, args); + OldBigNumber.config({ + DECIMAL_PLACES: prevDecimalPlaces, + }); + return val; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + // restore the original BigNumber config even in case of an exception + OldBigNumber.config({ + DECIMAL_PLACES: prevDecimalPlaces, + }); + throw err; + } } } diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 109fb65c..c6f28f42 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -151,6 +151,10 @@ describe('xaveFxPool: fxPools stub test', () => { testCase.expectedSpotPriceBeforeSwap, 'spotPriceBeforeSwapValue' ); + expect(OldBigNumber.config({}).DECIMAL_PLACES).to.eq( + 18, + 'OldBigNumber.config().DECIMAL_PLACES should be 18 after a call to FXPool functions' + ); if (testCase.swapType === 'OriginSwap') { let amountOut; @@ -176,17 +180,6 @@ describe('xaveFxPool: fxPools stub test', () => { 'amountOut vs. expectedSwapOutput' ); - // this doesn't make much sense now - // expect(amountOut.toString()).to.be.equal( - // viewRawAmount( - // bnum(testCase.expectedSwapOutput), - // bnum(poolPairData.decimalsOut), - // poolPairData.tokenOutLatestFXPrice, - // poolPairData.tokenOutfxOracleDecimals - // ).toString(), - // '_exactTokenInForTokenOut vs rawAmount' - // ); - const _spotPriceAfterSwapExactTokenInForTokenOut = newPool._spotPriceAfterSwapExactTokenInForTokenOut( poolPairData, @@ -217,12 +210,29 @@ describe('xaveFxPool: fxPools stub test', () => { let amountIn; if (testCase.testNo === '12') { + expect( + OldBigNumber.config({}).DECIMAL_PLACES + ).to.eq( + 18, + 'OldBigNumber.config().DECIMAL_PLACES should be 18 by default' + ); + // CurveMathRevert.LowerHalt const amountIn = newPool._tokenInForExactTokenOut( poolPairData, givenAmount ); - expect(amountIn.toString()).to.eq('0'); + expect(amountIn.toString()).to.eq( + '0', + `_tokenInForExactTokenOut should throw CurveMathRevert.LowerHalt` + ); + // ensure that even in the case of an exception, DECIMAL_PLACES is still 18 + expect( + OldBigNumber.config({}).DECIMAL_PLACES + ).to.eq( + 18, + 'OldBigNumber.config().DECIMAL_PLACES should be 18 even if FxPool._tokenInForExactTokenOut throws CurveMathRevert.LowerHalt' + ); } else { amountIn = newPool._tokenInForExactTokenOut( poolPairData, @@ -233,16 +243,6 @@ describe('xaveFxPool: fxPools stub test', () => { 'amountIn vs. expectedSwapOutput' ); - // expect(amountIn.toString()).to.be.eq( - // viewRawAmount( - // bnum(testCase.expectedSwapOutput), - // bnum(poolPairData.decimalsIn), - // poolPairData.tokenInLatestFXPrice, - // poolPairData.tokenInfxOracleDecimals - // ).toString(), - // '_tokenInForExactTokenOut vs rawAmount' - // ); - const _spotPriceAfterSwapTokenInForExactTokenOut = newPool._spotPriceAfterSwapTokenInForExactTokenOut( poolPairData, From 4d98aa8bfd32d65578d72d99687e47fb87bc59ea Mon Sep 17 00:00:00 2001 From: andreiashu Date: Thu, 15 Jun 2023 15:26:56 +0700 Subject: [PATCH 28/56] =?UTF-8?q?=F0=9F=92=BB=20removed=20comment=20about?= =?UTF-8?q?=20innacuracy=20test=20since=20it=20is=20irrelevant=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/xaveFxPool.integration.spec.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index 50bd3069..550da1d1 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -9,14 +9,6 @@ import { Vault__factory } from '@balancer-labs/typechain'; import { AddressZero } from '@ethersproject/constants'; import { setUp } from './testScripts/utils'; -/* - * Testing Notes: - * - Add infura api key on .env - * - Run node on terminal: yarn run node - */ - -// accuracy test: https://app.warp.dev/block/bcbBMkR8Da96QHQ2phmHZN - dotenv.config(); let sor: SOR; From 2576c4d3994dc1e7e1d0a7eec4c4879838967cfe Mon Sep 17 00:00:00 2001 From: aplki Date: Tue, 20 Jun 2023 17:20:58 +0800 Subject: [PATCH 29/56] test cases + bug --- src/pools/xaveFxPool/fxPoolMath.ts | 1 + .../fxPool/fxPoolTestCases_43667355.json | 149 ++++++++++++++++++ test/testData/fxPool/fxPool_43667355.json | 45 ++++++ test/xaveFxPool.spec.ts | 6 +- 4 files changed, 198 insertions(+), 3 deletions(-) create mode 100644 test/testData/fxPool/fxPoolTestCases_43667355.json create mode 100644 test/testData/fxPool/fxPool_43667355.json diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 28b86f98..edb6417d 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -514,6 +514,7 @@ export function _exactTokenInForTokenOut( } else { const epsilon = parsedFxPoolData.epsilon; const _amtWithFee = _amt[0].times(bnum(1).minus(epsilon)); // fee retained by the pool // @TODO this results in a 1 wei less in solidity + // -10.0019 * (1-0.0015) return viewRawAmount( _amtWithFee.abs(), diff --git a/test/testData/fxPool/fxPoolTestCases_43667355.json b/test/testData/fxPool/fxPoolTestCases_43667355.json new file mode 100644 index 00000000..d0c5111a --- /dev/null +++ b/test/testData/fxPool/fxPoolTestCases_43667355.json @@ -0,0 +1,149 @@ +[ + { + "testNo": "1", + "description": "Swap within Beta Region: OriginSwap/_exactTokenInForTokenOut USDC > ? XSGD", + "swapType": "OriginSwap", + "givenAmount": "100000", + "tokenIn": "USDC", + "tokenOut": "XSGD", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSwapOutput": "134652.537477", + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" + }, + { + "testNo": "2", + "description": "Swap within Beta Region: TargetSwap / tokenInForExactTokenOut ? XSGD > USDC", + "swapType": "TargetSwap", + "givenAmount": "100000", + "tokenIn": "XSGD", + "tokenOut": "USDC", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSwapOutput": "134787.257376", + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" + }, + { + "testNo": "3", + "description": "Swap within Beta Region: OriginSwap/_exactTokenInForTokenOut XSGD > ? USDC", + "swapType": "OriginSwap", + "givenAmount": "100000", + "tokenIn": "XSGD", + "tokenOut": "USDC", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSwapOutput": "74190.9709766", + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" + }, + { + "testNo": "4", + "description": "Swap within Beta Region: TargetSwap / tokenInForExactTokenOut ? USDC > XSGD", + "swapType": "TargetSwap", + "givenAmount": "100000", + "tokenIn": "USDC", + "tokenOut": "XSGD", + "expectedSpotPriceBeforeSwap": "0.7418926690", + "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSwapOutput": "74265.199061", + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" + }, + + { + "testNo": "5", + "description": "Swap Beyond Beta Region: OriginSwap/_exactTokenInForTokenOut USDC > ? XSGD", + "swapType": "OriginSwap", + "givenAmount": "200000", + "tokenIn": "USDC", + "tokenOut": "XSGD", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSwapOutput": "269305.074957", + "expectedDerivativeSpotPriceAfterSwap": "0.006230783064" + }, + + { + "testNo": "6", + "description": "Swap within Beta Region: TargetSwap / tokenInForExactTokenOut ? XSGD > USDC", + "swapType": "TargetSwap", + "givenAmount": "200000", + "tokenIn": "XSGD", + "tokenOut": "USDC", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSwapOutput": "269574.514752", + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" + }, + + { + "testNo": "7", + "description": "Swap within Beta Region: OriginSwap/_exactTokenInForTokenOut XSGD > ? USDC", + "swapType": "OriginSwap", + "givenAmount": "270000", + "tokenIn": "XSGD", + "tokenOut": "USDC", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSwapOutput": "200011.814394", + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" + }, + { + "testNo": "8", + "description": "Swap Beyond Beta Region: TargetSwap / tokenInForExactTokenOut ? USDC > XSGD", + "swapType": "TargetSwap", + "givenAmount": "270000", + "tokenIn": "USDC", + "tokenOut": "XSGD", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.7407674819", + "expectedSwapOutput": "200828.639953", + "expectedDerivativeSpotPriceAfterSwap": "0.006887781537" + }, + { + "testNo": "9", + "description": "Swap beyond Alpha Region: OriginSwap/_exactTokenInForTokenOut USDC > ? XSGD", + "swapType": "OriginSwap", + "givenAmount": "450000", + "tokenIn": "USDC", + "tokenOut": "XSGD", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSwapOutput": "605936.418653", + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" + }, + { + "testNo": "10", + "description": "Swap within Beta Region: TargetSwap / tokenInForExactTokenOut ? XSGD > USDC", + "swapType": "TargetSwap", + "givenAmount": "450000", + "tokenIn": "XSGD", + "tokenOut": "USDC", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSwapOutput": "606542.658192", + "expectedDerivativeSpotPriceAfterSwap": "0.002114169664" + }, + { + "testNo": "11", + "description": "Swap within Beta Region: OriginSwap/_exactTokenInForTokenOut XSGD > ? USDC", + "swapType": "OriginSwap", + "givenAmount": "610000", + "tokenIn": "XSGD", + "tokenOut": "USDC", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.6814172475", + "expectedSwapOutput": "415674.069003", + "expectedDerivativeSpotPriceAfterSwap": "-0.0815150536" + }, + { + "testNo": "12", + "description": "Swap beyond Alpha Region: TargetSwap / tokenInForExactTokenOut ? USDC > XSGD", + "swapType": "TargetSwap", + "givenAmount": "610000", + "tokenIn": "XSGD", + "tokenOut": "USDC", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "CurveMath/upper-halt", + "expectedSwapOutput": "CurveMath/upper-halt", + "expectedDerivativeSpotPriceAfterSwap": "CurveMath/upper-halt" + } +] diff --git a/test/testData/fxPool/fxPool_43667355.json b/test/testData/fxPool/fxPool_43667355.json new file mode 100644 index 00000000..e63f8aa2 --- /dev/null +++ b/test/testData/fxPool/fxPool_43667355.json @@ -0,0 +1,45 @@ +{ + "pools": [ + { + "id": "0x726e324c29a1e49309672b244bdc4ff62a270407000200000000000000000702", + "address": "0x726e324c29a1e49309672b244bdc4ff62a270407", + "swapFee": "0", + "poolType": "FX", + "totalShares": "1187294.678092153421192704", + "swapEnabled": true, + "tokens": [ + { + "address": "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "balance": "640405.311822", + "decimals": 6, + "priceRate": "1", + "weight": null, + "token": { + "latestFXPrice": "0.99997703", + "fxOracleDecimals": 8 + } + }, + { + "address": "0xdc3326e71d45186f113a2f448984ca0e8d201995", + "balance": "1533442.592483", + "decimals": 6, + "priceRate": "1", + "weight": null, + "token": { + "latestFXPrice": "0.74226380", + "fxOracleDecimals": 8 + } + } + ], + "tokensList": [ + "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", + "0xdc3326e71d45186f113a2f448984ca0e8d201995" + ], + "alpha": "0.8", + "beta": "0.48", + "lambda": "0.3", + "delta": "0.2734375", + "epsilon": "0.0005" + } + ] +} diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index c6f28f42..060138a0 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -13,8 +13,8 @@ import { } from '../src/pools/xaveFxPool/fxPoolMath'; // Add new pool test data in Subgraph Schema format -import testPools from './testData/fxPool/fxPool.json'; -import testCases from './testData/fxPool/fxPoolTestCases.json'; +import testPools from './testData/fxPool/fxPool_43667355.json'; +import testCases from './testData/fxPool/fxPoolTestCases_43667355.json'; type TestCaseType = { testNo: string; @@ -98,7 +98,7 @@ describe('xaveFxPool: fxPools stub test', () => { SwapTypes.SwapExactIn ); - expect(amount.toString()).to.equals('355216.345225'); + expect(amount.toString()).to.equals('960380.032958'); // @todo check }); }); From c2590b0b6b872ec7fea4aaf2a1ba473986fd81b9 Mon Sep 17 00:00:00 2001 From: aplki Date: Wed, 21 Jun 2023 18:17:13 +0800 Subject: [PATCH 30/56] =?UTF-8?q?=F0=9F=A6=A9=20new=20fxpool=20test=20case?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fxPool/fxPoolTestCases_43667355.json | 72 +++++++------------ 1 file changed, 25 insertions(+), 47 deletions(-) diff --git a/test/testData/fxPool/fxPoolTestCases_43667355.json b/test/testData/fxPool/fxPoolTestCases_43667355.json index d0c5111a..f367a20a 100644 --- a/test/testData/fxPool/fxPoolTestCases_43667355.json +++ b/test/testData/fxPool/fxPoolTestCases_43667355.json @@ -7,7 +7,7 @@ "tokenIn": "USDC", "tokenOut": "XSGD", "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSpotPriceAfterSwap": "0.741892669", "expectedSwapOutput": "134652.537477", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, @@ -19,8 +19,8 @@ "tokenIn": "XSGD", "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.7418926681", - "expectedSwapOutput": "134787.257376", + "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSwapOutput": "134787.257375", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, { @@ -31,8 +31,8 @@ "tokenIn": "XSGD", "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.7418926681", - "expectedSwapOutput": "74190.9709766", + "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSwapOutput": "74190.970975", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, { @@ -42,8 +42,8 @@ "givenAmount": "100000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.7418926690", - "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceAfterSwap": "0.741892669", "expectedSwapOutput": "74265.199061", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -56,9 +56,9 @@ "tokenIn": "USDC", "tokenOut": "XSGD", "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.7418926681", - "expectedSwapOutput": "269305.074957", - "expectedDerivativeSpotPriceAfterSwap": "0.006230783064" + "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSwapOutput": "269305.074955", + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, { @@ -69,9 +69,9 @@ "tokenIn": "XSGD", "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.7418926681", - "expectedSwapOutput": "269574.514752", - "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" + "expectedSpotPriceAfterSwap": "0.740778401", + "expectedSwapOutput": "269980.072169", + "expectedDerivativeSpotPriceAfterSwap": "0.00150192575850307544" }, { @@ -82,9 +82,9 @@ "tokenIn": "XSGD", "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.7418926681", - "expectedSwapOutput": "200011.814394", - "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" + "expectedSpotPriceAfterSwap": "0.740767482", + "expectedSwapOutput": "200011.814393", + "expectedDerivativeSpotPriceAfterSwap": "0.00151664278646597275" }, { "testNo": "8", @@ -94,22 +94,11 @@ "tokenIn": "USDC", "tokenOut": "XSGD", "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.7407674819", - "expectedSwapOutput": "200828.639953", - "expectedDerivativeSpotPriceAfterSwap": "0.006887781537" - }, - { - "testNo": "9", - "description": "Swap beyond Alpha Region: OriginSwap/_exactTokenInForTokenOut USDC > ? XSGD", - "swapType": "OriginSwap", - "givenAmount": "450000", - "tokenIn": "USDC", - "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.741892669", "expectedSpotPriceAfterSwap": "0.741892669", - "expectedSwapOutput": "605936.418653", + "expectedSwapOutput": "200516.037466", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, + { "testNo": "10", "description": "Swap within Beta Region: TargetSwap / tokenInForExactTokenOut ? XSGD > USDC", @@ -118,22 +107,11 @@ "tokenIn": "XSGD", "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.741892669", - "expectedSwapOutput": "606542.658192", - "expectedDerivativeSpotPriceAfterSwap": "0.002114169664" - }, - { - "testNo": "11", - "description": "Swap within Beta Region: OriginSwap/_exactTokenInForTokenOut XSGD > ? USDC", - "swapType": "OriginSwap", - "givenAmount": "610000", - "tokenIn": "XSGD", - "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.6814172475", - "expectedSwapOutput": "415674.069003", - "expectedDerivativeSpotPriceAfterSwap": "-0.0815150536" + "expectedSpotPriceAfterSwap": "0.669706953", + "expectedSwapOutput": "671920.250617", + "expectedDerivativeSpotPriceAfterSwap": "0.09729940529313825916" }, + { "testNo": "12", "description": "Swap beyond Alpha Region: TargetSwap / tokenInForExactTokenOut ? USDC > XSGD", @@ -142,8 +120,8 @@ "tokenIn": "XSGD", "tokenOut": "USDC", "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "CurveMath/upper-halt", - "expectedSwapOutput": "CurveMath/upper-halt", - "expectedDerivativeSpotPriceAfterSwap": "CurveMath/upper-halt" + "expectedSpotPriceAfterSwap": "CurveMath/lower-halt", + "expectedSwapOutput": "CurveMath/lower-halt", + "expectedDerivativeSpotPriceAfterSwap": "CurveMath/lower-halt" } ] From af3eb5c1d7904d9ac4784b6f5fc29b7a7931aa3f Mon Sep 17 00:00:00 2001 From: andreiashu Date: Thu, 22 Jun 2023 07:56:50 +0700 Subject: [PATCH 31/56] =?UTF-8?q?=F0=9F=8D=8E=20removed=20rounding=20of=20?= =?UTF-8?q?values=20in=20xaveFxPool.spec.ts;=20the=20values=20for=20`expec?= =?UTF-8?q?tedSpotPriceBeforeSwap`=20value=20are=20not=20precisely=20equal?= =?UTF-8?q?=20to=20the=20excel=20spreadsheet;=20all=20the=20tests=20that?= =?UTF-8?q?=20fail=20now,=20fail=20because=20of=20`expectedSpotPriceAfterS?= =?UTF-8?q?wap`=20-=20will=20need=20to=20check=20with=20Khidir=20about=20t?= =?UTF-8?q?his;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fxPool/fxPoolTestCases_43667355.json | 32 +++++----- test/xaveFxPool.spec.ts | 59 ++++++------------- 2 files changed, 34 insertions(+), 57 deletions(-) diff --git a/test/testData/fxPool/fxPoolTestCases_43667355.json b/test/testData/fxPool/fxPoolTestCases_43667355.json index f367a20a..81e6c539 100644 --- a/test/testData/fxPool/fxPoolTestCases_43667355.json +++ b/test/testData/fxPool/fxPoolTestCases_43667355.json @@ -6,8 +6,8 @@ "givenAmount": "100000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSpotPriceBeforeSwap": "0.7418926681", + "expectedSpotPriceAfterSwap": "0.7418926681", "expectedSwapOutput": "134652.537477", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, @@ -18,8 +18,8 @@ "givenAmount": "100000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSpotPriceBeforeSwap": "0.7418926681", + "expectedSpotPriceAfterSwap": "0.7418926681", "expectedSwapOutput": "134787.257375", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -30,8 +30,8 @@ "givenAmount": "100000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSpotPriceBeforeSwap": "0.7418926681", + "expectedSpotPriceAfterSwap": "0.7418926681", "expectedSwapOutput": "74190.970975", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, @@ -42,8 +42,8 @@ "givenAmount": "100000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSpotPriceBeforeSwap": "0.7418926681", + "expectedSpotPriceAfterSwap": "0.7418926681", "expectedSwapOutput": "74265.199061", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -55,8 +55,8 @@ "givenAmount": "200000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSpotPriceBeforeSwap": "0.7418926681", + "expectedSpotPriceAfterSwap": "0.7418926681", "expectedSwapOutput": "269305.074955", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" }, @@ -68,7 +68,7 @@ "givenAmount": "200000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceBeforeSwap": "0.7418926681", "expectedSpotPriceAfterSwap": "0.740778401", "expectedSwapOutput": "269980.072169", "expectedDerivativeSpotPriceAfterSwap": "0.00150192575850307544" @@ -81,7 +81,7 @@ "givenAmount": "270000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceBeforeSwap": "0.7418926681", "expectedSpotPriceAfterSwap": "0.740767482", "expectedSwapOutput": "200011.814393", "expectedDerivativeSpotPriceAfterSwap": "0.00151664278646597275" @@ -93,8 +93,8 @@ "givenAmount": "270000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.741892669", - "expectedSpotPriceAfterSwap": "0.741892669", + "expectedSpotPriceBeforeSwap": "0.7418926681", + "expectedSpotPriceAfterSwap": "0.7418926681", "expectedSwapOutput": "200516.037466", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -106,7 +106,7 @@ "givenAmount": "450000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceBeforeSwap": "0.7418926681", "expectedSpotPriceAfterSwap": "0.669706953", "expectedSwapOutput": "671920.250617", "expectedDerivativeSpotPriceAfterSwap": "0.09729940529313825916" @@ -119,7 +119,7 @@ "givenAmount": "610000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.741892669", + "expectedSpotPriceBeforeSwap": "0.7418926681", "expectedSpotPriceAfterSwap": "CurveMath/lower-halt", "expectedSwapOutput": "CurveMath/lower-halt", "expectedDerivativeSpotPriceAfterSwap": "CurveMath/lower-halt" diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 060138a0..6dc80f5b 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -47,36 +47,21 @@ describe('xaveFxPool: fxPools stub test', () => { expect(poolPairData.id).to.eq(poolData.id); expect(poolPairData.poolType).to.eq(PoolTypes.Fx); - expect( - poolPairData.alpha - .div(bnum(10).pow(18)) - .decimalPlaces(8) - .toString() - ).to.eq(poolData.alpha); - expect( - poolPairData.beta - .div(bnum(10).pow(18)) - .decimalPlaces(8) - .toString() - ).to.eq(poolData.beta); - expect( - poolPairData.lambda - .div(bnum(10).pow(18)) - .decimalPlaces(8) - .toString() - ).to.eq(poolData.lambda); - expect( - poolPairData.delta - .div(bnum(10).pow(18)) - .decimalPlaces(8) - .toString() - ).to.eq(poolData.delta); - expect( - poolPairData.epsilon - .div(bnum(10).pow(18)) - .decimalPlaces(8) - .toString() - ).to.eq(poolData.epsilon); + expect(poolPairData.alpha.div(bnum(10).pow(18)).toString()).to.eq( + poolData.alpha + ); + expect(poolPairData.beta.div(bnum(10).pow(18)).toString()).to.eq( + poolData.beta + ); + expect(poolPairData.lambda.div(bnum(10).pow(18)).toString()).to.eq( + poolData.lambda + ); + expect(poolPairData.delta.div(bnum(10).pow(18)).toString()).to.eq( + poolData.delta + ); + expect(poolPairData.epsilon.div(bnum(10).pow(18)).toString()).to.eq( + poolData.epsilon + ); }); }); @@ -143,11 +128,7 @@ describe('xaveFxPool: fxPools stub test', () => { poolPairData ); - expect( - spotPriceBeforeSwapValue - .decimalPlaces(9, OldBigNumber.ROUND_UP) - .toString() - ).to.equals( + expect(spotPriceBeforeSwapValue.toString()).to.equals( testCase.expectedSpotPriceBeforeSwap, 'spotPriceBeforeSwapValue' ); @@ -187,9 +168,7 @@ describe('xaveFxPool: fxPools stub test', () => { ); expect( - _spotPriceAfterSwapExactTokenInForTokenOut - .decimalPlaces(9, OldBigNumber.ROUND_UP) - .toString() + _spotPriceAfterSwapExactTokenInForTokenOut.toString() ).to.equals( testCase.expectedSpotPriceAfterSwap, 'expectedSpotPriceAfterSwap' @@ -250,9 +229,7 @@ describe('xaveFxPool: fxPools stub test', () => { ); expect( - _spotPriceAfterSwapTokenInForExactTokenOut - .decimalPlaces(9, OldBigNumber.ROUND_UP) - .toString() + _spotPriceAfterSwapTokenInForExactTokenOut.toString() ).to.equal( testCase.expectedSpotPriceAfterSwap, 'expectedSpotPriceAfterSwap' From 61a97748e7ae3654dc2fd2d447f9b78771c79fe3 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Fri, 23 Jun 2023 14:50:54 +0700 Subject: [PATCH 32/56] =?UTF-8?q?=F0=9F=90=A8=20fixed=20bug=20whereby=20th?= =?UTF-8?q?e=20way=20we=20calculated=20our=20derivative=20would=20have=20p?= =?UTF-8?q?ut=20us=20as=20last=20of=20the=20pools=20in=20terms=20of=20norm?= =?UTF-8?q?alized=20liquidity;=20fixed=20tests;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 2 +- src/pools/xaveFxPool/fxPoolMath.ts | 73 ++++++++++++++++--- src/pools/xaveFxPool/parseFixedCurveParam.ts | 2 +- .../fxPool/fxPoolTestCases_43667355.json | 50 ++++++------- test/xaveFxPool.spec.ts | 2 +- 5 files changed, 90 insertions(+), 39 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index ea733c14..1e0ac29b 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -102,7 +102,7 @@ export class FxPool implements PoolBase { this.alpha = parseFixedCurveParam(alpha); this.beta = parseFixedCurveParam(beta); this.lambda = parseFixedCurveParam(lambda); - this.delta = parseFixedCurveParam(delta); + this.delta = bnum(parseFixed(delta, 18).toString()); this.epsilon = parseFixedCurveParam(epsilon); } updateTotalShares: (newTotalShares: BigNumber) => void; diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index edb6417d..47162013 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -599,11 +599,16 @@ export const spotPriceBeforeSwap = ( parsedFxPoolData ); - return outputAmountInNumeraire[0] + const val = outputAmountInNumeraire[0] .abs() .times(bnum(1).minus(parsedFxPoolData.epsilon)) .div(inputAmountInNumeraire.abs()) - .times(parsedFxPoolData.baseTokenRate); + .times(parsedFxPoolData.baseTokenRate) + .decimalPlaces( + poolPairData.tokenOutfxOracleDecimals.toNumber(), + OldBigNumber.ROUND_DOWN + ); + return val; }; // spot price after origin swap @@ -656,9 +661,18 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .times(bnum(1).minus(epsilon)) .abs() .div(targetAmountInNumeraire.abs()) - .times(currentRate); + .times(currentRate) + .decimalPlaces( + poolPairData.tokenInfxOracleDecimals.toNumber(), + OldBigNumber.ROUND_DOWN + ); } else { - return currentRate.times(bnum(1).minus(epsilon)); + return currentRate + .times(bnum(1).minus(epsilon)) + .decimalPlaces( + poolPairData.tokenInfxOracleDecimals.toNumber(), + OldBigNumber.ROUND_DOWN + ); } } else { // if usdc is tokenOut @@ -675,9 +689,19 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .times(bnum(1).minus(epsilon)) .abs() .div(targetAmountInNumeraire.abs()); - return ratioOfOutputAndInput.times(currentRate); + return ratioOfOutputAndInput + .times(currentRate) + .decimalPlaces( + poolPairData.tokenInfxOracleDecimals.toNumber(), + OldBigNumber.ROUND_DOWN + ); } else { - return currentRate.times(bnum(1).minus(epsilon)); + return currentRate + .times(bnum(1).minus(epsilon)) + .decimalPlaces( + poolPairData.tokenInfxOracleDecimals.toNumber(), + OldBigNumber.ROUND_DOWN + ); } } }; @@ -729,9 +753,19 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( return targetAmountInNumeraire .abs() .div(outputAmount.times(epsilon.plus(1)).abs()) - .times(currentRate); + .times(currentRate) + .decimalPlaces( + poolPairData.tokenOutfxOracleDecimals.toNumber(), + OldBigNumber.ROUND_DOWN + ); } else { - return currentRate.times(bnum(1).minus(epsilon)); + // rate * (1-epsilon) + return currentRate + .times(bnum(1).minus(epsilon)) + .decimalPlaces( + poolPairData.tokenOutfxOracleDecimals.toNumber(), + OldBigNumber.ROUND_DOWN + ); } } else { // token[1] to token [0] in originswap @@ -745,9 +779,18 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( return targetAmountInNumeraire .abs() .div(outputAmount.times(epsilon.plus(1)).abs()) - .times(currentRate); + .times(currentRate) + .decimalPlaces( + poolPairData.tokenOutfxOracleDecimals.toNumber(), + OldBigNumber.ROUND_DOWN + ); } else { - return currentRate.times(bnum(1).minus(epsilon)); + return currentRate + .times(bnum(1).minus(epsilon)) + .decimalPlaces( + poolPairData.tokenOutfxOracleDecimals.toNumber(), + OldBigNumber.ROUND_DOWN + ); } } }; @@ -761,7 +804,11 @@ export const _derivativeSpotPriceAfterSwapExactTokenInForTokenOut = ( const y = _spotPriceAfterSwapExactTokenInForTokenOut(poolPairData, amount); const yMinusX = y.minus(x); const ans = yMinusX.div(x); - return ans.isZero() ? bnum(ALMOST_ZERO) : ans.abs(); + // if we're outside the Beta region the derivative will be negative + // but `UniversalNormalizedLiquidity` returns ZERO for negative values + // therefore we want to make sure this reflects the fact that we're + // moving outside of Beta region + return ans.abs(); }; // target swap @@ -773,5 +820,9 @@ export const _derivativeSpotPriceAfterSwapTokenInForExactTokenOut = ( const y = _spotPriceAfterSwapTokenInForExactTokenOut(poolPairData, amount); const yMinusX = y.minus(x); const ans = yMinusX.div(x); + // if we're outside the Beta region the derivative will be negative + // but `UniversalNormalizedLiquidity` returns ZERO for negative values + // therefore we want to make sure this reflects the fact that we're + // moving outside of Beta region return ans.abs(); }; diff --git a/src/pools/xaveFxPool/parseFixedCurveParam.ts b/src/pools/xaveFxPool/parseFixedCurveParam.ts index e6ef9265..04ea70ea 100644 --- a/src/pools/xaveFxPool/parseFixedCurveParam.ts +++ b/src/pools/xaveFxPool/parseFixedCurveParam.ts @@ -10,7 +10,7 @@ import { parseFixed } from '@ethersproject/bignumber'; * - `uint256(0.0005 * 1e18).divu(1e18)` is 9223372036854775 which is 0.000499999999999999956 * - `uint256(0.0005 * 1e18 + 1).divu(1e18)` is 9223372036854794 which is 0.00050000000000000099 * - * Most but one of the parameters (`delta`) use the formula `uint256(0.0005 * 1e18 + 1).divu(1e18)` + * Most but one of the parameters (`delta`) use the formula `uint256(PARAM * 1e18 + 1).divu(1e18)` * when converting to fixed point precision. This is the value that is used in calculations * in the smart contract. * diff --git a/test/testData/fxPool/fxPoolTestCases_43667355.json b/test/testData/fxPool/fxPoolTestCases_43667355.json index 81e6c539..7274b69d 100644 --- a/test/testData/fxPool/fxPoolTestCases_43667355.json +++ b/test/testData/fxPool/fxPoolTestCases_43667355.json @@ -6,10 +6,10 @@ "givenAmount": "100000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.7418926681", - "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.74189266", "expectedSwapOutput": "134652.537477", - "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, { "testNo": "2", @@ -18,8 +18,8 @@ "givenAmount": "100000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.7418926681", - "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.74189266", "expectedSwapOutput": "134787.257375", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -30,10 +30,10 @@ "givenAmount": "100000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.7418926681", - "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.74189266", "expectedSwapOutput": "74190.970975", - "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, { "testNo": "4", @@ -42,8 +42,8 @@ "givenAmount": "100000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.7418926681", - "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.74189266", "expectedSwapOutput": "74265.199061", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -55,10 +55,10 @@ "givenAmount": "200000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.7418926681", - "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.74189266", "expectedSwapOutput": "269305.074955", - "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000010" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, { @@ -68,10 +68,10 @@ "givenAmount": "200000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.7418926681", - "expectedSpotPriceAfterSwap": "0.740778401", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.7407784", "expectedSwapOutput": "269980.072169", - "expectedDerivativeSpotPriceAfterSwap": "0.00150192575850307544" + "expectedDerivativeSpotPriceAfterSwap": "0.00150191538490217708" }, { @@ -81,10 +81,10 @@ "givenAmount": "270000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.7418926681", - "expectedSpotPriceAfterSwap": "0.740767482", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.74076748", "expectedSwapOutput": "200011.814393", - "expectedDerivativeSpotPriceAfterSwap": "0.00151664278646597275" + "expectedDerivativeSpotPriceAfterSwap": "0.00151663449534599789" }, { "testNo": "8", @@ -93,8 +93,8 @@ "givenAmount": "270000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.7418926681", - "expectedSpotPriceAfterSwap": "0.7418926681", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.74189266", "expectedSwapOutput": "200516.037466", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -106,10 +106,10 @@ "givenAmount": "450000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.7418926681", - "expectedSpotPriceAfterSwap": "0.669706953", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.66970695", "expectedSwapOutput": "671920.250617", - "expectedDerivativeSpotPriceAfterSwap": "0.09729940529313825916" + "expectedDerivativeSpotPriceAfterSwap": "0.09729939908018499603" }, { @@ -119,7 +119,7 @@ "givenAmount": "610000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.7418926681", + "expectedSpotPriceBeforeSwap": "0.74189266", "expectedSpotPriceAfterSwap": "CurveMath/lower-halt", "expectedSwapOutput": "CurveMath/lower-halt", "expectedDerivativeSpotPriceAfterSwap": "CurveMath/lower-halt" diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 6dc80f5b..ba4da6f1 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -99,7 +99,7 @@ describe('xaveFxPool: fxPools stub test', () => { expect( newPool.getNormalizedLiquidity(poolPairData).toString() - ).to.equals(bnum(1).div(ALMOST_ZERO).toString()); + ).to.equals('0', 'getNormalizedLiquidity'); }); }); From f62a047d08207da1d8af4401b49dafcf33ed84c4 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Fri, 23 Jun 2023 14:51:58 +0700 Subject: [PATCH 33/56] =?UTF-8?q?=F0=9F=90=A8=20lint=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 1 - test/xaveFxPool.spec.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 47162013..49e346d4 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -515,7 +515,6 @@ export function _exactTokenInForTokenOut( const epsilon = parsedFxPoolData.epsilon; const _amtWithFee = _amt[0].times(bnum(1).minus(epsilon)); // fee retained by the pool // @TODO this results in a 1 wei less in solidity - // -10.0019 * (1-0.0015) return viewRawAmount( _amtWithFee.abs(), bnum(poolPairData.decimalsOut), diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index ba4da6f1..47e9f2d1 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -7,7 +7,6 @@ import { OldBigNumber, PoolTypes, SwapTypes } from '../src'; // Add new PoolType import { FxPool, FxPoolPairData } from '../src/pools/xaveFxPool/fxPool'; import { - ALMOST_ZERO, spotPriceBeforeSwap, _spotPriceAfterSwapExactTokenInForTokenOut, } from '../src/pools/xaveFxPool/fxPoolMath'; From 9c82d4b0f33ebd68c0eafbcaf61efe1289c1729f Mon Sep 17 00:00:00 2001 From: andreiashu Date: Fri, 23 Jun 2023 17:01:26 +0700 Subject: [PATCH 34/56] =?UTF-8?q?=E2=9B=B0=20=20removed=20obsolete=20comme?= =?UTF-8?q?nts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 49e346d4..215b73a1 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -212,28 +212,6 @@ const getParsedFxPoolData = ( }; }; -// get base decimals for -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const getBaseDecimals = (decimals: number) => { - switch (decimals) { - case 6: { - return ONE_TO_THE_SIX_NUM; - } - - case 2: { - return ONE_TO_THE_SECOND_NUM; - } - - case 18: { - return ONE_ETHER.toString(); - } - - default: { - return ONE_ETHER.toString(); - } - } -}; - // Base Assimilator Functions // calculations are from the BaseToUsdAssimilator @@ -513,7 +491,7 @@ export function _exactTokenInForTokenOut( throw new Error(CurveMathRevert.CannotSwap); } else { const epsilon = parsedFxPoolData.epsilon; - const _amtWithFee = _amt[0].times(bnum(1).minus(epsilon)); // fee retained by the pool // @TODO this results in a 1 wei less in solidity + const _amtWithFee = _amt[0].times(bnum(1).minus(epsilon)); return viewRawAmount( _amtWithFee.abs(), @@ -529,7 +507,6 @@ export function _tokenInForExactTokenOut( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber { - // const amountIn = scale(amount, poolPairData.decimalsOut); const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, false); const targetAmountInNumeraire = parsedFxPoolData.givenAmountInNumeraire.times(-1); From 60071d9239d45990f16ebcb5958b39631f9b737c Mon Sep 17 00:00:00 2001 From: andreiashu Date: Fri, 23 Jun 2023 17:17:21 +0700 Subject: [PATCH 35/56] =?UTF-8?q?=F0=9F=92=BE=20do=20not=20use=20floats=20?= =?UTF-8?q?in=20calculations;=20found=20a=20place=20where=20we=20were=20ro?= =?UTF-8?q?unding=20down=20to=2015=20decimals=20(I=20must=20have=20forgott?= =?UTF-8?q?en=20that=20code=20there);?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 6 +++--- src/pools/xaveFxPool/fxPoolMath.ts | 33 ++++++++++-------------------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 1e0ac29b..897a4398 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -219,7 +219,7 @@ export class FxPool implements PoolBase { const maxLimit = alphaValue .plus(1) .times(parsedReserves._oGLiq) - .times(0.5); + .times('0.5'); if (swapType === SwapTypes.SwapExactIn) { const maxLimitAmount = maxLimit.minus( @@ -355,8 +355,8 @@ export class FxPool implements PoolBase { /** * Runs the given function with the BigNumber config set to 36 decimals. * This is needed since in the Solidity code we use 64.64 fixed point numbers - * for the curve math operations. This makes the SOR default of 18 decimals - * not enough. + * for the curve math operations (ABDKMath64x64.sol). This makes the SOR + * default of 18 decimals not enough. * * @param funcName * @param args diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 215b73a1..78148b9b 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -1,22 +1,11 @@ -import { BigNumber as OldBigNumber, bnum, scale } from '../../utils/bignumber'; +import { BigNumber as OldBigNumber, bnum } from '../../utils/bignumber'; import { FxPoolPairData } from './fxPool'; import { BigNumber } from '@ethersproject/bignumber'; // Constants -export const CURVEMATH_MAX_DIFF = -0.000001000000000000024; -export const NEGATIVE_ONE = bnum('-1'); -export const ONE = bnum('1'); -export const ONE_TO_THE_SECOND_NUM = 100; -export const ONE_TO_THE_SECOND = BigInt(`${ONE_TO_THE_SECOND_NUM}`); -export const ONE_TO_THE_EIGHT_NUM = 100000000; -export const ONE_TO_THE_EIGHT = BigInt(`${ONE_TO_THE_EIGHT_NUM}`); -export const ONE_TO_THE_SIX_NUM = 1000000; -export const ONE_TO_THE_SIX = BigInt(`${ONE_TO_THE_SIX_NUM}`); -export const ONE_TO_THE_THIRTEEN_NUM = 10000000000000; -export const ONE_TO_THE_THIRTEEN = BigInt(`${ONE_TO_THE_THIRTEEN_NUM}`); -export const ONE_ETHER = scale(bnum('1'), 18); -export const ALMOST_ZERO = 0.0000000000000000001; // swapping within beta region has no slippage -const CURVEMATH_MAX = 0.25; //CURVEMATH MAX from contract +export const CURVEMATH_MAX_DIFF = bnum('-0.000001000000000000024'); +export const ONE_TO_THE_THIRTEEN_NUM = bnum('10000000000000'); +const CURVEMATH_MAX = bnum('0.25'); //CURVEMATH MAX from contract export enum CurveMathRevert { LowerHalt = 'CurveMath/lower-halt', @@ -113,7 +102,7 @@ export const poolBalancesToNumeraire = ( bnum(poolPairData.decimalsOut), poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals - ).decimalPlaces(15, OldBigNumber.ROUND_DOWN); + ); } else { tokenInNumeraire = viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceOut), @@ -280,7 +269,7 @@ const calculateMicroFee = ( fee_ = fee_.times(_delta); if (fee_.gt(CURVEMATH_MAX)) { - fee_ = bnum(CURVEMATH_MAX); + fee_ = CURVEMATH_MAX; } fee_ = fee_.times(_feeMargin); @@ -296,7 +285,7 @@ const calculateMicroFee = ( fee_ = _feeMargin.div(_ideal); fee_ = fee_.times(_delta); - if (fee_.gt(CURVEMATH_MAX)) fee_ = bnum(CURVEMATH_MAX); + if (fee_.gt(CURVEMATH_MAX)) fee_ = CURVEMATH_MAX; fee_ = fee_.times(_feeMargin); } else { @@ -616,9 +605,9 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( const outputAmount = outputAfterTrade[0]; - const maxBetaLimit = beta.plus(1).times(0.5).times(_oGLiq); + const maxBetaLimit = beta.plus(1).times('0.5').times(_oGLiq); - const minBetaLimit = bnum(1).minus(beta).times(0.5).times(_oGLiq); + const minBetaLimit = bnum(1).minus(beta).times('0.5').times(_oGLiq); if (isUSDC(poolPairData.tokenIn)) { // token[0] to token [1] in originswap @@ -716,9 +705,9 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( const outputAmount = outputAfterTrade[0]; - const maxBetaLimit = beta.plus(1).times(0.5).times(_oGLiq); + const maxBetaLimit = beta.plus(1).times('0.5').times(_oGLiq); - const minBetaLimit = bnum(1).minus(beta).times(0.5).times(_oGLiq); + const minBetaLimit = bnum(1).minus(beta).times('0.5').times(_oGLiq); if (isUSDC(poolPairData.tokenIn)) { // token[0] to token [1] in originswap From 090466f19ca0bb601e7e45093cb3f1dc7ab94fde Mon Sep 17 00:00:00 2001 From: andreiashu Date: Mon, 26 Jun 2023 08:31:47 +0700 Subject: [PATCH 36/56] =?UTF-8?q?=E2=9B=8F=20viewRawAmount=20and=20viewNum?= =?UTF-8?q?eraireAmount=20using=20BigInt=20internally?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 12 ++--- src/pools/xaveFxPool/fxPoolMath.ts | 83 ++++++++++++++++-------------- test/xaveFxPool.math.spec.ts | 16 +++++- test/xaveFxPool.spec.ts | 4 +- 4 files changed, 67 insertions(+), 48 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 897a4398..cbe69f10 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -37,9 +37,9 @@ export type FxPoolPairData = PoolPairBase & { delta: OldBigNumber; epsilon: OldBigNumber; tokenInLatestFXPrice: OldBigNumber; - tokenInfxOracleDecimals: OldBigNumber; + tokenInfxOracleDecimals: number; tokenOutLatestFXPrice: OldBigNumber; - tokenOutfxOracleDecimals: OldBigNumber; + tokenOutfxOracleDecimals: number; }; export class FxPool implements PoolBase { @@ -172,8 +172,8 @@ export class FxPool implements PoolBase { tO.token.fxOracleDecimals ).toString() ), // decimals is formatted from subgraph in rate we get from the chainlink oracle - tokenInfxOracleDecimals: bnum(tI.token.fxOracleDecimals), - tokenOutfxOracleDecimals: bnum(tO.token.fxOracleDecimals), + tokenInfxOracleDecimals: tI.token.fxOracleDecimals, + tokenOutfxOracleDecimals: tO.token.fxOracleDecimals, }; return poolPairData; @@ -228,7 +228,7 @@ export class FxPool implements PoolBase { return viewRawAmount( maxLimitAmount, - bnum(poolPairData.decimalsIn), + poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ).div(bnum(10).pow(poolPairData.decimalsIn)); @@ -239,7 +239,7 @@ export class FxPool implements PoolBase { return viewRawAmount( maxLimitAmount, - bnum(poolPairData.decimalsOut), + poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ).div(bnum(10).pow(poolPairData.decimalsOut)); diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 78148b9b..34283e29 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -1,6 +1,8 @@ import { BigNumber as OldBigNumber, bnum } from '../../utils/bignumber'; import { FxPoolPairData } from './fxPool'; import { BigNumber } from '@ethersproject/bignumber'; +import { ONE as ONE_ETH } from '../../utils/basicOperations'; +import { safeParseFixed } from '../../utils'; // Constants export const CURVEMATH_MAX_DIFF = bnum('-0.000001000000000000024'); @@ -57,7 +59,7 @@ const calculateGivenAmountInNumeraire = ( // tokenIn is given calculatedNumeraireAmount = viewNumeraireAmount( amount.times(bnum(10).pow(poolPairData.decimalsIn)), - bnum(poolPairData.decimalsIn), + poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ); @@ -65,7 +67,7 @@ const calculateGivenAmountInNumeraire = ( // tokenOut is given calculatedNumeraireAmount = viewNumeraireAmount( amount.times(bnum(10).pow(poolPairData.decimalsOut)), - bnum(poolPairData.decimalsOut), + poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ); @@ -93,27 +95,27 @@ export const poolBalancesToNumeraire = ( // _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); tokenInNumeraire = viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceIn), - bnum(poolPairData.decimalsIn), + poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ); tokenOutNumeraire = viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceOut), - bnum(poolPairData.decimalsOut), + poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ); } else { tokenInNumeraire = viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceOut), - bnum(poolPairData.decimalsOut), + poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ); tokenOutNumeraire = viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceIn), - bnum(poolPairData.decimalsIn), + poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ); @@ -135,13 +137,13 @@ const getParsedFxPoolData = ( const baseReserves = isUSDC(poolPairData.tokenIn) ? viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceOut), - bnum(poolPairData.decimalsOut), + poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ) : viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceIn), - bnum(poolPairData.decimalsIn), + poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ); @@ -150,13 +152,13 @@ const getParsedFxPoolData = ( const usdcReserves = isUSDC(poolPairData.tokenIn) ? viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceIn), - bnum(poolPairData.decimalsIn), + poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ) : viewNumeraireAmount( EthersBNToOldBn(poolPairData.balanceOut), - bnum(poolPairData.decimalsOut), + poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ); @@ -214,18 +216,22 @@ const getParsedFxPoolData = ( */ export const viewRawAmount = ( _amount: OldBigNumber, // numeraire - tokenDecimals: OldBigNumber, + tokenDecimals: number, rate: OldBigNumber, // wei - fxOracleDecimals: OldBigNumber + fxOracleDecimals: number ): OldBigNumber => { // solidity code `_amount.mulu(baseDecimals).mul(baseOracleDecimals).div(_rate); - return _amount - .times(bnum(10).pow(tokenDecimals)) - .integerValue(OldBigNumber.ROUND_DOWN) // `mulu` rounds down - .times(bnum(10).pow(fxOracleDecimals)) - .div(rate) - .integerValue(OldBigNumber.ROUND_DOWN); + const inAmount = BigInt( + safeParseFixed(_amount.toString(), tokenDecimals).toString() + ); + + const val = + (inAmount * BigInt(10 ** fxOracleDecimals) * ONE_ETH) / + BigInt(rate.toString()) / + ONE_ETH; + + return bnum(val.toString()); }; /** @@ -237,16 +243,17 @@ export const viewRawAmount = ( */ export const viewNumeraireAmount = ( _amount: OldBigNumber, // wei - tokenDecimals: OldBigNumber, + tokenDecimals: number, rate: OldBigNumber, // wei - fxOracleDecimals: OldBigNumber + fxOracleDecimals: number ): OldBigNumber => { // Solidity: _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); - return _amount - .times(rate) - .div(bnum(10).pow(fxOracleDecimals)) - .integerValue(OldBigNumber.ROUND_DOWN) - .div(bnum(10).pow(tokenDecimals)); + + const val = + (BigInt(_amount.toString()) * BigInt(rate.toString())) / + BigInt(10 ** fxOracleDecimals); + + return bnum(val.toString()).div(bnum(10).pow(tokenDecimals)); }; // Curve Math @@ -455,7 +462,7 @@ export function _exactTokenInForTokenOut( if (poolPairData.tokenIn === poolPairData.tokenOut) { return viewRawAmount( targetAmountInNumeraire, - bnum(poolPairData.decimalsIn), + poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ).div(bnum(10).pow(poolPairData.decimalsIn)); // must be the token out @@ -484,7 +491,7 @@ export function _exactTokenInForTokenOut( return viewRawAmount( _amtWithFee.abs(), - bnum(poolPairData.decimalsOut), + poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ).div(bnum(10).pow(poolPairData.decimalsOut)); @@ -504,7 +511,7 @@ export function _tokenInForExactTokenOut( viewRawAmount( // poolPairData.tokenOut as TokenSymbol, targetAmountInNumeraire, - bnum(poolPairData.decimalsOut), + poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ).div(bnum(10).pow(poolPairData.decimalsOut)); // must be the token out @@ -534,7 +541,7 @@ export function _tokenInForExactTokenOut( return viewRawAmount( _amtWithFee.abs(), - bnum(poolPairData.decimalsIn), + poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ).div(bnum(10).pow(poolPairData.decimalsIn)); // must be the token out @@ -570,7 +577,7 @@ export const spotPriceBeforeSwap = ( .div(inputAmountInNumeraire.abs()) .times(parsedFxPoolData.baseTokenRate) .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals.toNumber(), + poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); return val; @@ -628,14 +635,14 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .div(targetAmountInNumeraire.abs()) .times(currentRate) .decimalPlaces( - poolPairData.tokenInfxOracleDecimals.toNumber(), + poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { return currentRate .times(bnum(1).minus(epsilon)) .decimalPlaces( - poolPairData.tokenInfxOracleDecimals.toNumber(), + poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } @@ -657,14 +664,14 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( return ratioOfOutputAndInput .times(currentRate) .decimalPlaces( - poolPairData.tokenInfxOracleDecimals.toNumber(), + poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { return currentRate .times(bnum(1).minus(epsilon)) .decimalPlaces( - poolPairData.tokenInfxOracleDecimals.toNumber(), + poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } @@ -720,7 +727,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(outputAmount.times(epsilon.plus(1)).abs()) .times(currentRate) .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals.toNumber(), + poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { @@ -728,7 +735,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( return currentRate .times(bnum(1).minus(epsilon)) .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals.toNumber(), + poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } @@ -746,14 +753,14 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(outputAmount.times(epsilon.plus(1)).abs()) .times(currentRate) .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals.toNumber(), + poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { return currentRate .times(bnum(1).minus(epsilon)) .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals.toNumber(), + poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } diff --git a/test/xaveFxPool.math.spec.ts b/test/xaveFxPool.math.spec.ts index a32ca833..80afae85 100644 --- a/test/xaveFxPool.math.spec.ts +++ b/test/xaveFxPool.math.spec.ts @@ -10,9 +10,10 @@ import { } from '../src/pools/xaveFxPool/fxPoolMath'; context('xaveFxPool: fxMath functions', () => { - const tokenDecimals = bnum(6); - const tokenFxRateDecimals = bnum(8); + const tokenDecimals = 6; + const tokenFxRateDecimals = 8; const rate = bnum('74376600'); // 0.743766 + it(`should correctly return 'viewRawAmount'`, async () => { const rawAmount = viewRawAmount( bnum('10000'), @@ -24,6 +25,17 @@ context('xaveFxPool: fxMath functions', () => { expect(rawAmount.toString()).to.eq(expected); }); + it(`should correctly return large 'viewRawAmount'`, async () => { + const rawAmount = viewRawAmount( + bnum('10000').times(bnum(10).pow(18)), + tokenDecimals, + rate, + tokenFxRateDecimals + ); + const expected = '13445088912372977522500356294'; + expect(rawAmount.toString()).to.eq(expected); + }); + it(`should correctly return 'viewNumeraireAmount' values`, async () => { const numerarieAmount = viewNumeraireAmount( bnum('13445088912'), diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 47e9f2d1..caac961f 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -272,8 +272,8 @@ describe('xaveFxPool: fxPools stub test', () => { epsilon: bnum('0x01c6bf52634000'), tokenInLatestFXPrice: bnum('99963085000000'), tokenOutLatestFXPrice: bnum('74200489000000'), - tokenInfxOracleDecimals: bnum(8), - tokenOutfxOracleDecimals: bnum(8), + tokenInfxOracleDecimals: 8, + tokenOutfxOracleDecimals: 8, }; const sp = _spotPriceAfterSwapExactTokenInForTokenOut( poolPairData, From 5e537cca09e527876c244b888a3925402c2d1d28 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Mon, 26 Jun 2023 13:12:15 +0700 Subject: [PATCH 37/56] =?UTF-8?q?=F0=9F=90=BC=20calculateMicroFee=20is=20u?= =?UTF-8?q?sing=20BigNumber=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 85 +++++++++++-------- .../fxPool/fxPoolTestCases_43667355.json | 51 ----------- 2 files changed, 51 insertions(+), 85 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 34283e29..92aeab93 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -1,13 +1,14 @@ import { BigNumber as OldBigNumber, bnum } from '../../utils/bignumber'; import { FxPoolPairData } from './fxPool'; -import { BigNumber } from '@ethersproject/bignumber'; +import { BigNumber, parseFixed } from '@ethersproject/bignumber'; import { ONE as ONE_ETH } from '../../utils/basicOperations'; import { safeParseFixed } from '../../utils'; // Constants +export const ONE_36 = parseFixed('1', 36); export const CURVEMATH_MAX_DIFF = bnum('-0.000001000000000000024'); export const ONE_TO_THE_THIRTEEN_NUM = bnum('10000000000000'); -const CURVEMATH_MAX = bnum('0.25'); //CURVEMATH MAX from contract +const CURVEMATH_MAX_36 = parseFixed('0.25', 36); //CURVEMATH MAX from contract export enum CurveMathRevert { LowerHalt = 'CurveMath/lower-halt', @@ -259,44 +260,44 @@ export const viewNumeraireAmount = ( // Curve Math // calculations are from CurveMath.sol const calculateMicroFee = ( - _bal: OldBigNumber, - _ideal: OldBigNumber, - _beta: OldBigNumber, - _delta: OldBigNumber -): OldBigNumber => { + _bal: BigNumber, + _ideal: BigNumber, + _beta: BigNumber, + _delta: BigNumber +): BigNumber => { let _threshold, _feeMargin; - let fee_ = bnum(0); + let fee_ = BigNumber.from(0); if (_bal.lt(_ideal)) { - _threshold = _ideal.times(bnum(1).minus(_beta)); // CURVEMATH ONE + _threshold = _ideal.mul(ONE_36.sub(_beta)).div(ONE_36); if (_bal.lt(_threshold)) { - _feeMargin = _threshold.minus(_bal); - fee_ = _feeMargin.div(_ideal); - fee_ = fee_.times(_delta); + _feeMargin = _threshold.sub(_bal); + fee_ = _feeMargin.mul(ONE_36).div(_ideal); + fee_ = fee_.mul(_delta).div(ONE_36); - if (fee_.gt(CURVEMATH_MAX)) { - fee_ = CURVEMATH_MAX; + if (fee_.gt(CURVEMATH_MAX_36)) { + fee_ = CURVEMATH_MAX_36; } - fee_ = fee_.times(_feeMargin); + fee_ = fee_.mul(_feeMargin).div(ONE_36); } else { - fee_ = bnum(0); + fee_ = BigNumber.from(0); } } else { - _threshold = _ideal.times(_beta.plus(1)); // CURVEMATH_ONE + _threshold = _ideal.mul(_beta.add(ONE_36)).div(ONE_36); if (_bal.gt(_threshold)) { - _feeMargin = _bal.minus(_threshold); + _feeMargin = _bal.sub(_threshold); - fee_ = _feeMargin.div(_ideal); - fee_ = fee_.times(_delta); + fee_ = _feeMargin.mul(ONE_36).div(_ideal); + fee_ = fee_.mul(_delta).div(ONE_36); - if (fee_.gt(CURVEMATH_MAX)) fee_ = CURVEMATH_MAX; + if (fee_.gt(CURVEMATH_MAX_36)) fee_ = CURVEMATH_MAX_36; - fee_ = fee_.times(_feeMargin); + fee_ = fee_.mul(_feeMargin).div(ONE_36); } else { - fee_ = bnum(0); + fee_ = BigNumber.from(0); } } @@ -304,20 +305,20 @@ const calculateMicroFee = ( }; const calculateFee = ( - _gLiq: OldBigNumber, - _bals: OldBigNumber[], - _beta: OldBigNumber, - _delta: OldBigNumber, - _weights: OldBigNumber[] -): OldBigNumber => { + _gLiq: BigNumber, + _bals: BigNumber[], + _beta: BigNumber, + _delta: BigNumber, + _weights: BigNumber[] +): BigNumber => { const _length = _bals.length; - let psi_ = bnum(0); + let psi_ = BigNumber.from(0); for (let i = 0; i < _length; i++) { - const _ideal = _gLiq.times(_weights[i]); + const _ideal = _gLiq.mul(_weights[i]).div(ONE_36); // keep away from wei values like how the contract do it - psi_ = psi_.plus(calculateMicroFee(_bals[i], _ideal, _beta, _delta)); + psi_ = psi_.add(calculateMicroFee(_bals[i], _ideal, _beta, _delta)); } return psi_; @@ -343,12 +344,28 @@ const calculateTrade = ( outputAmt_ = _inputAmt.times(-1); - const _omega = calculateFee(_oGLiq, _oBals, beta, delta, _weights); + const _omega = bnum( + calculateFee( + safeParseFixed(_oGLiq.toString(), 36), + _oBals.map((d) => safeParseFixed(d.toString(), 36)), + safeParseFixed(beta.toString(), 36), + safeParseFixed(delta.toString(), 36), + _weights.map((d) => safeParseFixed(d.toString(), 36)) + ).toString() + ).div(bnum(10).pow(36)); let _psi: OldBigNumber; for (let i = 0; i < 32; i++) { - _psi = calculateFee(_nGLiq, _nBals, beta, delta, _weights); + _psi = bnum( + calculateFee( + safeParseFixed(_nGLiq.toString(), 36), + _nBals.map((d) => safeParseFixed(d.toString(), 36)), + safeParseFixed(beta.toString(), 36), + safeParseFixed(delta.toString(), 36), + _weights.map((d) => safeParseFixed(d.toString(), 36)) + ).toString() + ).div(bnum(10).pow(36)); const prevAmount = outputAmt_; diff --git a/test/testData/fxPool/fxPoolTestCases_43667355.json b/test/testData/fxPool/fxPoolTestCases_43667355.json index 7274b69d..5538a49b 100644 --- a/test/testData/fxPool/fxPoolTestCases_43667355.json +++ b/test/testData/fxPool/fxPoolTestCases_43667355.json @@ -72,56 +72,5 @@ "expectedSpotPriceAfterSwap": "0.7407784", "expectedSwapOutput": "269980.072169", "expectedDerivativeSpotPriceAfterSwap": "0.00150191538490217708" - }, - - { - "testNo": "7", - "description": "Swap within Beta Region: OriginSwap/_exactTokenInForTokenOut XSGD > ? USDC", - "swapType": "OriginSwap", - "givenAmount": "270000", - "tokenIn": "XSGD", - "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.74076748", - "expectedSwapOutput": "200011.814393", - "expectedDerivativeSpotPriceAfterSwap": "0.00151663449534599789" - }, - { - "testNo": "8", - "description": "Swap Beyond Beta Region: TargetSwap / tokenInForExactTokenOut ? USDC > XSGD", - "swapType": "TargetSwap", - "givenAmount": "270000", - "tokenIn": "USDC", - "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.74189266", - "expectedSwapOutput": "200516.037466", - "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" - }, - - { - "testNo": "10", - "description": "Swap within Beta Region: TargetSwap / tokenInForExactTokenOut ? XSGD > USDC", - "swapType": "TargetSwap", - "givenAmount": "450000", - "tokenIn": "XSGD", - "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.66970695", - "expectedSwapOutput": "671920.250617", - "expectedDerivativeSpotPriceAfterSwap": "0.09729939908018499603" - }, - - { - "testNo": "12", - "description": "Swap beyond Alpha Region: TargetSwap / tokenInForExactTokenOut ? USDC > XSGD", - "swapType": "TargetSwap", - "givenAmount": "610000", - "tokenIn": "XSGD", - "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "CurveMath/lower-halt", - "expectedSwapOutput": "CurveMath/lower-halt", - "expectedDerivativeSpotPriceAfterSwap": "CurveMath/lower-halt" } ] From 2f8f87b1cde45714afe752982a897ee1ab916c72 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Mon, 26 Jun 2023 13:21:50 +0700 Subject: [PATCH 38/56] =?UTF-8?q?=F0=9F=98=AE=20oops=20I=20deleted=20some?= =?UTF-8?q?=20of=20the=20test=20cases=20by=20mistake.=20fixed=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fxPool/fxPoolTestCases_43667355.json | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test/testData/fxPool/fxPoolTestCases_43667355.json b/test/testData/fxPool/fxPoolTestCases_43667355.json index 5538a49b..7274b69d 100644 --- a/test/testData/fxPool/fxPoolTestCases_43667355.json +++ b/test/testData/fxPool/fxPoolTestCases_43667355.json @@ -72,5 +72,56 @@ "expectedSpotPriceAfterSwap": "0.7407784", "expectedSwapOutput": "269980.072169", "expectedDerivativeSpotPriceAfterSwap": "0.00150191538490217708" + }, + + { + "testNo": "7", + "description": "Swap within Beta Region: OriginSwap/_exactTokenInForTokenOut XSGD > ? USDC", + "swapType": "OriginSwap", + "givenAmount": "270000", + "tokenIn": "XSGD", + "tokenOut": "USDC", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.74076748", + "expectedSwapOutput": "200011.814393", + "expectedDerivativeSpotPriceAfterSwap": "0.00151663449534599789" + }, + { + "testNo": "8", + "description": "Swap Beyond Beta Region: TargetSwap / tokenInForExactTokenOut ? USDC > XSGD", + "swapType": "TargetSwap", + "givenAmount": "270000", + "tokenIn": "USDC", + "tokenOut": "XSGD", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.74189266", + "expectedSwapOutput": "200516.037466", + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" + }, + + { + "testNo": "10", + "description": "Swap within Beta Region: TargetSwap / tokenInForExactTokenOut ? XSGD > USDC", + "swapType": "TargetSwap", + "givenAmount": "450000", + "tokenIn": "XSGD", + "tokenOut": "USDC", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "0.66970695", + "expectedSwapOutput": "671920.250617", + "expectedDerivativeSpotPriceAfterSwap": "0.09729939908018499603" + }, + + { + "testNo": "12", + "description": "Swap beyond Alpha Region: TargetSwap / tokenInForExactTokenOut ? USDC > XSGD", + "swapType": "TargetSwap", + "givenAmount": "610000", + "tokenIn": "XSGD", + "tokenOut": "USDC", + "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceAfterSwap": "CurveMath/lower-halt", + "expectedSwapOutput": "CurveMath/lower-halt", + "expectedDerivativeSpotPriceAfterSwap": "CurveMath/lower-halt" } ] From 94b26165180ad09e837db1688629531e98e7cfb3 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Mon, 26 Jun 2023 17:45:56 +0700 Subject: [PATCH 39/56] =?UTF-8?q?=F0=9F=90=AC=20calculateTrade=20uses=20Bi?= =?UTF-8?q?gNumber=20internally?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 130 ++++++++++++++--------------- 1 file changed, 64 insertions(+), 66 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 92aeab93..6de04c46 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -6,8 +6,8 @@ import { safeParseFixed } from '../../utils'; // Constants export const ONE_36 = parseFixed('1', 36); -export const CURVEMATH_MAX_DIFF = bnum('-0.000001000000000000024'); -export const ONE_TO_THE_THIRTEEN_NUM = bnum('10000000000000'); +export const CURVEMATH_MAX_DIFF_36 = parseFixed('-0.000001000000000000024', 36); +export const ONE_TO_THE_THIRTEEN_NUM_36 = parseFixed('10000000000000', 36); const CURVEMATH_MAX_36 = parseFixed('0.25', 36); //CURVEMATH MAX from contract export enum CurveMathRevert { @@ -334,60 +334,56 @@ const calculateTrade = ( _outputIndex: number, poolPairData: ParsedFxPoolData ): [OldBigNumber, OldBigNumber] => { - let outputAmt_; - const _weights: OldBigNumber[] = [bnum('0.5'), bnum('0.5')]; // const for now since all weights are 0.5 + const weights_: BigNumber[] = [ + safeParseFixed('0.5', 36), + safeParseFixed('0.5', 36), + ]; // const for now since all weights are 0.5 - const alpha = poolPairData.alpha; - const beta = poolPairData.beta; - const delta = poolPairData.delta; - const lambda = poolPairData.lambda; + const inputAmt_ = safeParseFixed(_inputAmt.toString(), 36); + const oGLiq_ = safeParseFixed(_oGLiq.toString(), 36); + let nGLiq_ = safeParseFixed(_nGLiq.toString(), 36); + const oBals_ = _oBals.map((d) => safeParseFixed(d.toString(), 36)); + const nBals_ = _nBals.map((d) => safeParseFixed(d.toString(), 36)); - outputAmt_ = _inputAmt.times(-1); + const alpha = safeParseFixed(poolPairData.alpha.toString(), 36); + const beta = safeParseFixed(poolPairData.beta.toString(), 36); + const delta = safeParseFixed(poolPairData.delta.toString(), 36); + const lambda = safeParseFixed(poolPairData.lambda.toString(), 36); - const _omega = bnum( - calculateFee( - safeParseFixed(_oGLiq.toString(), 36), - _oBals.map((d) => safeParseFixed(d.toString(), 36)), - safeParseFixed(beta.toString(), 36), - safeParseFixed(delta.toString(), 36), - _weights.map((d) => safeParseFixed(d.toString(), 36)) - ).toString() - ).div(bnum(10).pow(36)); + let outputAmt_ = inputAmt_.mul(-1); - let _psi: OldBigNumber; + const omega_ = calculateFee(oGLiq_, oBals_, beta, delta, weights_); + + let psi_: BigNumber; for (let i = 0; i < 32; i++) { - _psi = bnum( - calculateFee( - safeParseFixed(_nGLiq.toString(), 36), - _nBals.map((d) => safeParseFixed(d.toString(), 36)), - safeParseFixed(beta.toString(), 36), - safeParseFixed(delta.toString(), 36), - _weights.map((d) => safeParseFixed(d.toString(), 36)) - ).toString() - ).div(bnum(10).pow(36)); + psi_ = calculateFee(nGLiq_, nBals_, beta, delta, weights_); const prevAmount = outputAmt_; - outputAmt_ = _omega.lt(_psi) - ? _inputAmt.plus(_omega.minus(_psi)).times(-1) - : _inputAmt.plus(lambda.times(_omega.minus(_psi))).times(-1); + outputAmt_ = omega_.lt(psi_) + ? inputAmt_.add(omega_.sub(psi_)).mul(-1) + : inputAmt_.add(lambda.mul(omega_.sub(psi_)).div(ONE_36)).mul(-1); if ( outputAmt_ - .div(ONE_TO_THE_THIRTEEN_NUM) - .eq(prevAmount.div(ONE_TO_THE_THIRTEEN_NUM)) + .mul(ONE_36) + .div(ONE_TO_THE_THIRTEEN_NUM_36) + .eq(prevAmount.mul(ONE_36).div(ONE_TO_THE_THIRTEEN_NUM_36)) ) { - _nGLiq = _oGLiq.plus(_inputAmt).plus(outputAmt_); + nGLiq_ = oGLiq_.add(inputAmt_).add(outputAmt_); - _nBals[_outputIndex] = _oBals[_outputIndex].plus(outputAmt_); + nBals_[_outputIndex] = oBals_[_outputIndex].add(outputAmt_); // throws error already, removed if statement - enforceHalts(_oGLiq, _nGLiq, _oBals, _nBals, _weights, alpha); - enforceSwapInvariant(_oGLiq, _omega, _nGLiq, _psi); - return [outputAmt_, _nGLiq]; + enforceHalts(oGLiq_, nGLiq_, oBals_, nBals_, weights_, alpha); + enforceSwapInvariant(oGLiq_, omega_, nGLiq_, psi_); + return [ + bnum(outputAmt_.toString()).div(bnum(10).pow(36)), + bnum(nGLiq_.toString()).div(bnum(10).pow(36)), + ]; } else { - _nGLiq = _oGLiq.plus(_inputAmt).plus(outputAmt_); - _nBals[_outputIndex] = _oBals[_outputIndex].plus(outputAmt_); + nGLiq_ = oGLiq_.add(inputAmt_).add(outputAmt_); + nBals_[_outputIndex] = oBals_[_outputIndex].add(outputAmt_); } } @@ -396,47 +392,51 @@ const calculateTrade = ( // invariant enforcement const enforceHalts = ( - _oGLiq: OldBigNumber, - _nGLiq: OldBigNumber, - _oBals: OldBigNumber[], - _nBals: OldBigNumber[], - _weights: OldBigNumber[], - alpha: OldBigNumber + _oGLiq: BigNumber, + _nGLiq: BigNumber, + _oBals: BigNumber[], + _nBals: BigNumber[], + _weights: BigNumber[], + alpha: BigNumber ): boolean => { const _length = _nBals.length; const _alpha = alpha; for (let i = 0; i < _length; i++) { - const _nIdeal = _nGLiq.times(_weights[i]); + const _nIdeal = _nGLiq.mul(_weights[i]).div(ONE_36); if (_nBals[i].gt(_nIdeal)) { - const _upperAlpha = _alpha.plus(1); + const _upperAlpha = _alpha.add(ONE_36); - const _nHalt = _nIdeal.times(_upperAlpha); + const _nHalt = _nIdeal.mul(_upperAlpha).div(ONE_36); if (_nBals[i].gt(_nHalt)) { - const _oHalt = _oGLiq.times(_weights[i]).times(_upperAlpha); + const _oHalt = _oGLiq + .mul(_weights[i]) + .div(ONE_36) + .mul(_upperAlpha) + .div(ONE_36); if (_oBals[i].lt(_oHalt)) { throw new Error(CurveMathRevert.UpperHalt); } - if (_nBals[i].minus(_nHalt).gt(_oBals[i].minus(_oHalt))) { + if (_nBals[i].sub(_nHalt).gt(_oBals[i].sub(_oHalt))) { throw new Error(CurveMathRevert.UpperHalt); } } } else { - const _lowerAlpha = bnum(1).minus(_alpha); + const _lowerAlpha = ONE_36.sub(_alpha); - const _nHalt = _nIdeal.times(_lowerAlpha); + const _nHalt = _nIdeal.mul(_lowerAlpha).div(ONE_36); if (_nBals[i].lt(_nHalt)) { - let _oHalt = _oGLiq.times(_weights[i]); - _oHalt = _oHalt.times(_lowerAlpha); + let _oHalt = _oGLiq.mul(_weights[i]).div(ONE_36); + _oHalt = _oHalt.mul(_lowerAlpha).div(ONE_36); if (_oBals[i].gt(_oHalt)) { throw new Error(CurveMathRevert.LowerHalt); } - if (_nHalt.minus(_nBals[i]).gt(_oHalt.minus(_oBals[i]))) { + if (_nHalt.sub(_nBals[i]).gt(_oHalt.sub(_oBals[i]))) { throw new Error(CurveMathRevert.LowerHalt); } } @@ -446,19 +446,19 @@ const enforceHalts = ( }; const enforceSwapInvariant = ( - _oGLiq: OldBigNumber, - _omega: OldBigNumber, - _nGLiq: OldBigNumber, - _psi: OldBigNumber + _oGLiq: BigNumber, + _omega: BigNumber, + _nGLiq: BigNumber, + _psi: BigNumber ): boolean => { - const _nextUtil = _nGLiq.minus(_psi); + const _nextUtil = _nGLiq.sub(_psi); - const _prevUtil = _oGLiq.minus(_omega); + const _prevUtil = _oGLiq.sub(_omega); - const _diff = _nextUtil.minus(_prevUtil); + const _diff = _nextUtil.sub(_prevUtil); // from int128 private constant MAX_DIFF = -0x10C6F7A0B5EE converted to plain decimals - if (_diff.gt(0) || _diff.gte(CURVEMATH_MAX_DIFF)) { + if (_diff.gt(0) || _diff.gte(CURVEMATH_MAX_DIFF_36)) { return true; } else { throw new Error(CurveMathRevert.SwapInvariantViolation); @@ -710,10 +710,8 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( const _oGLiq = parsedFxPoolData._oGLiq; const _nBals = parsedFxPoolData._nBals; const currentRate = parsedFxPoolData.baseTokenRate; - const beta = parsedFxPoolData.beta; const epsilon = parsedFxPoolData.epsilon; - const _nGLiq = parsedFxPoolData._nGLiq; const _oBals = parsedFxPoolData._oBals; From 71ab58b8df751b399fedc72b421d0b7e23c37d8e Mon Sep 17 00:00:00 2001 From: andreiashu Date: Mon, 26 Jun 2023 19:07:33 +0700 Subject: [PATCH 40/56] =?UTF-8?q?=F0=9F=91=BD=20alpha,=20beta,=20delta,=20?= =?UTF-8?q?epsilon,=20viewRawAmount,=20viewNumeraire=20using=20BigNumber?= =?UTF-8?q?=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 136 ++++++++++++++++++++-------- test/xaveFxPool.integration.spec.ts | 16 ++-- 2 files changed, 104 insertions(+), 48 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 6de04c46..6f12dcc9 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -1,7 +1,6 @@ import { BigNumber as OldBigNumber, bnum } from '../../utils/bignumber'; import { FxPoolPairData } from './fxPool'; import { BigNumber, parseFixed } from '@ethersproject/bignumber'; -import { ONE as ONE_ETH } from '../../utils/basicOperations'; import { safeParseFixed } from '../../utils'; // Constants @@ -19,11 +18,11 @@ export enum CurveMathRevert { } interface ParsedFxPoolData { - alpha: OldBigNumber; - beta: OldBigNumber; - delta: OldBigNumber; - epsilon: OldBigNumber; - lambda: OldBigNumber; + alpha: BigNumber; + beta: BigNumber; + delta: BigNumber; + epsilon: BigNumber; + lambda: BigNumber; baseTokenRate: OldBigNumber; _oGLiq: OldBigNumber; _nGLiq: OldBigNumber; @@ -181,11 +180,11 @@ const getParsedFxPoolData = ( ); return { - alpha: poolPairData.alpha.div(bnum(10).pow(18)), - beta: poolPairData.beta.div(bnum(10).pow(18)), - delta: poolPairData.delta.div(bnum(10).pow(18)), - epsilon: poolPairData.epsilon.div(bnum(10).pow(18)), - lambda: poolPairData.lambda.div(bnum(10).pow(18)), + alpha: parseFixed(poolPairData.alpha.toString(), 18), + beta: parseFixed(poolPairData.beta.toString(), 18), + delta: parseFixed(poolPairData.delta.toString(), 18), + epsilon: parseFixed(poolPairData.epsilon.toString(), 18), + lambda: parseFixed(poolPairData.lambda.toString(), 18), baseTokenRate: baseTokenRate, _oGLiq: baseReserves.plus(usdcReserves), _nGLiq: baseReserves.plus(usdcReserves), @@ -223,14 +222,11 @@ export const viewRawAmount = ( ): OldBigNumber => { // solidity code `_amount.mulu(baseDecimals).mul(baseOracleDecimals).div(_rate); - const inAmount = BigInt( - safeParseFixed(_amount.toString(), tokenDecimals).toString() - ); - - const val = - (inAmount * BigInt(10 ** fxOracleDecimals) * ONE_ETH) / - BigInt(rate.toString()) / - ONE_ETH; + const val = safeParseFixed(_amount.toString(), tokenDecimals) + .mul(safeParseFixed('1', fxOracleDecimals)) + .mul(ONE_36) + .div(safeParseFixed(rate.toString(), 36)) + ; return bnum(val.toString()); }; @@ -250,9 +246,9 @@ export const viewNumeraireAmount = ( ): OldBigNumber => { // Solidity: _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); - const val = - (BigInt(_amount.toString()) * BigInt(rate.toString())) / - BigInt(10 ** fxOracleDecimals); + const val = safeParseFixed(_amount.toString(), 36).mul( + safeParseFixed(rate.toString(), 36) + ).div(ONE_36).div(ONE_36).div(safeParseFixed('1', fxOracleDecimals)); return bnum(val.toString()).div(bnum(10).pow(tokenDecimals)); }; @@ -345,10 +341,10 @@ const calculateTrade = ( const oBals_ = _oBals.map((d) => safeParseFixed(d.toString(), 36)); const nBals_ = _nBals.map((d) => safeParseFixed(d.toString(), 36)); - const alpha = safeParseFixed(poolPairData.alpha.toString(), 36); - const beta = safeParseFixed(poolPairData.beta.toString(), 36); - const delta = safeParseFixed(poolPairData.delta.toString(), 36); - const lambda = safeParseFixed(poolPairData.lambda.toString(), 36); + const alpha = poolPairData.alpha; + const beta = poolPairData.beta; + const delta = poolPairData.delta; + const lambda = poolPairData.lambda; let outputAmt_ = inputAmt_.mul(-1); @@ -504,7 +500,9 @@ export function _exactTokenInForTokenOut( throw new Error(CurveMathRevert.CannotSwap); } else { const epsilon = parsedFxPoolData.epsilon; - const _amtWithFee = _amt[0].times(bnum(1).minus(epsilon)); + const _amtWithFee = _amt[0].times( + bnum(1).minus(bnum(epsilon.toString()).div(bnum(10).pow(36))) + ); return viewRawAmount( _amtWithFee.abs(), @@ -590,7 +588,11 @@ export const spotPriceBeforeSwap = ( const val = outputAmountInNumeraire[0] .abs() - .times(bnum(1).minus(parsedFxPoolData.epsilon)) + .times( + bnum(1).minus( + bnum(parsedFxPoolData.epsilon.toString()).div(bnum(10).pow(36)) + ) + ) .div(inputAmountInNumeraire.abs()) .times(parsedFxPoolData.baseTokenRate) .decimalPlaces( @@ -629,9 +631,16 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( const outputAmount = outputAfterTrade[0]; - const maxBetaLimit = beta.plus(1).times('0.5').times(_oGLiq); + const maxBetaLimit = bnum(beta.toString()) + .div(bnum(10).pow(36)) + .plus(1) + .times('0.5') + .times(_oGLiq); - const minBetaLimit = bnum(1).minus(beta).times('0.5').times(_oGLiq); + const minBetaLimit = bnum(1) + .minus(bnum(beta.toString()).div(bnum(10).pow(36))) + .times('0.5') + .times(_oGLiq); if (isUSDC(poolPairData.tokenIn)) { // token[0] to token [1] in originswap @@ -647,7 +656,11 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( return amount.isZero() ? spotPriceBeforeSwap(amount, poolPairData) : outputAmount - .times(bnum(1).minus(epsilon)) + .times( + bnum(1).minus( + bnum(epsilon.toString()).div(bnum(10).pow(36)) + ) + ) .abs() .div(targetAmountInNumeraire.abs()) .times(currentRate) @@ -657,7 +670,11 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( ); } else { return currentRate - .times(bnum(1).minus(epsilon)) + .times( + bnum(1).minus( + bnum(epsilon.toString()).div(bnum(10).pow(36)) + ) + ) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -675,7 +692,11 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( return spotPriceBeforeSwap(amount, poolPairData); const ratioOfOutputAndInput = outputAmount - .times(bnum(1).minus(epsilon)) + .times( + bnum(1).minus( + bnum(epsilon.toString()).div(bnum(10).pow(36)) + ) + ) .abs() .div(targetAmountInNumeraire.abs()); return ratioOfOutputAndInput @@ -686,7 +707,11 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( ); } else { return currentRate - .times(bnum(1).minus(epsilon)) + .times( + bnum(1).minus( + bnum(epsilon.toString()).div(bnum(10).pow(36)) + ) + ) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -727,9 +752,16 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( const outputAmount = outputAfterTrade[0]; - const maxBetaLimit = beta.plus(1).times('0.5').times(_oGLiq); + const maxBetaLimit = bnum(beta.toString()) + .div(bnum(10).pow(36)) + .plus(1) + .times('0.5') + .times(_oGLiq); - const minBetaLimit = bnum(1).minus(beta).times('0.5').times(_oGLiq); + const minBetaLimit = bnum(1) + .minus(bnum(beta.toString()).div(bnum(10).pow(36))) + .times('0.5') + .times(_oGLiq); if (isUSDC(poolPairData.tokenIn)) { // token[0] to token [1] in originswap @@ -739,7 +771,15 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { return targetAmountInNumeraire .abs() - .div(outputAmount.times(epsilon.plus(1)).abs()) + .div( + outputAmount + .times( + bnum(epsilon.toString()) + .div(bnum(10).pow(36)) + .plus(1) + ) + .abs() + ) .times(currentRate) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, @@ -748,7 +788,11 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( } else { // rate * (1-epsilon) return currentRate - .times(bnum(1).minus(epsilon)) + .times( + bnum(1).minus( + bnum(epsilon.toString()).div(bnum(10).pow(36)) + ) + ) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -765,7 +809,15 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( if (isBeyondMinBeta && isBeyondMaxBeta) { return targetAmountInNumeraire .abs() - .div(outputAmount.times(epsilon.plus(1)).abs()) + .div( + outputAmount + .times( + bnum(epsilon.toString()) + .div(bnum(10).pow(36)) + .plus(1) + ) + .abs() + ) .times(currentRate) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, @@ -773,7 +825,11 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( ); } else { return currentRate - .times(bnum(1).minus(epsilon)) + .times( + bnum(1).minus( + bnum(epsilon.toString()).div(bnum(10).pow(36)) + ) + ) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN diff --git a/test/xaveFxPool.integration.spec.ts b/test/xaveFxPool.integration.spec.ts index 550da1d1..d98d24f1 100644 --- a/test/xaveFxPool.integration.spec.ts +++ b/test/xaveFxPool.integration.spec.ts @@ -113,12 +113,12 @@ describe('xaveFxPool: DAI-USDC integration (Mainnet) tests', () => { funds ); - expect(queryResult[0].toString()).to.eq( - swapInfo.swapAmount.toString() + expect(swapInfo.swapAmount.toString()).to.eq( + queryResult[0].toString() ); - expect(queryResult[1].abs().toString()).to.be.eq( - swapInfo.returnAmount.toString() + expect(swapInfo.returnAmount.toString()).to.be.eq( + queryResult[1].abs().toString() ); }); @@ -142,11 +142,11 @@ describe('xaveFxPool: DAI-USDC integration (Mainnet) tests', () => { funds ); - expect(queryResult[0].abs().toString()).to.be.eq( - swapInfo.returnAmount.toString() + expect(swapInfo.returnAmount.toString()).to.be.eq( + queryResult[0].abs().toString() ); - expect(queryResult[1].abs().toString()).to.eq( - swapInfo.swapAmount.toString() + expect(swapInfo.swapAmount.toString()).to.eq( + queryResult[1].abs().toString() ); }); }); From ac20365052bbbbbeeab1369978afe89250d4e262 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Tue, 27 Jun 2023 07:49:50 +0700 Subject: [PATCH 41/56] tokenOutLatestFXPrice and tokenInLatestFXPrice are BigNumber now --- src/pools/xaveFxPool/fxPool.ts | 20 +++--- src/pools/xaveFxPool/fxPoolMath.ts | 100 ++++++++++++++++------------- test/xaveFxPool.math.spec.ts | 5 +- test/xaveFxPool.spec.ts | 4 +- 4 files changed, 67 insertions(+), 62 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index cbe69f10..53d54b47 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -36,9 +36,9 @@ export type FxPoolPairData = PoolPairBase & { lambda: OldBigNumber; delta: OldBigNumber; epsilon: OldBigNumber; - tokenInLatestFXPrice: OldBigNumber; + tokenInLatestFXPrice: BigNumber; tokenInfxOracleDecimals: number; - tokenOutLatestFXPrice: OldBigNumber; + tokenOutLatestFXPrice: BigNumber; tokenOutfxOracleDecimals: number; }; @@ -160,17 +160,13 @@ export class FxPool implements PoolBase { lambda: this.lambda, delta: this.delta, epsilon: this.epsilon, - tokenInLatestFXPrice: bnum( - parseFixed( - tI.token.latestFXPrice, - tI.token.fxOracleDecimals - ).toString() + tokenInLatestFXPrice: parseFixed( + tI.token.latestFXPrice, + tI.token.fxOracleDecimals ), // decimals is formatted from subgraph in rate we get from the chainlink oracle - tokenOutLatestFXPrice: bnum( - parseFixed( - tO.token.latestFXPrice, - tO.token.fxOracleDecimals - ).toString() + tokenOutLatestFXPrice: parseFixed( + tO.token.latestFXPrice, + tO.token.fxOracleDecimals ), // decimals is formatted from subgraph in rate we get from the chainlink oracle tokenInfxOracleDecimals: tI.token.fxOracleDecimals, tokenOutfxOracleDecimals: tO.token.fxOracleDecimals, diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 6f12dcc9..1b654e4d 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -23,7 +23,7 @@ interface ParsedFxPoolData { delta: BigNumber; epsilon: BigNumber; lambda: BigNumber; - baseTokenRate: OldBigNumber; + baseTokenRate: BigNumber; _oGLiq: OldBigNumber; _nGLiq: OldBigNumber; _oBals: OldBigNumber[]; @@ -58,7 +58,9 @@ const calculateGivenAmountInNumeraire = ( if (isOriginSwap) { // tokenIn is given calculatedNumeraireAmount = viewNumeraireAmount( - amount.times(bnum(10).pow(poolPairData.decimalsIn)), + BigNumber.from( + amount.times(bnum(10).pow(poolPairData.decimalsIn)).toString() + ), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals @@ -66,7 +68,9 @@ const calculateGivenAmountInNumeraire = ( } else { // tokenOut is given calculatedNumeraireAmount = viewNumeraireAmount( - amount.times(bnum(10).pow(poolPairData.decimalsOut)), + BigNumber.from( + amount.times(bnum(10).pow(poolPairData.decimalsOut)).toString() + ), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals @@ -76,15 +80,6 @@ const calculateGivenAmountInNumeraire = ( return calculatedNumeraireAmount; }; -/** - * Convert from an Ethersjs BigNumber to a bignumber.js BigNumber - * @param amount BigNumber - * @returns OldBigNumber - */ -const EthersBNToOldBn = (amount: BigNumber): OldBigNumber => { - return bnum(amount.toString()); -}; - export const poolBalancesToNumeraire = ( poolPairData: FxPoolPairData ): ReservesInNumeraire => { @@ -94,27 +89,27 @@ export const poolBalancesToNumeraire = ( // amount * rate / 10^poolPairData.decimalsIn -> rate: (_rate / 10^fxOracleDecimals) // _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); tokenInNumeraire = viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceIn), + poolPairData.balanceIn, poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ); tokenOutNumeraire = viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceOut), + poolPairData.balanceOut, poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ); } else { tokenInNumeraire = viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceOut), + poolPairData.balanceOut, poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ); tokenOutNumeraire = viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceIn), + poolPairData.balanceIn, poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals @@ -136,13 +131,13 @@ const getParsedFxPoolData = ( // reserves are in raw amount, they converted to numeraire const baseReserves = isUSDC(poolPairData.tokenIn) ? viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceOut), + poolPairData.balanceOut, poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ) : viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceIn), + poolPairData.balanceIn, poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals @@ -151,13 +146,13 @@ const getParsedFxPoolData = ( // reserves are not in wei const usdcReserves = isUSDC(poolPairData.tokenIn) ? viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceIn), + poolPairData.balanceIn, poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ) : viewNumeraireAmount( - EthersBNToOldBn(poolPairData.balanceOut), + poolPairData.balanceOut, poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals @@ -165,12 +160,16 @@ const getParsedFxPoolData = ( // rate is converted from chainlink to the actual rate in decimals const baseTokenRate = isUSDC(poolPairData.tokenIn) - ? poolPairData.tokenOutLatestFXPrice.div( - bnum(10).pow(poolPairData.tokenOutfxOracleDecimals) - ) - : poolPairData.tokenInLatestFXPrice.div( - bnum(10).pow(poolPairData.tokenInfxOracleDecimals) - ); + ? poolPairData.tokenOutLatestFXPrice + .mul(ONE_36) + .div( + BigNumber.from(10).pow(poolPairData.tokenOutfxOracleDecimals) + ) + : poolPairData.tokenInLatestFXPrice + .mul(ONE_36) + .div( + BigNumber.from(10).pow(poolPairData.tokenInfxOracleDecimals) + ); // given amount in or out converted to numeraire const givenAmountInNumeraire = calculateGivenAmountInNumeraire( @@ -217,17 +216,15 @@ const getParsedFxPoolData = ( export const viewRawAmount = ( _amount: OldBigNumber, // numeraire tokenDecimals: number, - rate: OldBigNumber, // wei + rate: BigNumber, // wei fxOracleDecimals: number ): OldBigNumber => { // solidity code `_amount.mulu(baseDecimals).mul(baseOracleDecimals).div(_rate); const val = safeParseFixed(_amount.toString(), tokenDecimals) - .mul(safeParseFixed('1', fxOracleDecimals)) - .mul(ONE_36) - .div(safeParseFixed(rate.toString(), 36)) - ; - + .mul(safeParseFixed('1', fxOracleDecimals)) + .mul(ONE_36) + .div(safeParseFixed(rate.toString(), 36)); return bnum(val.toString()); }; @@ -239,16 +236,18 @@ export const viewRawAmount = ( * @returns amount in numeraire (ie. user friendly decimals) */ export const viewNumeraireAmount = ( - _amount: OldBigNumber, // wei + _amount: BigNumber, // wei tokenDecimals: number, - rate: OldBigNumber, // wei + rate: BigNumber, // wei fxOracleDecimals: number ): OldBigNumber => { // Solidity: _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); - const val = safeParseFixed(_amount.toString(), 36).mul( - safeParseFixed(rate.toString(), 36) - ).div(ONE_36).div(ONE_36).div(safeParseFixed('1', fxOracleDecimals)); + const val = safeParseFixed(_amount.toString(), 36) + .mul(safeParseFixed(rate.toString(), 36)) + .div(ONE_36) + .div(ONE_36) + .div(safeParseFixed('1', fxOracleDecimals)); return bnum(val.toString()).div(bnum(10).pow(tokenDecimals)); }; @@ -594,7 +593,8 @@ export const spotPriceBeforeSwap = ( ) ) .div(inputAmountInNumeraire.abs()) - .times(parsedFxPoolData.baseTokenRate) + .times(parsedFxPoolData.baseTokenRate.toString()) + .div(ONE_36.toString()) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -663,13 +663,15 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( ) .abs() .div(targetAmountInNumeraire.abs()) - .times(currentRate) + .times(currentRate.toString()) + .div(ONE_36.toString()) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { - return currentRate + return bnum(currentRate.toString()) + .div(ONE_36.toString()) .times( bnum(1).minus( bnum(epsilon.toString()).div(bnum(10).pow(36)) @@ -700,13 +702,15 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .abs() .div(targetAmountInNumeraire.abs()); return ratioOfOutputAndInput - .times(currentRate) + .times(currentRate.toString()) + .div(ONE_36.toString()) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { - return currentRate + return bnum(currentRate.toString()) + .div(ONE_36.toString()) .times( bnum(1).minus( bnum(epsilon.toString()).div(bnum(10).pow(36)) @@ -780,14 +784,16 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( ) .abs() ) - .times(currentRate) + .times(currentRate.toString()) + .div(ONE_36.toString()) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { // rate * (1-epsilon) - return currentRate + return bnum(currentRate.toString()) + .div(ONE_36.toString()) .times( bnum(1).minus( bnum(epsilon.toString()).div(bnum(10).pow(36)) @@ -818,13 +824,15 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( ) .abs() ) - .times(currentRate) + .times(currentRate.toString()) + .div(ONE_36.toString()) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { - return currentRate + return bnum(currentRate.toString()) + .div(ONE_36.toString()) .times( bnum(1).minus( bnum(epsilon.toString()).div(bnum(10).pow(36)) diff --git a/test/xaveFxPool.math.spec.ts b/test/xaveFxPool.math.spec.ts index 80afae85..9e72815b 100644 --- a/test/xaveFxPool.math.spec.ts +++ b/test/xaveFxPool.math.spec.ts @@ -8,11 +8,12 @@ import { viewRawAmount, viewNumeraireAmount, } from '../src/pools/xaveFxPool/fxPoolMath'; +import { BigNumber } from '@ethersproject/bignumber'; context('xaveFxPool: fxMath functions', () => { const tokenDecimals = 6; const tokenFxRateDecimals = 8; - const rate = bnum('74376600'); // 0.743766 + const rate = BigNumber.from('74376600'); // 0.743766 it(`should correctly return 'viewRawAmount'`, async () => { const rawAmount = viewRawAmount( @@ -38,7 +39,7 @@ context('xaveFxPool: fxMath functions', () => { it(`should correctly return 'viewNumeraireAmount' values`, async () => { const numerarieAmount = viewNumeraireAmount( - bnum('13445088912'), + BigNumber.from('13445088912'), tokenDecimals, rate, tokenFxRateDecimals diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index caac961f..56945db2 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -270,8 +270,8 @@ describe('xaveFxPool: fxPools stub test', () => { lambda: bnum('0x0429d069189e0000'), delta: bnum('0x03cb71f51fc55800'), epsilon: bnum('0x01c6bf52634000'), - tokenInLatestFXPrice: bnum('99963085000000'), - tokenOutLatestFXPrice: bnum('74200489000000'), + tokenInLatestFXPrice: BigNumber.from('99963085000000'), + tokenOutLatestFXPrice: BigNumber.from('74200489000000'), tokenInfxOracleDecimals: 8, tokenOutfxOracleDecimals: 8, }; From a3d7557fc382680aeed9359959d0786d21c89dc9 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Tue, 27 Jun 2023 08:02:32 +0700 Subject: [PATCH 42/56] =?UTF-8?q?=E2=9A=BD=20amount=20->=20amount=5F36=20[?= =?UTF-8?q?wip]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 64 +++++++++++++++++++----------- test/xaveFxPool.math.spec.ts | 3 +- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 1b654e4d..025d4702 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -51,16 +51,14 @@ const isUSDC = (address: string) => { const calculateGivenAmountInNumeraire = ( isOriginSwap: boolean, poolPairData: FxPoolPairData, - amount: OldBigNumber + amount_36: BigNumber ) => { let calculatedNumeraireAmount; if (isOriginSwap) { // tokenIn is given calculatedNumeraireAmount = viewNumeraireAmount( - BigNumber.from( - amount.times(bnum(10).pow(poolPairData.decimalsIn)).toString() - ), + safeParseFixed(amount_36.toString(), poolPairData.decimalsIn), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals @@ -68,9 +66,7 @@ const calculateGivenAmountInNumeraire = ( } else { // tokenOut is given calculatedNumeraireAmount = viewNumeraireAmount( - BigNumber.from( - amount.times(bnum(10).pow(poolPairData.decimalsOut)).toString() - ), + safeParseFixed(amount_36.toString(), poolPairData.decimalsOut), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals @@ -89,27 +85,27 @@ export const poolBalancesToNumeraire = ( // amount * rate / 10^poolPairData.decimalsIn -> rate: (_rate / 10^fxOracleDecimals) // _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); tokenInNumeraire = viewNumeraireAmount( - poolPairData.balanceIn, + safeParseFixed(poolPairData.balanceIn.toString(), 36), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ); tokenOutNumeraire = viewNumeraireAmount( - poolPairData.balanceOut, + safeParseFixed(poolPairData.balanceOut.toString(), 36), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ); } else { tokenInNumeraire = viewNumeraireAmount( - poolPairData.balanceOut, + safeParseFixed(poolPairData.balanceOut.toString(), 36), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ); tokenOutNumeraire = viewNumeraireAmount( - poolPairData.balanceIn, + safeParseFixed(poolPairData.balanceIn.toString(), 36), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals @@ -124,20 +120,20 @@ export const poolBalancesToNumeraire = ( }; // everything is in order of USDC, base token const getParsedFxPoolData = ( - amount: OldBigNumber, + amount_36: BigNumber, poolPairData: FxPoolPairData, isOriginSwap: boolean ): ParsedFxPoolData => { // reserves are in raw amount, they converted to numeraire const baseReserves = isUSDC(poolPairData.tokenIn) ? viewNumeraireAmount( - poolPairData.balanceOut, + safeParseFixed(poolPairData.balanceOut.toString(), 36), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ) : viewNumeraireAmount( - poolPairData.balanceIn, + safeParseFixed(poolPairData.balanceIn.toString(), 36), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals @@ -146,13 +142,13 @@ const getParsedFxPoolData = ( // reserves are not in wei const usdcReserves = isUSDC(poolPairData.tokenIn) ? viewNumeraireAmount( - poolPairData.balanceIn, + safeParseFixed(poolPairData.balanceIn.toString(), 36), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ) : viewNumeraireAmount( - poolPairData.balanceOut, + safeParseFixed(poolPairData.balanceOut.toString(), 36), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals @@ -175,7 +171,7 @@ const getParsedFxPoolData = ( const givenAmountInNumeraire = calculateGivenAmountInNumeraire( isOriginSwap, poolPairData, - amount + amount_36 ); return { @@ -236,14 +232,14 @@ export const viewRawAmount = ( * @returns amount in numeraire (ie. user friendly decimals) */ export const viewNumeraireAmount = ( - _amount: BigNumber, // wei + amount_36: BigNumber, // wei tokenDecimals: number, rate: BigNumber, // wei fxOracleDecimals: number ): OldBigNumber => { // Solidity: _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); - const val = safeParseFixed(_amount.toString(), 36) + const val = amount_36 .mul(safeParseFixed(rate.toString(), 36)) .div(ONE_36) .div(ONE_36) @@ -467,7 +463,11 @@ export function _exactTokenInForTokenOut( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber { - const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, true); + const parsedFxPoolData = getParsedFxPoolData( + safeParseFixed(amount.toString(), 36), + poolPairData, + true + ); const targetAmountInNumeraire = parsedFxPoolData.givenAmountInNumeraire; @@ -517,7 +517,11 @@ export function _tokenInForExactTokenOut( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber { - const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, false); + const parsedFxPoolData = getParsedFxPoolData( + safeParseFixed(amount.toString(), 36), + poolPairData, + false + ); const targetAmountInNumeraire = parsedFxPoolData.givenAmountInNumeraire.times(-1); @@ -568,7 +572,11 @@ export const spotPriceBeforeSwap = ( ): OldBigNumber => { // input amount 1 XSGD to get the output in USDC const inputAmountInNumeraire = bnum(1); - const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, true); + const parsedFxPoolData = getParsedFxPoolData( + safeParseFixed(amount.toString(), 36), + poolPairData, + true + ); const _oGLiq = parsedFxPoolData._oGLiq; const _nGLiq = parsedFxPoolData._nGLiq; @@ -607,7 +615,11 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( poolPairData: FxPoolPairData, amount: OldBigNumber ): OldBigNumber => { - const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, true); + const parsedFxPoolData = getParsedFxPoolData( + safeParseFixed(amount.toString(), 36), + poolPairData, + true + ); const targetAmountInNumeraire = parsedFxPoolData.givenAmountInNumeraire; @@ -731,7 +743,11 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( poolPairData: FxPoolPairData, amount: OldBigNumber ): OldBigNumber => { - const parsedFxPoolData = getParsedFxPoolData(amount, poolPairData, false); + const parsedFxPoolData = getParsedFxPoolData( + safeParseFixed(amount.toString(), 36), + poolPairData, + false + ); const targetAmountInNumeraire = parsedFxPoolData.givenAmountInNumeraire.times(-1); diff --git a/test/xaveFxPool.math.spec.ts b/test/xaveFxPool.math.spec.ts index 9e72815b..81c8e018 100644 --- a/test/xaveFxPool.math.spec.ts +++ b/test/xaveFxPool.math.spec.ts @@ -9,6 +9,7 @@ import { viewNumeraireAmount, } from '../src/pools/xaveFxPool/fxPoolMath'; import { BigNumber } from '@ethersproject/bignumber'; +import { safeParseFixed } from '../src/utils'; context('xaveFxPool: fxMath functions', () => { const tokenDecimals = 6; @@ -39,7 +40,7 @@ context('xaveFxPool: fxMath functions', () => { it(`should correctly return 'viewNumeraireAmount' values`, async () => { const numerarieAmount = viewNumeraireAmount( - BigNumber.from('13445088912'), + safeParseFixed('13445088912', 36), tokenDecimals, rate, tokenFxRateDecimals From 26737342777f5d8b0238bed77b9879634d00db2c Mon Sep 17 00:00:00 2001 From: andreiashu Date: Tue, 27 Jun 2023 08:32:04 +0700 Subject: [PATCH 43/56] =?UTF-8?q?=F0=9F=8E=BB=20viewRawAmount=20amount=20-?= =?UTF-8?q?>=20amount=5F36?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 6 +++--- src/pools/xaveFxPool/fxPoolMath.ts | 15 ++++++++------- test/xaveFxPool.math.spec.ts | 7 +++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 53d54b47..c8b22dbd 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -4,7 +4,7 @@ import { Zero } from '@ethersproject/constants'; import { BigNumber as OldBigNumber, ZERO, bnum } from '../../utils/bignumber'; import { parseFixedCurveParam } from './parseFixedCurveParam'; -import { isSameAddress } from '../../utils'; +import { isSameAddress, safeParseFixed } from '../../utils'; import { universalNormalizedLiquidity } from '../liquidity'; import { PoolBase, @@ -223,7 +223,7 @@ export class FxPool implements PoolBase { ); return viewRawAmount( - maxLimitAmount, + safeParseFixed(maxLimitAmount.toString(), 36), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals @@ -234,7 +234,7 @@ export class FxPool implements PoolBase { ); return viewRawAmount( - maxLimitAmount, + safeParseFixed(maxLimitAmount.toString(), 36), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 025d4702..33afc49f 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -210,14 +210,15 @@ const getParsedFxPoolData = ( * @returns amount in wei */ export const viewRawAmount = ( - _amount: OldBigNumber, // numeraire + amount_36: BigNumber, tokenDecimals: number, rate: BigNumber, // wei fxOracleDecimals: number ): OldBigNumber => { - // solidity code `_amount.mulu(baseDecimals).mul(baseOracleDecimals).div(_rate); + // solidity code `amount.mulu(baseDecimals).mul(baseOracleDecimals).div(_rate); - const val = safeParseFixed(_amount.toString(), tokenDecimals) + const val = safeParseFixed(amount_36.toString(), tokenDecimals) + .div(ONE_36) .mul(safeParseFixed('1', fxOracleDecimals)) .mul(ONE_36) .div(safeParseFixed(rate.toString(), 36)); @@ -473,7 +474,7 @@ export function _exactTokenInForTokenOut( if (poolPairData.tokenIn === poolPairData.tokenOut) { return viewRawAmount( - targetAmountInNumeraire, + safeParseFixed(targetAmountInNumeraire.toString(), 36), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals @@ -504,7 +505,7 @@ export function _exactTokenInForTokenOut( ); return viewRawAmount( - _amtWithFee.abs(), + safeParseFixed(_amtWithFee.abs().toString(), 36), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals @@ -528,7 +529,7 @@ export function _tokenInForExactTokenOut( if (poolPairData.tokenIn === poolPairData.tokenOut) { viewRawAmount( // poolPairData.tokenOut as TokenSymbol, - targetAmountInNumeraire, + safeParseFixed(targetAmountInNumeraire.toString(), 36), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals @@ -558,7 +559,7 @@ export function _tokenInForExactTokenOut( const _amtWithFee = _amt[0].times(epsilon.plus(1)); // fee retained by the pool return viewRawAmount( - _amtWithFee.abs(), + safeParseFixed(_amtWithFee.abs().toString(), 36), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals diff --git a/test/xaveFxPool.math.spec.ts b/test/xaveFxPool.math.spec.ts index 81c8e018..ed2e6e2f 100644 --- a/test/xaveFxPool.math.spec.ts +++ b/test/xaveFxPool.math.spec.ts @@ -2,7 +2,6 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires require('dotenv').config(); import { expect } from 'chai'; -import { bnum } from '../src/utils/bignumber'; import { viewRawAmount, @@ -18,7 +17,7 @@ context('xaveFxPool: fxMath functions', () => { it(`should correctly return 'viewRawAmount'`, async () => { const rawAmount = viewRawAmount( - bnum('10000'), + safeParseFixed('10000', 36), tokenDecimals, rate, tokenFxRateDecimals @@ -29,12 +28,12 @@ context('xaveFxPool: fxMath functions', () => { it(`should correctly return large 'viewRawAmount'`, async () => { const rawAmount = viewRawAmount( - bnum('10000').times(bnum(10).pow(18)), + safeParseFixed('10000', 45), tokenDecimals, rate, tokenFxRateDecimals ); - const expected = '13445088912372977522500356294'; + const expected = '13445088912372977522'; expect(rawAmount.toString()).to.eq(expected); }); From 83cc506ac453191155a7ab72d3a63815ec8b8029 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Tue, 27 Jun 2023 13:45:09 +0700 Subject: [PATCH 44/56] ParsedFxPoolData uses BigNumber --- src/pools/xaveFxPool/fxPool.ts | 25 ++- src/pools/xaveFxPool/fxPoolMath.ts | 282 ++++++++++++++--------------- test/xaveFxPool.math.spec.ts | 4 +- 3 files changed, 157 insertions(+), 154 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index c8b22dbd..35f5a347 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -23,6 +23,7 @@ import { _spotPriceAfterSwapExactTokenInForTokenOut, _spotPriceAfterSwapTokenInForExactTokenOut, _tokenInForExactTokenOut, + ONE_36, } from './fxPoolMath'; type FxPoolToken = Pick< @@ -210,31 +211,35 @@ export class FxPool implements PoolBase { try { const parsedReserves = poolBalancesToNumeraire(poolPairData); - const alphaValue = poolPairData.alpha.div(bnum(10).pow(18)); + const alphaValue = safeParseFixed( + poolPairData.alpha.toString(), + 18 + ); const maxLimit = alphaValue - .plus(1) - .times(parsedReserves._oGLiq) - .times('0.5'); + .add(ONE_36) + .mul(parsedReserves._oGLiq_36) + .div(ONE_36) + .div(2); if (swapType === SwapTypes.SwapExactIn) { - const maxLimitAmount = maxLimit.minus( - parsedReserves.tokenInReservesInNumeraire + const maxLimitAmount_36 = maxLimit.sub( + parsedReserves.tokenInReservesInNumeraire_36.toString() ); return viewRawAmount( - safeParseFixed(maxLimitAmount.toString(), 36), + maxLimitAmount_36, poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ).div(bnum(10).pow(poolPairData.decimalsIn)); } else { - const maxLimitAmount = maxLimit.minus( - parsedReserves.tokenOutReservesInNumeraire + const maxLimitAmount_36 = maxLimit.sub( + parsedReserves.tokenOutReservesInNumeraire_36 ); return viewRawAmount( - safeParseFixed(maxLimitAmount.toString(), 36), + maxLimitAmount_36, poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 33afc49f..484d4a1c 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -23,18 +23,18 @@ interface ParsedFxPoolData { delta: BigNumber; epsilon: BigNumber; lambda: BigNumber; - baseTokenRate: BigNumber; - _oGLiq: OldBigNumber; - _nGLiq: OldBigNumber; - _oBals: OldBigNumber[]; - _nBals: OldBigNumber[]; - givenAmountInNumeraire: OldBigNumber; + baseTokenRate_36: BigNumber; + _oGLiq_36: BigNumber; + _nGLiq_36: BigNumber; + _oBals_36: BigNumber[]; + _nBals_36: BigNumber[]; + givenAmountInNumeraire_36: BigNumber; } interface ReservesInNumeraire { - tokenInReservesInNumeraire: OldBigNumber; - tokenOutReservesInNumeraire: OldBigNumber; - _oGLiq: OldBigNumber; + tokenInReservesInNumeraire_36: BigNumber; + tokenOutReservesInNumeraire_36: BigNumber; + _oGLiq_36: BigNumber; } const isUSDC = (address: string) => { @@ -52,12 +52,12 @@ const calculateGivenAmountInNumeraire = ( isOriginSwap: boolean, poolPairData: FxPoolPairData, amount_36: BigNumber -) => { - let calculatedNumeraireAmount; +): BigNumber => { + let calculatedNumeraireAmount_36: BigNumber; if (isOriginSwap) { // tokenIn is given - calculatedNumeraireAmount = viewNumeraireAmount( + calculatedNumeraireAmount_36 = viewNumeraireAmount( safeParseFixed(amount_36.toString(), poolPairData.decimalsIn), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, @@ -65,7 +65,7 @@ const calculateGivenAmountInNumeraire = ( ); } else { // tokenOut is given - calculatedNumeraireAmount = viewNumeraireAmount( + calculatedNumeraireAmount_36 = viewNumeraireAmount( safeParseFixed(amount_36.toString(), poolPairData.decimalsOut), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, @@ -73,13 +73,13 @@ const calculateGivenAmountInNumeraire = ( ); } - return calculatedNumeraireAmount; + return calculatedNumeraireAmount_36; }; export const poolBalancesToNumeraire = ( poolPairData: FxPoolPairData ): ReservesInNumeraire => { - let tokenInNumeraire, tokenOutNumeraire; + let tokenInNumeraire: BigNumber, tokenOutNumeraire: BigNumber; if (isUSDC(poolPairData.tokenIn)) { // amount * rate / 10^poolPairData.decimalsIn -> rate: (_rate / 10^fxOracleDecimals) @@ -113,9 +113,9 @@ export const poolBalancesToNumeraire = ( } return { - tokenInReservesInNumeraire: tokenInNumeraire, - tokenOutReservesInNumeraire: tokenOutNumeraire, - _oGLiq: tokenInNumeraire.plus(tokenOutNumeraire), + tokenInReservesInNumeraire_36: tokenInNumeraire, + tokenOutReservesInNumeraire_36: tokenOutNumeraire, + _oGLiq_36: tokenInNumeraire.add(tokenOutNumeraire), }; }; // everything is in order of USDC, base token @@ -125,7 +125,7 @@ const getParsedFxPoolData = ( isOriginSwap: boolean ): ParsedFxPoolData => { // reserves are in raw amount, they converted to numeraire - const baseReserves = isUSDC(poolPairData.tokenIn) + const baseReserves_36 = isUSDC(poolPairData.tokenIn) ? viewNumeraireAmount( safeParseFixed(poolPairData.balanceOut.toString(), 36), poolPairData.decimalsOut, @@ -140,7 +140,7 @@ const getParsedFxPoolData = ( ); // reserves are not in wei - const usdcReserves = isUSDC(poolPairData.tokenIn) + const usdcReserves_36 = isUSDC(poolPairData.tokenIn) ? viewNumeraireAmount( safeParseFixed(poolPairData.balanceIn.toString(), 36), poolPairData.decimalsIn, @@ -155,20 +155,16 @@ const getParsedFxPoolData = ( ); // rate is converted from chainlink to the actual rate in decimals - const baseTokenRate = isUSDC(poolPairData.tokenIn) + const baseTokenRate_36 = isUSDC(poolPairData.tokenIn) ? poolPairData.tokenOutLatestFXPrice .mul(ONE_36) - .div( - BigNumber.from(10).pow(poolPairData.tokenOutfxOracleDecimals) - ) + .div(safeParseFixed('1', poolPairData.tokenOutfxOracleDecimals)) : poolPairData.tokenInLatestFXPrice .mul(ONE_36) - .div( - BigNumber.from(10).pow(poolPairData.tokenInfxOracleDecimals) - ); + .div(safeParseFixed('1', poolPairData.tokenInfxOracleDecimals)); // given amount in or out converted to numeraire - const givenAmountInNumeraire = calculateGivenAmountInNumeraire( + const givenAmountInNumeraire_36 = calculateGivenAmountInNumeraire( isOriginSwap, poolPairData, amount_36 @@ -180,21 +176,21 @@ const getParsedFxPoolData = ( delta: parseFixed(poolPairData.delta.toString(), 18), epsilon: parseFixed(poolPairData.epsilon.toString(), 18), lambda: parseFixed(poolPairData.lambda.toString(), 18), - baseTokenRate: baseTokenRate, - _oGLiq: baseReserves.plus(usdcReserves), - _nGLiq: baseReserves.plus(usdcReserves), - _oBals: [usdcReserves, baseReserves], - _nBals: isUSDC(poolPairData.tokenIn) + baseTokenRate_36: baseTokenRate_36, + _oGLiq_36: baseReserves_36.add(usdcReserves_36), + _nGLiq_36: baseReserves_36.add(usdcReserves_36), + _oBals_36: [usdcReserves_36, baseReserves_36], + _nBals_36: isUSDC(poolPairData.tokenIn) ? [ - usdcReserves.plus(givenAmountInNumeraire), - baseReserves.minus(givenAmountInNumeraire), + usdcReserves_36.add(givenAmountInNumeraire_36), + baseReserves_36.sub(givenAmountInNumeraire_36), ] : [ - usdcReserves.minus(givenAmountInNumeraire), - baseReserves.plus(givenAmountInNumeraire), + usdcReserves_36.sub(givenAmountInNumeraire_36), + baseReserves_36.add(givenAmountInNumeraire_36), ], - givenAmountInNumeraire: givenAmountInNumeraire, + givenAmountInNumeraire_36: givenAmountInNumeraire_36, }; }; @@ -230,23 +226,25 @@ export const viewRawAmount = ( * @param tokenDecimals * @param rate in wei * @param fxOracleDecimals - * @returns amount in numeraire (ie. user friendly decimals) + * @returns amount in numeraire in 36 decimals */ export const viewNumeraireAmount = ( amount_36: BigNumber, // wei tokenDecimals: number, rate: BigNumber, // wei fxOracleDecimals: number -): OldBigNumber => { +): BigNumber => { // Solidity: _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); const val = amount_36 .mul(safeParseFixed(rate.toString(), 36)) .div(ONE_36) .div(ONE_36) - .div(safeParseFixed('1', fxOracleDecimals)); + .div(safeParseFixed('1', fxOracleDecimals)) + .mul(ONE_36) + .div(safeParseFixed('1', tokenDecimals)); - return bnum(val.toString()).div(bnum(10).pow(tokenDecimals)); + return val; }; // Curve Math @@ -318,11 +316,11 @@ const calculateFee = ( // return outputAmount and ngliq const calculateTrade = ( - _oGLiq: OldBigNumber, - _nGLiq: OldBigNumber, - _oBals: OldBigNumber[], - _nBals: OldBigNumber[], - _inputAmt: OldBigNumber, + _oGLiq_36: BigNumber, + _nGLiq_36: BigNumber, + _oBals_36: BigNumber[], + _nBals_36: BigNumber[], + _inputAmt_36: BigNumber, _outputIndex: number, poolPairData: ParsedFxPoolData ): [OldBigNumber, OldBigNumber] => { @@ -331,31 +329,27 @@ const calculateTrade = ( safeParseFixed('0.5', 36), ]; // const for now since all weights are 0.5 - const inputAmt_ = safeParseFixed(_inputAmt.toString(), 36); - const oGLiq_ = safeParseFixed(_oGLiq.toString(), 36); - let nGLiq_ = safeParseFixed(_nGLiq.toString(), 36); - const oBals_ = _oBals.map((d) => safeParseFixed(d.toString(), 36)); - const nBals_ = _nBals.map((d) => safeParseFixed(d.toString(), 36)); - const alpha = poolPairData.alpha; const beta = poolPairData.beta; const delta = poolPairData.delta; const lambda = poolPairData.lambda; - let outputAmt_ = inputAmt_.mul(-1); + let outputAmt_ = _inputAmt_36.mul(-1); - const omega_ = calculateFee(oGLiq_, oBals_, beta, delta, weights_); + const omega_ = calculateFee(_oGLiq_36, _oBals_36, beta, delta, weights_); let psi_: BigNumber; for (let i = 0; i < 32; i++) { - psi_ = calculateFee(nGLiq_, nBals_, beta, delta, weights_); + psi_ = calculateFee(_nGLiq_36, _nBals_36, beta, delta, weights_); const prevAmount = outputAmt_; outputAmt_ = omega_.lt(psi_) - ? inputAmt_.add(omega_.sub(psi_)).mul(-1) - : inputAmt_.add(lambda.mul(omega_.sub(psi_)).div(ONE_36)).mul(-1); + ? _inputAmt_36.add(omega_.sub(psi_)).mul(-1) + : _inputAmt_36 + .add(lambda.mul(omega_.sub(psi_)).div(ONE_36)) + .mul(-1); if ( outputAmt_ @@ -363,19 +357,26 @@ const calculateTrade = ( .div(ONE_TO_THE_THIRTEEN_NUM_36) .eq(prevAmount.mul(ONE_36).div(ONE_TO_THE_THIRTEEN_NUM_36)) ) { - nGLiq_ = oGLiq_.add(inputAmt_).add(outputAmt_); + _nGLiq_36 = _oGLiq_36.add(_inputAmt_36).add(outputAmt_); - nBals_[_outputIndex] = oBals_[_outputIndex].add(outputAmt_); + _nBals_36[_outputIndex] = _oBals_36[_outputIndex].add(outputAmt_); // throws error already, removed if statement - enforceHalts(oGLiq_, nGLiq_, oBals_, nBals_, weights_, alpha); - enforceSwapInvariant(oGLiq_, omega_, nGLiq_, psi_); + enforceHalts( + _oGLiq_36, + _nGLiq_36, + _oBals_36, + _nBals_36, + weights_, + alpha + ); + enforceSwapInvariant(_oGLiq_36, omega_, _nGLiq_36, psi_); return [ bnum(outputAmt_.toString()).div(bnum(10).pow(36)), - bnum(nGLiq_.toString()).div(bnum(10).pow(36)), + bnum(_nGLiq_36.toString()).div(bnum(10).pow(36)), ]; } else { - nGLiq_ = oGLiq_.add(inputAmt_).add(outputAmt_); - nBals_[_outputIndex] = oBals_[_outputIndex].add(outputAmt_); + _nGLiq_36 = _oGLiq_36.add(_inputAmt_36).add(outputAmt_); + _nBals_36[_outputIndex] = _oBals_36[_outputIndex].add(outputAmt_); } } @@ -470,28 +471,29 @@ export function _exactTokenInForTokenOut( true ); - const targetAmountInNumeraire = parsedFxPoolData.givenAmountInNumeraire; + const targetAmountInNumeraire_36 = + parsedFxPoolData.givenAmountInNumeraire_36; if (poolPairData.tokenIn === poolPairData.tokenOut) { return viewRawAmount( - safeParseFixed(targetAmountInNumeraire.toString(), 36), + targetAmountInNumeraire_36, poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals ).div(bnum(10).pow(poolPairData.decimalsIn)); // must be the token out } - const _oGLiq = parsedFxPoolData._oGLiq; - const _nGLiq = parsedFxPoolData._nGLiq; - const _oBals = parsedFxPoolData._oBals; - const _nBals = parsedFxPoolData._nBals; + const _oGLiq_36 = parsedFxPoolData._oGLiq_36; + const _nGLiq_36 = parsedFxPoolData._nGLiq_36; + const _oBals_36 = parsedFxPoolData._oBals_36; + const _nBals_36 = parsedFxPoolData._nBals_36; const _amt = calculateTrade( - _oGLiq, // _oGLiq - _nGLiq, // _nGLiq - _oBals, // _oBals - _nBals, // _nBals - targetAmountInNumeraire, // input amount + _oGLiq_36, // _oGLiq + _nGLiq_36, // _nGLiq + _oBals_36, // _oBals + _nBals_36, // _nBals + targetAmountInNumeraire_36, // input amount isUSDC(poolPairData.tokenIn) ? 1 : 0, // if USDC return base token (index 1), else return 0 for USDC out parsedFxPoolData ); @@ -523,30 +525,25 @@ export function _tokenInForExactTokenOut( poolPairData, false ); - const targetAmountInNumeraire = - parsedFxPoolData.givenAmountInNumeraire.times(-1); + const targetAmountInNumeraire_36 = + parsedFxPoolData.givenAmountInNumeraire_36.mul(-1); if (poolPairData.tokenIn === poolPairData.tokenOut) { viewRawAmount( // poolPairData.tokenOut as TokenSymbol, - safeParseFixed(targetAmountInNumeraire.toString(), 36), + targetAmountInNumeraire_36, poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals ).div(bnum(10).pow(poolPairData.decimalsOut)); // must be the token out } - const _oGLiq = parsedFxPoolData._oGLiq; - const _nGLiq = parsedFxPoolData._nGLiq; - const _oBals = parsedFxPoolData._oBals; - const _nBals = parsedFxPoolData._nBals; - const _amt = calculateTrade( - _oGLiq, // _oGLiq - _nGLiq, // _nGLiq - _oBals, // _oBals - _nBals, // _nBals - targetAmountInNumeraire, + parsedFxPoolData._oGLiq_36, + parsedFxPoolData._nGLiq_36, + parsedFxPoolData._oBals_36, + parsedFxPoolData._nBals_36, + targetAmountInNumeraire_36, isUSDC(poolPairData.tokenIn) ? 0 : 1, // if USDC return 0 else return 1 for base token parsedFxPoolData ); @@ -579,17 +576,12 @@ export const spotPriceBeforeSwap = ( true ); - const _oGLiq = parsedFxPoolData._oGLiq; - const _nGLiq = parsedFxPoolData._nGLiq; - const _oBals = parsedFxPoolData._oBals; - const _nBals = parsedFxPoolData._nBals; - const outputAmountInNumeraire = calculateTrade( - _oGLiq, // _oGLiq - _nGLiq, // _nGLiq - _oBals, // _oBals - _nBals, // _nBals - bnum(1), // input amount + parsedFxPoolData._oGLiq_36, // _oGLiq + parsedFxPoolData._nGLiq_36, // _nGLiq + parsedFxPoolData._oBals_36, // _oBals + parsedFxPoolData._nBals_36, // _nBals + parseFixed('1', 36), // input amount 0, // always output in USDC parsedFxPoolData ); @@ -602,7 +594,7 @@ export const spotPriceBeforeSwap = ( ) ) .div(inputAmountInNumeraire.abs()) - .times(parsedFxPoolData.baseTokenRate.toString()) + .times(parsedFxPoolData.baseTokenRate_36.toString()) .div(ONE_36.toString()) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, @@ -622,22 +614,23 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( true ); - const targetAmountInNumeraire = parsedFxPoolData.givenAmountInNumeraire; + const targetAmountInNumeraire_36 = + parsedFxPoolData.givenAmountInNumeraire_36; - const _oGLiq = parsedFxPoolData._oGLiq; - const _nBals = parsedFxPoolData._nBals; - const currentRate = parsedFxPoolData.baseTokenRate; + const _oGLiq_36 = parsedFxPoolData._oGLiq_36; + const _nBals_36 = parsedFxPoolData._nBals_36; + const currentRate = parsedFxPoolData.baseTokenRate_36; const beta = parsedFxPoolData.beta; const epsilon = parsedFxPoolData.epsilon; - const _nGLiq = parsedFxPoolData._nGLiq; - const _oBals = parsedFxPoolData._oBals; + const _nGLiq_36 = parsedFxPoolData._nGLiq_36; + const _oBals_36 = parsedFxPoolData._oBals_36; const outputAfterTrade = calculateTrade( - _oGLiq, // _oGLiq - _nGLiq, // _nGLiq - _oBals, // _oBals - _nBals, // _nBals - targetAmountInNumeraire, // input amount + _oGLiq_36, + _nGLiq_36, + _oBals_36, + _nBals_36, + targetAmountInNumeraire_36, // input amount isUSDC(poolPairData.tokenIn) ? 1 : 0, // if USDC return base token (index 1), else return 0 for USDC out parsedFxPoolData ); @@ -648,18 +641,20 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .div(bnum(10).pow(36)) .plus(1) .times('0.5') - .times(_oGLiq); + .times(_oGLiq_36.toString()) + .div(bnum(10).pow(36)); const minBetaLimit = bnum(1) .minus(bnum(beta.toString()).div(bnum(10).pow(36))) .times('0.5') - .times(_oGLiq); + .times(_oGLiq_36.toString()) + .div(bnum(10).pow(36)); if (isUSDC(poolPairData.tokenIn)) { // token[0] to token [1] in originswap - const oBals0after = _nBals[0]; + const oBals0after = bnum(_nBals_36[0].toString()).div(bnum(10).pow(36)); - const oBals1after = _nBals[1]; + const oBals1after = bnum(_nBals_36[1].toString()).div(bnum(10).pow(36)); if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { // returns 0 because Math.abs(targetAmountInNumeraire)) * currentRate @@ -675,7 +670,8 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( ) ) .abs() - .div(targetAmountInNumeraire.abs()) + .times(bnum(10).pow(36)) + .div(bnum(targetAmountInNumeraire_36.toString()).abs()) .times(currentRate.toString()) .div(ONE_36.toString()) .decimalPlaces( @@ -698,9 +694,8 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( } else { // if usdc is tokenOut // token[1] to token [0] in originswap - const oBals0after = _nBals[1]; - - const oBals1after = _nBals[0]; + const oBals0after = bnum(_nBals_36[1].toString()).div(bnum(10).pow(36)); + const oBals1after = bnum(_nBals_36[0].toString()).div(bnum(10).pow(36)); if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { if (amount.isZero()) @@ -713,7 +708,8 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( ) ) .abs() - .div(targetAmountInNumeraire.abs()); + .times(bnum(10).pow(36)) + .div(bnum(targetAmountInNumeraire_36.toString()).abs()); return ratioOfOutputAndInput .times(currentRate.toString()) .div(ONE_36.toString()) @@ -750,23 +746,23 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( false ); - const targetAmountInNumeraire = - parsedFxPoolData.givenAmountInNumeraire.times(-1); + const targetAmountInNumeraire_36 = + parsedFxPoolData.givenAmountInNumeraire_36.mul(-1); - const _oGLiq = parsedFxPoolData._oGLiq; - const _nBals = parsedFxPoolData._nBals; - const currentRate = parsedFxPoolData.baseTokenRate; + const _oGLiq_36 = parsedFxPoolData._oGLiq_36; + const _nBals_36 = parsedFxPoolData._nBals_36; + const currentRate = parsedFxPoolData.baseTokenRate_36; const beta = parsedFxPoolData.beta; const epsilon = parsedFxPoolData.epsilon; - const _nGLiq = parsedFxPoolData._nGLiq; - const _oBals = parsedFxPoolData._oBals; + const _nGLiq_36 = parsedFxPoolData._nGLiq_36; + const _oBals_36 = parsedFxPoolData._oBals_36; const outputAfterTrade = calculateTrade( - _oGLiq, // _oGLiq - _nGLiq, // _nGLiq - _oBals, // _oBals - _nBals, // _nBals - targetAmountInNumeraire, // input amount + _oGLiq_36, + _nGLiq_36, + _oBals_36, + _nBals_36, + targetAmountInNumeraire_36, // input amount isUSDC(poolPairData.tokenIn) ? 0 : 1, // if USDC return 0 else return 1 for base token parsedFxPoolData ); @@ -777,20 +773,21 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(bnum(10).pow(36)) .plus(1) .times('0.5') - .times(_oGLiq); - + .times(_oGLiq_36.toString()) + .div(bnum(10).pow(36)); const minBetaLimit = bnum(1) .minus(bnum(beta.toString()).div(bnum(10).pow(36))) .times('0.5') - .times(_oGLiq); - + .times(_oGLiq_36.toString()) + .div(bnum(10).pow(36)); if (isUSDC(poolPairData.tokenIn)) { // token[0] to token [1] in originswap - const oBals0after = _nBals[0]; - const oBals1after = _nBals[1]; + const oBals0after = bnum(_nBals_36[0].toString()).div(bnum(10).pow(36)); + const oBals1after = bnum(_nBals_36[1].toString()).div(bnum(19).pow(36)); if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { - return targetAmountInNumeraire + return bnum(targetAmountInNumeraire_36.toString()) + .div(bnum(10).pow(36)) .abs() .div( outputAmount @@ -823,14 +820,15 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( } } else { // token[1] to token [0] in originswap - const oBals0after = _nBals[0]; - const oBals1after = _nBals[1]; + const oBals0after = bnum(_nBals_36[0].toString()).div(bnum(10).pow(36)); + const oBals1after = bnum(_nBals_36[1].toString()).div(bnum(10).pow(36)); const isBeyondMinBeta = oBals0after.lt(minBetaLimit); const isBeyondMaxBeta = oBals1after.gt(maxBetaLimit); if (isBeyondMinBeta && isBeyondMaxBeta) { - return targetAmountInNumeraire + return bnum(targetAmountInNumeraire_36.toString()) + .div(bnum(10).pow(36)) .abs() .div( outputAmount diff --git a/test/xaveFxPool.math.spec.ts b/test/xaveFxPool.math.spec.ts index ed2e6e2f..9261bb7e 100644 --- a/test/xaveFxPool.math.spec.ts +++ b/test/xaveFxPool.math.spec.ts @@ -44,7 +44,7 @@ context('xaveFxPool: fxMath functions', () => { rate, tokenFxRateDecimals ); - const expected = '9999.999999'; - expect(numerarieAmount.toString()).to.eq(expected); + const expected = safeParseFixed('9999.999999', 36); + expect(numerarieAmount.toString()).to.eq(expected.toString()); }); }); From b90f7256263a0cb6e27165f92f8a695383d243d9 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Tue, 27 Jun 2023 14:31:33 +0700 Subject: [PATCH 45/56] =?UTF-8?q?=F0=9F=96=96=20spotPriceBeforeSwap=20uses?= =?UTF-8?q?=20BigNumber=20amount?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 36 +++++++++++++----------------- test/xaveFxPool.spec.ts | 5 ++--- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 484d4a1c..ba719f44 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -565,16 +565,12 @@ export function _tokenInForExactTokenOut( } export const spotPriceBeforeSwap = ( - amount: OldBigNumber, + amount_36: BigNumber, poolPairData: FxPoolPairData ): OldBigNumber => { // input amount 1 XSGD to get the output in USDC const inputAmountInNumeraire = bnum(1); - const parsedFxPoolData = getParsedFxPoolData( - safeParseFixed(amount.toString(), 36), - poolPairData, - true - ); + const parsedFxPoolData = getParsedFxPoolData(amount_36, poolPairData, true); const outputAmountInNumeraire = calculateTrade( parsedFxPoolData._oGLiq_36, // _oGLiq @@ -608,18 +604,15 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( poolPairData: FxPoolPairData, amount: OldBigNumber ): OldBigNumber => { - const parsedFxPoolData = getParsedFxPoolData( - safeParseFixed(amount.toString(), 36), - poolPairData, - true - ); + const amount_36 = safeParseFixed(amount.toString(), 36); + const parsedFxPoolData = getParsedFxPoolData(amount_36, poolPairData, true); const targetAmountInNumeraire_36 = parsedFxPoolData.givenAmountInNumeraire_36; const _oGLiq_36 = parsedFxPoolData._oGLiq_36; const _nBals_36 = parsedFxPoolData._nBals_36; - const currentRate = parsedFxPoolData.baseTokenRate_36; + const currentRate_36 = parsedFxPoolData.baseTokenRate_36; const beta = parsedFxPoolData.beta; const epsilon = parsedFxPoolData.epsilon; const _nGLiq_36 = parsedFxPoolData._nGLiq_36; @@ -662,7 +655,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( // which is used in front end display. return amount.isZero() - ? spotPriceBeforeSwap(amount, poolPairData) + ? spotPriceBeforeSwap(amount_36, poolPairData) : outputAmount .times( bnum(1).minus( @@ -672,14 +665,14 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .abs() .times(bnum(10).pow(36)) .div(bnum(targetAmountInNumeraire_36.toString()).abs()) - .times(currentRate.toString()) + .times(currentRate_36.toString()) .div(ONE_36.toString()) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { - return bnum(currentRate.toString()) + return bnum(currentRate_36.toString()) .div(ONE_36.toString()) .times( bnum(1).minus( @@ -699,7 +692,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { if (amount.isZero()) - return spotPriceBeforeSwap(amount, poolPairData); + return spotPriceBeforeSwap(amount_36, poolPairData); const ratioOfOutputAndInput = outputAmount .times( @@ -711,14 +704,14 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .times(bnum(10).pow(36)) .div(bnum(targetAmountInNumeraire_36.toString()).abs()); return ratioOfOutputAndInput - .times(currentRate.toString()) + .times(currentRate_36.toString()) .div(ONE_36.toString()) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { - return bnum(currentRate.toString()) + return bnum(currentRate_36.toString()) .div(ONE_36.toString()) .times( bnum(1).minus( @@ -740,8 +733,9 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( poolPairData: FxPoolPairData, amount: OldBigNumber ): OldBigNumber => { + const amount_36 = safeParseFixed(amount.toString(), 36); const parsedFxPoolData = getParsedFxPoolData( - safeParseFixed(amount.toString(), 36), + amount_36, poolPairData, false ); @@ -866,7 +860,7 @@ export const _derivativeSpotPriceAfterSwapExactTokenInForTokenOut = ( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber => { - const x = spotPriceBeforeSwap(bnum('1'), poolPairData); + const x = spotPriceBeforeSwap(parseFixed('1', 36), poolPairData); const y = _spotPriceAfterSwapExactTokenInForTokenOut(poolPairData, amount); const yMinusX = y.minus(x); const ans = yMinusX.div(x); @@ -882,7 +876,7 @@ export const _derivativeSpotPriceAfterSwapTokenInForExactTokenOut = ( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber => { - const x = spotPriceBeforeSwap(bnum('1'), poolPairData); + const x = spotPriceBeforeSwap(parseFixed('1', 36), poolPairData); const y = _spotPriceAfterSwapTokenInForExactTokenOut(poolPairData, amount); const yMinusX = y.minus(x); const ans = yMinusX.div(x); diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 56945db2..2a8b57b6 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -9,6 +9,7 @@ import { FxPool, FxPoolPairData } from '../src/pools/xaveFxPool/fxPool'; import { spotPriceBeforeSwap, _spotPriceAfterSwapExactTokenInForTokenOut, + ONE_36, } from '../src/pools/xaveFxPool/fxPoolMath'; // Add new pool test data in Subgraph Schema format @@ -28,8 +29,6 @@ type TestCaseType = { expectedDerivativeSpotPriceAfterSwap: string; }; -const ONE_NUMERAIRE = bnum(1); - describe('xaveFxPool: fxPools stub test', () => { context('parsePoolPairData', () => { it(`should correctly parse token > token`, async () => { @@ -123,7 +122,7 @@ describe('xaveFxPool: fxPools stub test', () => { ); const spotPriceBeforeSwapValue = spotPriceBeforeSwap( - ONE_NUMERAIRE, + ONE_36, poolPairData ); From 4635ed9f7717ff1a7d026abc1f8722e56a6c3fe9 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Tue, 27 Jun 2023 15:21:46 +0700 Subject: [PATCH 46/56] =?UTF-8?q?=F0=9F=9A=A3=20params=20to=20param=5F64?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 103 ++++++++++++++++------------- 1 file changed, 56 insertions(+), 47 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index ba719f44..77f07bef 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -18,11 +18,11 @@ export enum CurveMathRevert { } interface ParsedFxPoolData { - alpha: BigNumber; - beta: BigNumber; - delta: BigNumber; - epsilon: BigNumber; - lambda: BigNumber; + alpha_36: BigNumber; + beta_36: BigNumber; + delta_36: BigNumber; + epsilon_36: BigNumber; + lambda_36: BigNumber; baseTokenRate_36: BigNumber; _oGLiq_36: BigNumber; _nGLiq_36: BigNumber; @@ -171,11 +171,13 @@ const getParsedFxPoolData = ( ); return { - alpha: parseFixed(poolPairData.alpha.toString(), 18), - beta: parseFixed(poolPairData.beta.toString(), 18), - delta: parseFixed(poolPairData.delta.toString(), 18), - epsilon: parseFixed(poolPairData.epsilon.toString(), 18), - lambda: parseFixed(poolPairData.lambda.toString(), 18), + // poolPairData already has the parameters with 18 decimals + // therefore we only need to add 18 decimals more + alpha_36: safeParseFixed(poolPairData.alpha.toString(), 18), + beta_36: parseFixed(poolPairData.beta.toString(), 18), + delta_36: parseFixed(poolPairData.delta.toString(), 18), + epsilon_36: parseFixed(poolPairData.epsilon.toString(), 18), + lambda_36: parseFixed(poolPairData.lambda.toString(), 18), baseTokenRate_36: baseTokenRate_36, _oGLiq_36: baseReserves_36.add(usdcReserves_36), _nGLiq_36: baseReserves_36.add(usdcReserves_36), @@ -302,16 +304,16 @@ const calculateFee = ( _weights: BigNumber[] ): BigNumber => { const _length = _bals.length; - let psi_ = BigNumber.from(0); + let psi_36 = BigNumber.from(0); for (let i = 0; i < _length; i++) { const _ideal = _gLiq.mul(_weights[i]).div(ONE_36); // keep away from wei values like how the contract do it - psi_ = psi_.add(calculateMicroFee(_bals[i], _ideal, _beta, _delta)); + psi_36 = psi_36.add(calculateMicroFee(_bals[i], _ideal, _beta, _delta)); } - return psi_; + return psi_36; }; // return outputAmount and ngliq @@ -329,26 +331,32 @@ const calculateTrade = ( safeParseFixed('0.5', 36), ]; // const for now since all weights are 0.5 - const alpha = poolPairData.alpha; - const beta = poolPairData.beta; - const delta = poolPairData.delta; - const lambda = poolPairData.lambda; + const alpha_36 = poolPairData.alpha_36; + const beta_36 = poolPairData.beta_36; + const delta_36 = poolPairData.delta_36; + const lambda_36 = poolPairData.lambda_36; let outputAmt_ = _inputAmt_36.mul(-1); - const omega_ = calculateFee(_oGLiq_36, _oBals_36, beta, delta, weights_); + const omega_36 = calculateFee( + _oGLiq_36, + _oBals_36, + beta_36, + delta_36, + weights_ + ); - let psi_: BigNumber; + let psi_36: BigNumber; for (let i = 0; i < 32; i++) { - psi_ = calculateFee(_nGLiq_36, _nBals_36, beta, delta, weights_); + psi_36 = calculateFee(_nGLiq_36, _nBals_36, beta_36, delta_36, weights_); const prevAmount = outputAmt_; - outputAmt_ = omega_.lt(psi_) - ? _inputAmt_36.add(omega_.sub(psi_)).mul(-1) + outputAmt_ = omega_36.lt(psi_36) + ? _inputAmt_36.add(omega_36.sub(psi_36)).mul(-1) : _inputAmt_36 - .add(lambda.mul(omega_.sub(psi_)).div(ONE_36)) + .add(lambda_36.mul(omega_36.sub(psi_36)).div(ONE_36)) .mul(-1); if ( @@ -367,9 +375,9 @@ const calculateTrade = ( _oBals_36, _nBals_36, weights_, - alpha + alpha_36 ); - enforceSwapInvariant(_oGLiq_36, omega_, _nGLiq_36, psi_); + enforceSwapInvariant(_oGLiq_36, omega_36, _nGLiq_36, psi_36); return [ bnum(outputAmt_.toString()).div(bnum(10).pow(36)), bnum(_nGLiq_36.toString()).div(bnum(10).pow(36)), @@ -390,16 +398,15 @@ const enforceHalts = ( _oBals: BigNumber[], _nBals: BigNumber[], _weights: BigNumber[], - alpha: BigNumber + alpha_36: BigNumber ): boolean => { const _length = _nBals.length; - const _alpha = alpha; for (let i = 0; i < _length; i++) { const _nIdeal = _nGLiq.mul(_weights[i]).div(ONE_36); if (_nBals[i].gt(_nIdeal)) { - const _upperAlpha = _alpha.add(ONE_36); + const _upperAlpha = alpha_36.add(ONE_36); const _nHalt = _nIdeal.mul(_upperAlpha).div(ONE_36); @@ -418,7 +425,7 @@ const enforceHalts = ( } } } else { - const _lowerAlpha = ONE_36.sub(_alpha); + const _lowerAlpha = ONE_36.sub(alpha_36); const _nHalt = _nIdeal.mul(_lowerAlpha).div(ONE_36); @@ -501,7 +508,7 @@ export function _exactTokenInForTokenOut( if (_amt === undefined) { throw new Error(CurveMathRevert.CannotSwap); } else { - const epsilon = parsedFxPoolData.epsilon; + const epsilon = parsedFxPoolData.epsilon_36; const _amtWithFee = _amt[0].times( bnum(1).minus(bnum(epsilon.toString()).div(bnum(10).pow(36))) ); @@ -586,7 +593,9 @@ export const spotPriceBeforeSwap = ( .abs() .times( bnum(1).minus( - bnum(parsedFxPoolData.epsilon.toString()).div(bnum(10).pow(36)) + bnum(parsedFxPoolData.epsilon_36.toString()).div( + bnum(10).pow(36) + ) ) ) .div(inputAmountInNumeraire.abs()) @@ -613,8 +622,8 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( const _oGLiq_36 = parsedFxPoolData._oGLiq_36; const _nBals_36 = parsedFxPoolData._nBals_36; const currentRate_36 = parsedFxPoolData.baseTokenRate_36; - const beta = parsedFxPoolData.beta; - const epsilon = parsedFxPoolData.epsilon; + const beta_36 = parsedFxPoolData.beta_36; + const epsilon_36 = parsedFxPoolData.epsilon_36; const _nGLiq_36 = parsedFxPoolData._nGLiq_36; const _oBals_36 = parsedFxPoolData._oBals_36; @@ -630,7 +639,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( const outputAmount = outputAfterTrade[0]; - const maxBetaLimit = bnum(beta.toString()) + const maxBetaLimit = bnum(beta_36.toString()) .div(bnum(10).pow(36)) .plus(1) .times('0.5') @@ -638,7 +647,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .div(bnum(10).pow(36)); const minBetaLimit = bnum(1) - .minus(bnum(beta.toString()).div(bnum(10).pow(36))) + .minus(bnum(beta_36.toString()).div(bnum(10).pow(36))) .times('0.5') .times(_oGLiq_36.toString()) .div(bnum(10).pow(36)); @@ -659,7 +668,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( : outputAmount .times( bnum(1).minus( - bnum(epsilon.toString()).div(bnum(10).pow(36)) + bnum(epsilon_36.toString()).div(bnum(10).pow(36)) ) ) .abs() @@ -676,7 +685,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .div(ONE_36.toString()) .times( bnum(1).minus( - bnum(epsilon.toString()).div(bnum(10).pow(36)) + bnum(epsilon_36.toString()).div(bnum(10).pow(36)) ) ) .decimalPlaces( @@ -697,7 +706,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( const ratioOfOutputAndInput = outputAmount .times( bnum(1).minus( - bnum(epsilon.toString()).div(bnum(10).pow(36)) + bnum(epsilon_36.toString()).div(bnum(10).pow(36)) ) ) .abs() @@ -715,7 +724,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .div(ONE_36.toString()) .times( bnum(1).minus( - bnum(epsilon.toString()).div(bnum(10).pow(36)) + bnum(epsilon_36.toString()).div(bnum(10).pow(36)) ) ) .decimalPlaces( @@ -746,8 +755,8 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( const _oGLiq_36 = parsedFxPoolData._oGLiq_36; const _nBals_36 = parsedFxPoolData._nBals_36; const currentRate = parsedFxPoolData.baseTokenRate_36; - const beta = parsedFxPoolData.beta; - const epsilon = parsedFxPoolData.epsilon; + const beta_36 = parsedFxPoolData.beta_36; + const epsilon_36 = parsedFxPoolData.epsilon_36; const _nGLiq_36 = parsedFxPoolData._nGLiq_36; const _oBals_36 = parsedFxPoolData._oBals_36; @@ -763,14 +772,14 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( const outputAmount = outputAfterTrade[0]; - const maxBetaLimit = bnum(beta.toString()) + const maxBetaLimit = bnum(beta_36.toString()) .div(bnum(10).pow(36)) .plus(1) .times('0.5') .times(_oGLiq_36.toString()) .div(bnum(10).pow(36)); const minBetaLimit = bnum(1) - .minus(bnum(beta.toString()).div(bnum(10).pow(36))) + .minus(bnum(beta_36.toString()).div(bnum(10).pow(36))) .times('0.5') .times(_oGLiq_36.toString()) .div(bnum(10).pow(36)); @@ -786,7 +795,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div( outputAmount .times( - bnum(epsilon.toString()) + bnum(epsilon_36.toString()) .div(bnum(10).pow(36)) .plus(1) ) @@ -804,7 +813,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(ONE_36.toString()) .times( bnum(1).minus( - bnum(epsilon.toString()).div(bnum(10).pow(36)) + bnum(epsilon_36.toString()).div(bnum(10).pow(36)) ) ) .decimalPlaces( @@ -827,7 +836,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div( outputAmount .times( - bnum(epsilon.toString()) + bnum(epsilon_36.toString()) .div(bnum(10).pow(36)) .plus(1) ) @@ -844,7 +853,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(ONE_36.toString()) .times( bnum(1).minus( - bnum(epsilon.toString()).div(bnum(10).pow(36)) + bnum(epsilon_36.toString()).div(bnum(10).pow(36)) ) ) .decimalPlaces( From e5b7a8da2a04399b02d0e76679c3b604686af9ef Mon Sep 17 00:00:00 2001 From: andreiashu Date: Tue, 27 Jun 2023 17:24:31 +0700 Subject: [PATCH 47/56] _derivativeSpotPriceAfterSwapTokenInForExactTokenOut and _derivativeSpotPriceAfterSwapExactTokenInForTokenOut used BigNumber now --- src/pools/xaveFxPool/fxPoolMath.ts | 304 +++++++++++++++-------------- test/xaveFxPool.spec.ts | 14 +- 2 files changed, 162 insertions(+), 156 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 77f07bef..dfadee82 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -5,6 +5,7 @@ import { safeParseFixed } from '../../utils'; // Constants export const ONE_36 = parseFixed('1', 36); +export const ONE_18 = parseFixed('1', 18); export const CURVEMATH_MAX_DIFF_36 = parseFixed('-0.000001000000000000024', 36); export const ONE_TO_THE_THIRTEEN_NUM_36 = parseFixed('10000000000000', 36); const CURVEMATH_MAX_36 = parseFixed('0.25', 36); //CURVEMATH MAX from contract @@ -325,7 +326,7 @@ const calculateTrade = ( _inputAmt_36: BigNumber, _outputIndex: number, poolPairData: ParsedFxPoolData -): [OldBigNumber, OldBigNumber] => { +): [BigNumber, BigNumber] => { const weights_: BigNumber[] = [ safeParseFixed('0.5', 36), safeParseFixed('0.5', 36), @@ -349,7 +350,13 @@ const calculateTrade = ( let psi_36: BigNumber; for (let i = 0; i < 32; i++) { - psi_36 = calculateFee(_nGLiq_36, _nBals_36, beta_36, delta_36, weights_); + psi_36 = calculateFee( + _nGLiq_36, + _nBals_36, + beta_36, + delta_36, + weights_ + ); const prevAmount = outputAmt_; @@ -378,10 +385,7 @@ const calculateTrade = ( alpha_36 ); enforceSwapInvariant(_oGLiq_36, omega_36, _nGLiq_36, psi_36); - return [ - bnum(outputAmt_.toString()).div(bnum(10).pow(36)), - bnum(_nGLiq_36.toString()).div(bnum(10).pow(36)), - ]; + return [outputAmt_, _nGLiq_36]; } else { _nGLiq_36 = _oGLiq_36.add(_inputAmt_36).add(outputAmt_); _nBals_36[_outputIndex] = _oBals_36[_outputIndex].add(outputAmt_); @@ -446,14 +450,14 @@ const enforceHalts = ( }; const enforceSwapInvariant = ( - _oGLiq: BigNumber, - _omega: BigNumber, - _nGLiq: BigNumber, - _psi: BigNumber + _oGLiq_36: BigNumber, + _omega_36: BigNumber, + _nGLiq_36: BigNumber, + _psi_36: BigNumber ): boolean => { - const _nextUtil = _nGLiq.sub(_psi); + const _nextUtil = _nGLiq_36.sub(_psi_36); - const _prevUtil = _oGLiq.sub(_omega); + const _prevUtil = _oGLiq_36.sub(_omega_36); const _diff = _nextUtil.sub(_prevUtil); @@ -495,7 +499,7 @@ export function _exactTokenInForTokenOut( const _oBals_36 = parsedFxPoolData._oBals_36; const _nBals_36 = parsedFxPoolData._nBals_36; - const _amt = calculateTrade( + const _amt_36 = calculateTrade( _oGLiq_36, // _oGLiq _nGLiq_36, // _nGLiq _oBals_36, // _oBals @@ -505,16 +509,16 @@ export function _exactTokenInForTokenOut( parsedFxPoolData ); - if (_amt === undefined) { + if (_amt_36 === undefined) { throw new Error(CurveMathRevert.CannotSwap); } else { - const epsilon = parsedFxPoolData.epsilon_36; - const _amtWithFee = _amt[0].times( - bnum(1).minus(bnum(epsilon.toString()).div(bnum(10).pow(36))) - ); + const epsilon_36 = parsedFxPoolData.epsilon_36; + const _amtWithFee_36 = _amt_36[0] + .mul(ONE_36.sub(epsilon_36)) + .div(ONE_36); return viewRawAmount( - safeParseFixed(_amtWithFee.abs().toString(), 36), + _amtWithFee_36.abs(), poolPairData.decimalsOut, poolPairData.tokenOutLatestFXPrice, poolPairData.tokenOutfxOracleDecimals @@ -545,7 +549,7 @@ export function _tokenInForExactTokenOut( ).div(bnum(10).pow(poolPairData.decimalsOut)); // must be the token out } - const _amt = calculateTrade( + const _amt_36 = calculateTrade( parsedFxPoolData._oGLiq_36, parsedFxPoolData._nGLiq_36, parsedFxPoolData._oBals_36, @@ -555,15 +559,15 @@ export function _tokenInForExactTokenOut( parsedFxPoolData ); - if (_amt === undefined) { + if (_amt_36 === undefined) { throw new Error(CurveMathRevert.CannotSwap); } else { - const epsilon = poolPairData.epsilon.div(bnum(10).pow(18)); + const epsilon_36 = safeParseFixed(poolPairData.epsilon.toString(), 18); - const _amtWithFee = _amt[0].times(epsilon.plus(1)); // fee retained by the pool + const _amtWithFee = _amt_36[0].mul(ONE_36.add(epsilon_36)).div(ONE_36); // fee retained by the pool return viewRawAmount( - safeParseFixed(_amtWithFee.abs().toString(), 36), + _amtWithFee.abs(), poolPairData.decimalsIn, poolPairData.tokenInLatestFXPrice, poolPairData.tokenInfxOracleDecimals @@ -575,11 +579,9 @@ export const spotPriceBeforeSwap = ( amount_36: BigNumber, poolPairData: FxPoolPairData ): OldBigNumber => { - // input amount 1 XSGD to get the output in USDC - const inputAmountInNumeraire = bnum(1); const parsedFxPoolData = getParsedFxPoolData(amount_36, poolPairData, true); - const outputAmountInNumeraire = calculateTrade( + const outputAmountInNumeraire_36 = calculateTrade( parsedFxPoolData._oGLiq_36, // _oGLiq parsedFxPoolData._nGLiq_36, // _nGLiq parsedFxPoolData._oBals_36, // _oBals @@ -589,23 +591,18 @@ export const spotPriceBeforeSwap = ( parsedFxPoolData ); - const val = outputAmountInNumeraire[0] + const val = outputAmountInNumeraire_36[0] .abs() - .times( - bnum(1).minus( - bnum(parsedFxPoolData.epsilon_36.toString()).div( - bnum(10).pow(36) - ) - ) - ) - .div(inputAmountInNumeraire.abs()) - .times(parsedFxPoolData.baseTokenRate_36.toString()) - .div(ONE_36.toString()) + .mul(ONE_36.sub(parsedFxPoolData.epsilon_36)) + .div(ONE_36) + .mul(parsedFxPoolData.baseTokenRate_36) + .div(ONE_36); + return bnum(val.toString()) + .div(bnum(10).pow(36)) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); - return val; }; // spot price after origin swap @@ -627,7 +624,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( const _nGLiq_36 = parsedFxPoolData._nGLiq_36; const _oBals_36 = parsedFxPoolData._oBals_36; - const outputAfterTrade = calculateTrade( + const outputAfterTrade_36 = calculateTrade( _oGLiq_36, _nGLiq_36, _oBals_36, @@ -637,57 +634,59 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( parsedFxPoolData ); - const outputAmount = outputAfterTrade[0]; + const outputAmount_36 = outputAfterTrade_36[0]; - const maxBetaLimit = bnum(beta_36.toString()) - .div(bnum(10).pow(36)) - .plus(1) - .times('0.5') - .times(_oGLiq_36.toString()) - .div(bnum(10).pow(36)); + const maxBetaLimit_36 = beta_36 + .add(ONE_36) + .div(2) + .mul(_oGLiq_36) + .div(ONE_36); - const minBetaLimit = bnum(1) - .minus(bnum(beta_36.toString()).div(bnum(10).pow(36))) - .times('0.5') - .times(_oGLiq_36.toString()) - .div(bnum(10).pow(36)); + const minBetaLimit_36 = ONE_36.sub(beta_36) + .div(2) + .mul(_oGLiq_36) + .div(ONE_36); if (isUSDC(poolPairData.tokenIn)) { // token[0] to token [1] in originswap - const oBals0after = bnum(_nBals_36[0].toString()).div(bnum(10).pow(36)); + const oBals0after_36 = _nBals_36[0]; - const oBals1after = bnum(_nBals_36[1].toString()).div(bnum(10).pow(36)); + const oBals1after_36 = _nBals_36[1]; - if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { + if ( + oBals1after_36.lt(minBetaLimit_36) && + oBals0after_36.gt(maxBetaLimit_36) + ) { // returns 0 because Math.abs(targetAmountInNumeraire)) * currentRate // used that function with a 0 amount to get a market spot price for the pool // which is used in front end display. return amount.isZero() ? spotPriceBeforeSwap(amount_36, poolPairData) - : outputAmount - .times( - bnum(1).minus( - bnum(epsilon_36.toString()).div(bnum(10).pow(36)) - ) - ) - .abs() - .times(bnum(10).pow(36)) - .div(bnum(targetAmountInNumeraire_36.toString()).abs()) - .times(currentRate_36.toString()) - .div(ONE_36.toString()) + : bnum( + outputAmount_36 + .mul(ONE_36.sub(epsilon_36)) + .div(ONE_36) + .abs() + .mul(ONE_36) + .div(targetAmountInNumeraire_36.abs()) + .mul(currentRate_36) + .div(ONE_36) + .toString() + ) + .div(bnum(10).pow(36)) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { - return bnum(currentRate_36.toString()) - .div(ONE_36.toString()) - .times( - bnum(1).minus( - bnum(epsilon_36.toString()).div(bnum(10).pow(36)) - ) - ) + return bnum( + currentRate_36 + .mul(ONE_36.sub(epsilon_36)) + .div(ONE_36) + .toString() + ) + .div(bnum(10).pow(36)) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -696,25 +695,26 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( } else { // if usdc is tokenOut // token[1] to token [0] in originswap - const oBals0after = bnum(_nBals_36[1].toString()).div(bnum(10).pow(36)); - const oBals1after = bnum(_nBals_36[0].toString()).div(bnum(10).pow(36)); + const oBals0after_36 = _nBals_36[1]; + const oBals1after_36 = _nBals_36[0]; - if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { + if ( + oBals1after_36.lt(minBetaLimit_36) && + oBals0after_36.gt(maxBetaLimit_36) + ) { if (amount.isZero()) return spotPriceBeforeSwap(amount_36, poolPairData); - const ratioOfOutputAndInput = outputAmount - .times( - bnum(1).minus( - bnum(epsilon_36.toString()).div(bnum(10).pow(36)) - ) - ) + const ratioOfOutputAndInput = outputAmount_36 + .mul(ONE_36.sub(epsilon_36)) + .div(ONE_36) .abs() - .times(bnum(10).pow(36)) - .div(bnum(targetAmountInNumeraire_36.toString()).abs()); - return ratioOfOutputAndInput - .times(currentRate_36.toString()) - .div(ONE_36.toString()) + .mul(ONE_36) + .div(targetAmountInNumeraire_36.abs()); + return bnum( + ratioOfOutputAndInput.mul(currentRate_36).div(ONE_36).toString() + ) + .div(bnum(10).pow(36)) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -754,13 +754,13 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( const _oGLiq_36 = parsedFxPoolData._oGLiq_36; const _nBals_36 = parsedFxPoolData._nBals_36; - const currentRate = parsedFxPoolData.baseTokenRate_36; + const currentRate_36 = parsedFxPoolData.baseTokenRate_36; const beta_36 = parsedFxPoolData.beta_36; const epsilon_36 = parsedFxPoolData.epsilon_36; const _nGLiq_36 = parsedFxPoolData._nGLiq_36; const _oBals_36 = parsedFxPoolData._oBals_36; - const outputAfterTrade = calculateTrade( + const outputAfterTrade_36 = calculateTrade( _oGLiq_36, _nGLiq_36, _oBals_36, @@ -770,96 +770,102 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( parsedFxPoolData ); - const outputAmount = outputAfterTrade[0]; - - const maxBetaLimit = bnum(beta_36.toString()) - .div(bnum(10).pow(36)) - .plus(1) - .times('0.5') - .times(_oGLiq_36.toString()) - .div(bnum(10).pow(36)); - const minBetaLimit = bnum(1) - .minus(bnum(beta_36.toString()).div(bnum(10).pow(36))) - .times('0.5') - .times(_oGLiq_36.toString()) - .div(bnum(10).pow(36)); + const outputAmount_36 = outputAfterTrade_36[0]; + + const maxBetaLimit_36 = beta_36 + .add(ONE_36) + .div(2) + .mul(_oGLiq_36) + .div(ONE_36); + const minBetaLimit_36 = ONE_36.sub(beta_36) + .div(2) + .mul(_oGLiq_36) + .div(ONE_36); if (isUSDC(poolPairData.tokenIn)) { // token[0] to token [1] in originswap - const oBals0after = bnum(_nBals_36[0].toString()).div(bnum(10).pow(36)); - const oBals1after = bnum(_nBals_36[1].toString()).div(bnum(19).pow(36)); + const oBals0after_36 = _nBals_36[0]; + const oBals1after_36 = _nBals_36[1]; - if (oBals1after.lt(minBetaLimit) && oBals0after.gt(maxBetaLimit)) { - return bnum(targetAmountInNumeraire_36.toString()) + if ( + oBals1after_36.lt(minBetaLimit_36) && + oBals0after_36.gt(maxBetaLimit_36) + ) { + const val = bnum( + targetAmountInNumeraire_36 + .abs() + .mul(ONE_36) + .div( + outputAmount_36 + .mul(epsilon_36.add(ONE_36)) + .div(ONE_36) + .abs() + ) + .mul(currentRate_36) + .div(ONE_36) + .toString() + ) .div(bnum(10).pow(36)) - .abs() - .div( - outputAmount - .times( - bnum(epsilon_36.toString()) - .div(bnum(10).pow(36)) - .plus(1) - ) - .abs() - ) - .times(currentRate.toString()) - .div(ONE_36.toString()) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); + return val; } else { // rate * (1-epsilon) - return bnum(currentRate.toString()) - .div(ONE_36.toString()) - .times( - bnum(1).minus( - bnum(epsilon_36.toString()).div(bnum(10).pow(36)) - ) - ) + const val = bnum( + currentRate_36 + .mul(ONE_36.sub(epsilon_36)) + .div(ONE_36) + .toString() + ) + .div(bnum(10).pow(36)) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); + return val; } } else { // token[1] to token [0] in originswap - const oBals0after = bnum(_nBals_36[0].toString()).div(bnum(10).pow(36)); - const oBals1after = bnum(_nBals_36[1].toString()).div(bnum(10).pow(36)); + const oBals0after_36 = _nBals_36[0]; + const oBals1after_36 = _nBals_36[1]; - const isBeyondMinBeta = oBals0after.lt(minBetaLimit); - const isBeyondMaxBeta = oBals1after.gt(maxBetaLimit); + const isBeyondMinBeta = oBals0after_36.lt(minBetaLimit_36); + const isBeyondMaxBeta = oBals1after_36.gt(maxBetaLimit_36); if (isBeyondMinBeta && isBeyondMaxBeta) { - return bnum(targetAmountInNumeraire_36.toString()) + return bnum( + targetAmountInNumeraire_36 + .abs() + .mul(ONE_36) + .div( + outputAmount_36 + .mul(epsilon_36.add(ONE_36)) + .div(ONE_36) + .abs() + ) + .mul(currentRate_36) + .div(ONE_36) + .toString() + ) .div(bnum(10).pow(36)) - .abs() - .div( - outputAmount - .times( - bnum(epsilon_36.toString()) - .div(bnum(10).pow(36)) - .plus(1) - ) - .abs() - ) - .times(currentRate.toString()) - .div(ONE_36.toString()) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); } else { - return bnum(currentRate.toString()) - .div(ONE_36.toString()) - .times( - bnum(1).minus( - bnum(epsilon_36.toString()).div(bnum(10).pow(36)) - ) - ) + const val = bnum( + currentRate_36 + .mul(ONE_36.sub(epsilon_36)) + .div(ONE_36) + .toString() + ) + .div(bnum(10).pow(36)) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN ); + return val; } } }; diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 2a8b57b6..1e5f66ed 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -146,7 +146,7 @@ describe('xaveFxPool: fxPools stub test', () => { ); expect(amountOut.toString()).to.eq( '0', - 'amountOut' + '_exactTokenInForTokenOut' ); } else { amountOut = newPool._exactTokenInForTokenOut( @@ -156,7 +156,7 @@ describe('xaveFxPool: fxPools stub test', () => { expect(amountOut.toString()).to.be.equal( testCase.expectedSwapOutput, - 'amountOut vs. expectedSwapOutput' + '_exactTokenInForTokenOut' ); const _spotPriceAfterSwapExactTokenInForTokenOut = @@ -169,7 +169,7 @@ describe('xaveFxPool: fxPools stub test', () => { _spotPriceAfterSwapExactTokenInForTokenOut.toString() ).to.equals( testCase.expectedSpotPriceAfterSwap, - 'expectedSpotPriceAfterSwap' + '_spotPriceAfterSwapExactTokenInForTokenOut' ); const derivative = @@ -180,7 +180,7 @@ describe('xaveFxPool: fxPools stub test', () => { expect(derivative.toFixed(20)).to.be.equal( testCase.expectedDerivativeSpotPriceAfterSwap, - 'derivative' + '_derivativeSpotPriceAfterSwapExactTokenInForTokenOut' ); } } else { @@ -217,7 +217,7 @@ describe('xaveFxPool: fxPools stub test', () => { ); expect(amountIn.toString()).to.be.equal( testCase.expectedSwapOutput, - 'amountIn vs. expectedSwapOutput' + '_tokenInForExactTokenOut' ); const _spotPriceAfterSwapTokenInForExactTokenOut = @@ -230,7 +230,7 @@ describe('xaveFxPool: fxPools stub test', () => { _spotPriceAfterSwapTokenInForExactTokenOut.toString() ).to.equal( testCase.expectedSpotPriceAfterSwap, - 'expectedSpotPriceAfterSwap' + '_spotPriceAfterSwapTokenInForExactTokenOut' ); const derivative = @@ -241,7 +241,7 @@ describe('xaveFxPool: fxPools stub test', () => { expect(derivative.toFixed(20)).to.be.equal( testCase.expectedDerivativeSpotPriceAfterSwap, - 'derivative' + '_derivativeSpotPriceAfterSwapTokenInForExactTokenOut' ); } } From 3471d7d7c37b5ab2b2403d5ddfbc9e00daa13109 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Tue, 27 Jun 2023 17:35:02 +0700 Subject: [PATCH 48/56] =?UTF-8?q?=F0=9F=90=BA=20viewRawAmount=20and=20spot?= =?UTF-8?q?PriceBeforeSwap=20return=20BigNumber?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 24 +++++--- src/pools/xaveFxPool/fxPoolMath.ts | 97 +++++++++++++++++++----------- test/xaveFxPool.spec.ts | 12 ++-- 3 files changed, 85 insertions(+), 48 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 35f5a347..81b03b27 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -227,22 +227,26 @@ export class FxPool implements PoolBase { parsedReserves.tokenInReservesInNumeraire_36.toString() ); - return viewRawAmount( - maxLimitAmount_36, - poolPairData.decimalsIn, - poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInfxOracleDecimals + return bnum( + viewRawAmount( + maxLimitAmount_36, + poolPairData.decimalsIn, + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInfxOracleDecimals + ).toString() ).div(bnum(10).pow(poolPairData.decimalsIn)); } else { const maxLimitAmount_36 = maxLimit.sub( parsedReserves.tokenOutReservesInNumeraire_36 ); - return viewRawAmount( - maxLimitAmount_36, - poolPairData.decimalsOut, - poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutfxOracleDecimals + return bnum( + viewRawAmount( + maxLimitAmount_36, + poolPairData.decimalsOut, + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutfxOracleDecimals + ).toString() ).div(bnum(10).pow(poolPairData.decimalsOut)); } } catch { diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index dfadee82..2ec9b44e 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -213,7 +213,7 @@ export const viewRawAmount = ( tokenDecimals: number, rate: BigNumber, // wei fxOracleDecimals: number -): OldBigNumber => { +): BigNumber => { // solidity code `amount.mulu(baseDecimals).mul(baseOracleDecimals).div(_rate); const val = safeParseFixed(amount_36.toString(), tokenDecimals) @@ -221,7 +221,7 @@ export const viewRawAmount = ( .mul(safeParseFixed('1', fxOracleDecimals)) .mul(ONE_36) .div(safeParseFixed(rate.toString(), 36)); - return bnum(val.toString()); + return val; }; /** @@ -486,11 +486,13 @@ export function _exactTokenInForTokenOut( parsedFxPoolData.givenAmountInNumeraire_36; if (poolPairData.tokenIn === poolPairData.tokenOut) { - return viewRawAmount( - targetAmountInNumeraire_36, - poolPairData.decimalsIn, - poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInfxOracleDecimals + return bnum( + viewRawAmount( + targetAmountInNumeraire_36, + poolPairData.decimalsIn, + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInfxOracleDecimals + ).toString() ).div(bnum(10).pow(poolPairData.decimalsIn)); // must be the token out } @@ -517,11 +519,13 @@ export function _exactTokenInForTokenOut( .mul(ONE_36.sub(epsilon_36)) .div(ONE_36); - return viewRawAmount( - _amtWithFee_36.abs(), - poolPairData.decimalsOut, - poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutfxOracleDecimals + return bnum( + viewRawAmount( + _amtWithFee_36.abs(), + poolPairData.decimalsOut, + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutfxOracleDecimals + ).toString() ).div(bnum(10).pow(poolPairData.decimalsOut)); } } @@ -540,12 +544,14 @@ export function _tokenInForExactTokenOut( parsedFxPoolData.givenAmountInNumeraire_36.mul(-1); if (poolPairData.tokenIn === poolPairData.tokenOut) { - viewRawAmount( - // poolPairData.tokenOut as TokenSymbol, - targetAmountInNumeraire_36, - poolPairData.decimalsOut, - poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutfxOracleDecimals + bnum( + viewRawAmount( + // poolPairData.tokenOut as TokenSymbol, + targetAmountInNumeraire_36, + poolPairData.decimalsOut, + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutfxOracleDecimals + ).toString() ).div(bnum(10).pow(poolPairData.decimalsOut)); // must be the token out } @@ -566,11 +572,13 @@ export function _tokenInForExactTokenOut( const _amtWithFee = _amt_36[0].mul(ONE_36.add(epsilon_36)).div(ONE_36); // fee retained by the pool - return viewRawAmount( - _amtWithFee.abs(), - poolPairData.decimalsIn, - poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInfxOracleDecimals + return bnum( + viewRawAmount( + _amtWithFee.abs(), + poolPairData.decimalsIn, + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInfxOracleDecimals + ).toString() ).div(bnum(10).pow(poolPairData.decimalsIn)); // must be the token out } } @@ -578,7 +586,7 @@ export function _tokenInForExactTokenOut( export const spotPriceBeforeSwap = ( amount_36: BigNumber, poolPairData: FxPoolPairData -): OldBigNumber => { +): BigNumber => { const parsedFxPoolData = getParsedFxPoolData(amount_36, poolPairData, true); const outputAmountInNumeraire_36 = calculateTrade( @@ -597,12 +605,7 @@ export const spotPriceBeforeSwap = ( .div(ONE_36) .mul(parsedFxPoolData.baseTokenRate_36) .div(ONE_36); - return bnum(val.toString()) - .div(bnum(10).pow(36)) - .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + return val; }; // spot price after origin swap @@ -662,7 +665,12 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( // which is used in front end display. return amount.isZero() - ? spotPriceBeforeSwap(amount_36, poolPairData) + ? bnum(spotPriceBeforeSwap(amount_36, poolPairData).toString()) + .div(bnum(10).pow(36)) + .decimalPlaces( + poolPairData.tokenOutfxOracleDecimals, + OldBigNumber.ROUND_DOWN + ) : bnum( outputAmount_36 .mul(ONE_36.sub(epsilon_36)) @@ -703,7 +711,14 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( oBals0after_36.gt(maxBetaLimit_36) ) { if (amount.isZero()) - return spotPriceBeforeSwap(amount_36, poolPairData); + return bnum( + spotPriceBeforeSwap(amount_36, poolPairData).toString() + ) + .div(bnum(10).pow(36)) + .decimalPlaces( + poolPairData.tokenOutfxOracleDecimals, + OldBigNumber.ROUND_DOWN + ); const ratioOfOutputAndInput = outputAmount_36 .mul(ONE_36.sub(epsilon_36)) @@ -875,7 +890,14 @@ export const _derivativeSpotPriceAfterSwapExactTokenInForTokenOut = ( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber => { - const x = spotPriceBeforeSwap(parseFixed('1', 36), poolPairData); + const x = bnum( + spotPriceBeforeSwap(parseFixed('1', 36), poolPairData).toString() + ) + .div(bnum(10).pow(36)) + .decimalPlaces( + poolPairData.tokenOutfxOracleDecimals, + OldBigNumber.ROUND_DOWN + ); const y = _spotPriceAfterSwapExactTokenInForTokenOut(poolPairData, amount); const yMinusX = y.minus(x); const ans = yMinusX.div(x); @@ -891,7 +913,14 @@ export const _derivativeSpotPriceAfterSwapTokenInForExactTokenOut = ( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber => { - const x = spotPriceBeforeSwap(parseFixed('1', 36), poolPairData); + const x = bnum( + spotPriceBeforeSwap(parseFixed('1', 36), poolPairData).toString() + ) + .div(bnum(10).pow(36)) + .decimalPlaces( + poolPairData.tokenOutfxOracleDecimals, + OldBigNumber.ROUND_DOWN + ); const y = _spotPriceAfterSwapTokenInForExactTokenOut(poolPairData, amount); const yMinusX = y.minus(x); const ans = yMinusX.div(x); diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 1e5f66ed..bc7dfd26 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -121,10 +121,14 @@ describe('xaveFxPool: fxPools stub test', () => { : newPool.tokens[1].address // tokenOut ); - const spotPriceBeforeSwapValue = spotPriceBeforeSwap( - ONE_36, - poolPairData - ); + const spotPriceBeforeSwapValue = bnum( + spotPriceBeforeSwap(ONE_36, poolPairData).toString() + ) + .div(bnum(10).pow(36)) + .decimalPlaces( + poolPairData.tokenOutfxOracleDecimals, + OldBigNumber.ROUND_DOWN + ); expect(spotPriceBeforeSwapValue.toString()).to.equals( testCase.expectedSpotPriceBeforeSwap, From edc0b95e2c12894702a235eedad825e3782a4165 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Tue, 27 Jun 2023 17:40:26 +0700 Subject: [PATCH 49/56] =?UTF-8?q?=F0=9F=8D=B3bnum(10).pow(36)=20->=20OLD?= =?UTF-8?q?=5FONE=5F36?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 2ec9b44e..4caedb01 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -6,6 +6,7 @@ import { safeParseFixed } from '../../utils'; // Constants export const ONE_36 = parseFixed('1', 36); export const ONE_18 = parseFixed('1', 18); +export const OLD_ONE_36 = bnum(10).pow(36); export const CURVEMATH_MAX_DIFF_36 = parseFixed('-0.000001000000000000024', 36); export const ONE_TO_THE_THIRTEEN_NUM_36 = parseFixed('10000000000000', 36); const CURVEMATH_MAX_36 = parseFixed('0.25', 36); //CURVEMATH MAX from contract @@ -666,7 +667,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( return amount.isZero() ? bnum(spotPriceBeforeSwap(amount_36, poolPairData).toString()) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -682,7 +683,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .div(ONE_36) .toString() ) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -694,7 +695,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .div(ONE_36) .toString() ) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -714,7 +715,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( return bnum( spotPriceBeforeSwap(amount_36, poolPairData).toString() ) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -729,7 +730,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( return bnum( ratioOfOutputAndInput.mul(currentRate_36).div(ONE_36).toString() ) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -738,9 +739,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( return bnum(currentRate_36.toString()) .div(ONE_36.toString()) .times( - bnum(1).minus( - bnum(epsilon_36.toString()).div(bnum(10).pow(36)) - ) + bnum(1).minus(bnum(epsilon_36.toString()).div(OLD_ONE_36)) ) .decimalPlaces( poolPairData.tokenInfxOracleDecimals, @@ -819,7 +818,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(ONE_36) .toString() ) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -833,7 +832,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(ONE_36) .toString() ) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -863,7 +862,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(ONE_36) .toString() ) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -875,7 +874,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(ONE_36) .toString() ) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -893,7 +892,7 @@ export const _derivativeSpotPriceAfterSwapExactTokenInForTokenOut = ( const x = bnum( spotPriceBeforeSwap(parseFixed('1', 36), poolPairData).toString() ) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN @@ -916,7 +915,7 @@ export const _derivativeSpotPriceAfterSwapTokenInForExactTokenOut = ( const x = bnum( spotPriceBeforeSwap(parseFixed('1', 36), poolPairData).toString() ) - .div(bnum(10).pow(36)) + .div(OLD_ONE_36) .decimalPlaces( poolPairData.tokenOutfxOracleDecimals, OldBigNumber.ROUND_DOWN From 23503778ed6da2a141b14d3ab2b59ddaadfebe23 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Tue, 27 Jun 2023 18:07:06 +0700 Subject: [PATCH 50/56] =?UTF-8?q?=E2=9B=B0=20default=20to=208=20for=20`fxO?= =?UTF-8?q?racleDecimals`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 9 +++++++-- test/testData/fxPool/fxPool_43667355.json | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index 81b03b27..e0f709c6 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -142,8 +142,13 @@ export class FxPool implements PoolBase { if (!tO.token?.latestFXPrice || !tI.token?.latestFXPrice) throw 'FX Pool Missing LatestFxPrice'; - if (!tO.token?.fxOracleDecimals || !tI.token?.fxOracleDecimals) - throw 'FX Pool Missing tokenIn or tokenOut fxOracleDecimals'; + + if (tO.token?.fxOracleDecimals == null) { + tO.token.fxOracleDecimals = 8; + } + if (tI.token?.fxOracleDecimals == null) { + tI.token.fxOracleDecimals = 8; + } const poolPairData: FxPoolPairData = { id: this.id, diff --git a/test/testData/fxPool/fxPool_43667355.json b/test/testData/fxPool/fxPool_43667355.json index e63f8aa2..2ac61101 100644 --- a/test/testData/fxPool/fxPool_43667355.json +++ b/test/testData/fxPool/fxPool_43667355.json @@ -16,7 +16,7 @@ "weight": null, "token": { "latestFXPrice": "0.99997703", - "fxOracleDecimals": 8 + "__fxOracleDecimals__": "purposefully missing to test the default" } }, { @@ -27,7 +27,7 @@ "weight": null, "token": { "latestFXPrice": "0.74226380", - "fxOracleDecimals": 8 + "__fxOracleDecimals__": "purposefully missing to test the default" } } ], From 1e0d545b385e21faa0d621a35f44e0a04d8664d8 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 28 Jun 2023 10:51:05 +0700 Subject: [PATCH 51/56] =?UTF-8?q?=F0=9F=8D=9C=20removed=20redundant=20oper?= =?UTF-8?q?ations;=20thanks=20@brunoguerios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 4caedb01..2e99c0e3 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -675,9 +675,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( : bnum( outputAmount_36 .mul(ONE_36.sub(epsilon_36)) - .div(ONE_36) .abs() - .mul(ONE_36) .div(targetAmountInNumeraire_36.abs()) .mul(currentRate_36) .div(ONE_36) @@ -723,9 +721,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( const ratioOfOutputAndInput = outputAmount_36 .mul(ONE_36.sub(epsilon_36)) - .div(ONE_36) .abs() - .mul(ONE_36) .div(targetAmountInNumeraire_36.abs()); return bnum( ratioOfOutputAndInput.mul(currentRate_36).div(ONE_36).toString() From 2cf04c2c0923872522b6dd4594568b1aab89a2d7 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Wed, 28 Jun 2023 11:07:07 +0700 Subject: [PATCH 52/56] =?UTF-8?q?=F0=9F=8F=BA=20parseFixedCurveParam=20tes?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/xaveFxPool.math.spec.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/xaveFxPool.math.spec.ts b/test/xaveFxPool.math.spec.ts index 9261bb7e..c309e6c9 100644 --- a/test/xaveFxPool.math.spec.ts +++ b/test/xaveFxPool.math.spec.ts @@ -9,6 +9,7 @@ import { } from '../src/pools/xaveFxPool/fxPoolMath'; import { BigNumber } from '@ethersproject/bignumber'; import { safeParseFixed } from '../src/utils'; +import { parseFixedCurveParam } from '../src/pools/xaveFxPool/parseFixedCurveParam'; context('xaveFxPool: fxMath functions', () => { const tokenDecimals = 6; @@ -47,4 +48,20 @@ context('xaveFxPool: fxMath functions', () => { const expected = safeParseFixed('9999.999999', 36); expect(numerarieAmount.toString()).to.eq(expected.toString()); }); + + it('should correctly parse FXPool parameters', async () => { + // @todo ABDK's UI (https://toolkit.abdk.consulting/math#convert-number) + // returns 273437500000000000.867 for '0.2734375' + expect(parseFixedCurveParam('0.2734375').toString()).to.eq( + '273437500000000000.976' + ); + expect(parseFixedCurveParam('0.8').toString()).to.eq( + '800000000000000000.987' + ); + // @todo ABDK's UI + // returns 0.00050000000000000099 for '0.0005' + expect(parseFixedCurveParam('0.0005').toString()).to.eq( + '500000000000000.987' + ); + }); }); From 1bbc298bc57b0dfec0016e0edab0e082fb8e2b5e Mon Sep 17 00:00:00 2001 From: aplki Date: Thu, 29 Jun 2023 21:09:48 +0800 Subject: [PATCH 53/56] =?UTF-8?q?=F0=9F=99=85=F0=9F=8F=BB=E2=80=8D?= =?UTF-8?q?=E2=99=82=EF=B8=8F=20removed=20the=20conditional=20that=20was?= =?UTF-8?q?=20previously=20used=20on=20other=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 48 +++++++++++------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 2e99c0e3..3358bc21 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -78,41 +78,27 @@ const calculateGivenAmountInNumeraire = ( return calculatedNumeraireAmount_36; }; +// Now only used in the fxpool.ts to calculate numeraire values of the raw amount balances +// from the subgraph to calculate the limit to swap export const poolBalancesToNumeraire = ( poolPairData: FxPoolPairData ): ReservesInNumeraire => { - let tokenInNumeraire: BigNumber, tokenOutNumeraire: BigNumber; - - if (isUSDC(poolPairData.tokenIn)) { - // amount * rate / 10^poolPairData.decimalsIn -> rate: (_rate / 10^fxOracleDecimals) - // _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); - tokenInNumeraire = viewNumeraireAmount( - safeParseFixed(poolPairData.balanceIn.toString(), 36), - poolPairData.decimalsIn, - poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInfxOracleDecimals - ); - tokenOutNumeraire = viewNumeraireAmount( - safeParseFixed(poolPairData.balanceOut.toString(), 36), - poolPairData.decimalsOut, - poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutfxOracleDecimals - ); - } else { - tokenInNumeraire = viewNumeraireAmount( - safeParseFixed(poolPairData.balanceOut.toString(), 36), - poolPairData.decimalsOut, - poolPairData.tokenOutLatestFXPrice, - poolPairData.tokenOutfxOracleDecimals - ); + // amount * rate / 10^poolPairData.decimalsIn -> rate: (_rate / 10^fxOracleDecimals) + // _amount.mul(_rate).div(basefxOracleDecimals).divu(baseDecimals); + + const tokenInNumeraire = viewNumeraireAmount( + safeParseFixed(poolPairData.balanceIn.toString(), 36), + poolPairData.decimalsIn, + poolPairData.tokenInLatestFXPrice, + poolPairData.tokenInfxOracleDecimals + ); - tokenOutNumeraire = viewNumeraireAmount( - safeParseFixed(poolPairData.balanceIn.toString(), 36), - poolPairData.decimalsIn, - poolPairData.tokenInLatestFXPrice, - poolPairData.tokenInfxOracleDecimals - ); - } + const tokenOutNumeraire = viewNumeraireAmount( + safeParseFixed(poolPairData.balanceOut.toString(), 36), + poolPairData.decimalsOut, + poolPairData.tokenOutLatestFXPrice, + poolPairData.tokenOutfxOracleDecimals + ); return { tokenInReservesInNumeraire_36: tokenInNumeraire, From 3d347f5eee0eb6483c1626b9f2af32748c81677e Mon Sep 17 00:00:00 2001 From: andreiashu Date: Mon, 3 Jul 2023 12:42:05 +0700 Subject: [PATCH 54/56] =?UTF-8?q?=F0=9F=8D=B3=20removed=20the=20rounding?= =?UTF-8?q?=20down=20from=20spot=20price=20related=20functions;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPool.ts | 1 + src/pools/xaveFxPool/fxPoolMath.ts | 95 ++++--------------- .../fxPool/fxPoolTestCases_43667355.json | 44 ++++----- test/xaveFxPool.spec.ts | 12 +-- 4 files changed, 47 insertions(+), 105 deletions(-) diff --git a/src/pools/xaveFxPool/fxPool.ts b/src/pools/xaveFxPool/fxPool.ts index e0f709c6..52cb4f8b 100644 --- a/src/pools/xaveFxPool/fxPool.ts +++ b/src/pools/xaveFxPool/fxPool.ts @@ -336,6 +336,7 @@ export class FxPool implements PoolBase { poolPairData: FxPoolPairData, amount: OldBigNumber ): OldBigNumber { + // `calculateTrade` will throw if the trade is beyond alpha region try { return this._inHigherPrecision( _derivativeSpotPriceAfterSwapExactTokenInForTokenOut, diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 3358bc21..33da1271 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -573,7 +573,7 @@ export function _tokenInForExactTokenOut( export const spotPriceBeforeSwap = ( amount_36: BigNumber, poolPairData: FxPoolPairData -): BigNumber => { +): OldBigNumber => { const parsedFxPoolData = getParsedFxPoolData(amount_36, poolPairData, true); const outputAmountInNumeraire_36 = calculateTrade( @@ -586,12 +586,15 @@ export const spotPriceBeforeSwap = ( parsedFxPoolData ); - const val = outputAmountInNumeraire_36[0] - .abs() - .mul(ONE_36.sub(parsedFxPoolData.epsilon_36)) - .div(ONE_36) - .mul(parsedFxPoolData.baseTokenRate_36) - .div(ONE_36); + const val = bnum( + outputAmountInNumeraire_36[0] + .abs() + .mul(ONE_36.sub(parsedFxPoolData.epsilon_36)) + .div(ONE_36) + .mul(parsedFxPoolData.baseTokenRate_36) + .div(ONE_36) + .toString() + ).div(OLD_ONE_36); return val; }; @@ -652,12 +655,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( // which is used in front end display. return amount.isZero() - ? bnum(spotPriceBeforeSwap(amount_36, poolPairData).toString()) - .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ) + ? spotPriceBeforeSwap(amount_36, poolPairData) : bnum( outputAmount_36 .mul(ONE_36.sub(epsilon_36)) @@ -667,11 +665,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .div(ONE_36) .toString() ) - .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenInfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + .div(OLD_ONE_36); } else { return bnum( currentRate_36 @@ -679,11 +673,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .div(ONE_36) .toString() ) - .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenInfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + .div(OLD_ONE_36); } } else { // if usdc is tokenOut @@ -696,14 +686,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( oBals0after_36.gt(maxBetaLimit_36) ) { if (amount.isZero()) - return bnum( - spotPriceBeforeSwap(amount_36, poolPairData).toString() - ) - .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + return spotPriceBeforeSwap(amount_36, poolPairData); const ratioOfOutputAndInput = outputAmount_36 .mul(ONE_36.sub(epsilon_36)) @@ -712,20 +695,12 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( return bnum( ratioOfOutputAndInput.mul(currentRate_36).div(ONE_36).toString() ) - .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenInfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + .div(OLD_ONE_36); } else { return bnum(currentRate_36.toString()) .div(ONE_36.toString()) .times( bnum(1).minus(bnum(epsilon_36.toString()).div(OLD_ONE_36)) - ) - .decimalPlaces( - poolPairData.tokenInfxOracleDecimals, - OldBigNumber.ROUND_DOWN ); } } @@ -801,10 +776,6 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .toString() ) .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); return val; } else { // rate * (1-epsilon) @@ -814,11 +785,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(ONE_36) .toString() ) - .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + .div(OLD_ONE_36); return val; } } else { @@ -844,11 +811,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(ONE_36) .toString() ) - .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + .div(OLD_ONE_36); } else { const val = bnum( currentRate_36 @@ -856,11 +819,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .div(ONE_36) .toString() ) - .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + .div(OLD_ONE_36); return val; } } @@ -871,14 +830,7 @@ export const _derivativeSpotPriceAfterSwapExactTokenInForTokenOut = ( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber => { - const x = bnum( - spotPriceBeforeSwap(parseFixed('1', 36), poolPairData).toString() - ) - .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + const x = spotPriceBeforeSwap(parseFixed('1', 36), poolPairData); const y = _spotPriceAfterSwapExactTokenInForTokenOut(poolPairData, amount); const yMinusX = y.minus(x); const ans = yMinusX.div(x); @@ -894,14 +846,7 @@ export const _derivativeSpotPriceAfterSwapTokenInForExactTokenOut = ( amount: OldBigNumber, poolPairData: FxPoolPairData ): OldBigNumber => { - const x = bnum( - spotPriceBeforeSwap(parseFixed('1', 36), poolPairData).toString() - ) - .div(OLD_ONE_36) - .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + const x = spotPriceBeforeSwap(parseFixed('1', 36), poolPairData); const y = _spotPriceAfterSwapTokenInForExactTokenOut(poolPairData, amount); const yMinusX = y.minus(x); const ans = yMinusX.div(x); diff --git a/test/testData/fxPool/fxPoolTestCases_43667355.json b/test/testData/fxPool/fxPoolTestCases_43667355.json index 7274b69d..5985f1d4 100644 --- a/test/testData/fxPool/fxPoolTestCases_43667355.json +++ b/test/testData/fxPool/fxPoolTestCases_43667355.json @@ -6,8 +6,8 @@ "givenAmount": "100000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.74189266", + "expectedSpotPriceBeforeSwap": "0.741892668099999999", + "expectedSpotPriceAfterSwap": "0.7418926680999999992673856294", "expectedSwapOutput": "134652.537477", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -18,8 +18,8 @@ "givenAmount": "100000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.74189266", + "expectedSpotPriceBeforeSwap": "0.741892668099999999", + "expectedSpotPriceAfterSwap": "0.7418926680999999992673856294", "expectedSwapOutput": "134787.257375", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -30,8 +30,8 @@ "givenAmount": "100000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.74189266", + "expectedSpotPriceBeforeSwap": "0.741892668099999999", + "expectedSpotPriceAfterSwap": "0.7418926680999999992673856294", "expectedSwapOutput": "74190.970975", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -42,8 +42,8 @@ "givenAmount": "100000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.74189266", + "expectedSpotPriceBeforeSwap": "0.741892668099999999", + "expectedSpotPriceAfterSwap": "0.7418926680999999992673856294", "expectedSwapOutput": "74265.199061", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -55,8 +55,8 @@ "givenAmount": "200000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.74189266", + "expectedSpotPriceBeforeSwap": "0.741892668099999999", + "expectedSpotPriceAfterSwap": "0.7418926680999999992673856294", "expectedSwapOutput": "269305.074955", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -68,10 +68,10 @@ "givenAmount": "200000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.7407784", + "expectedSpotPriceBeforeSwap": "0.741892668099999999", + "expectedSpotPriceAfterSwap": "0.740778400391736036366991206905941151", "expectedSwapOutput": "269980.072169", - "expectedDerivativeSpotPriceAfterSwap": "0.00150191538490217708" + "expectedDerivativeSpotPriceAfterSwap": "0.00150192575850307544" }, { @@ -81,10 +81,10 @@ "givenAmount": "270000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.74076748", + "expectedSpotPriceBeforeSwap": "0.741892668099999999", + "expectedSpotPriceAfterSwap": "0.740767481936594140174584949186879657", "expectedSwapOutput": "200011.814393", - "expectedDerivativeSpotPriceAfterSwap": "0.00151663449534599789" + "expectedDerivativeSpotPriceAfterSwap": "0.00151664278646597275" }, { "testNo": "8", @@ -93,8 +93,8 @@ "givenAmount": "270000", "tokenIn": "USDC", "tokenOut": "XSGD", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.74189266", + "expectedSpotPriceBeforeSwap": "0.741892668099999999", + "expectedSpotPriceAfterSwap": "0.7418926680999999992673856294", "expectedSwapOutput": "200516.037466", "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" }, @@ -106,10 +106,10 @@ "givenAmount": "450000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.74189266", - "expectedSpotPriceAfterSwap": "0.66970695", + "expectedSpotPriceBeforeSwap": "0.741892668099999999", + "expectedSpotPriceAfterSwap": "0.669706952702530393874295458687083876", "expectedSwapOutput": "671920.250617", - "expectedDerivativeSpotPriceAfterSwap": "0.09729939908018499603" + "expectedDerivativeSpotPriceAfterSwap": "0.09729940529313825882" }, { @@ -119,7 +119,7 @@ "givenAmount": "610000", "tokenIn": "XSGD", "tokenOut": "USDC", - "expectedSpotPriceBeforeSwap": "0.74189266", + "expectedSpotPriceBeforeSwap": "0.741892668099999999", "expectedSpotPriceAfterSwap": "CurveMath/lower-halt", "expectedSwapOutput": "CurveMath/lower-halt", "expectedDerivativeSpotPriceAfterSwap": "CurveMath/lower-halt" diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index bc7dfd26..1e5f66ed 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -121,14 +121,10 @@ describe('xaveFxPool: fxPools stub test', () => { : newPool.tokens[1].address // tokenOut ); - const spotPriceBeforeSwapValue = bnum( - spotPriceBeforeSwap(ONE_36, poolPairData).toString() - ) - .div(bnum(10).pow(36)) - .decimalPlaces( - poolPairData.tokenOutfxOracleDecimals, - OldBigNumber.ROUND_DOWN - ); + const spotPriceBeforeSwapValue = spotPriceBeforeSwap( + ONE_36, + poolPairData + ); expect(spotPriceBeforeSwapValue.toString()).to.equals( testCase.expectedSpotPriceBeforeSwap, From 7191c7b6cb156ddf16376fcd4881ce6a03904b81 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Mon, 3 Jul 2023 12:42:53 +0700 Subject: [PATCH 55/56] =?UTF-8?q?=E2=9B=B2=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 33da1271..4ada035d 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -664,16 +664,14 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .mul(currentRate_36) .div(ONE_36) .toString() - ) - .div(OLD_ONE_36); + ).div(OLD_ONE_36); } else { return bnum( currentRate_36 .mul(ONE_36.sub(epsilon_36)) .div(ONE_36) .toString() - ) - .div(OLD_ONE_36); + ).div(OLD_ONE_36); } } else { // if usdc is tokenOut @@ -694,8 +692,7 @@ export const _spotPriceAfterSwapExactTokenInForTokenOut = ( .div(targetAmountInNumeraire_36.abs()); return bnum( ratioOfOutputAndInput.mul(currentRate_36).div(ONE_36).toString() - ) - .div(OLD_ONE_36); + ).div(OLD_ONE_36); } else { return bnum(currentRate_36.toString()) .div(ONE_36.toString()) @@ -774,8 +771,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .mul(currentRate_36) .div(ONE_36) .toString() - ) - .div(OLD_ONE_36) + ).div(OLD_ONE_36); return val; } else { // rate * (1-epsilon) @@ -784,8 +780,7 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .mul(ONE_36.sub(epsilon_36)) .div(ONE_36) .toString() - ) - .div(OLD_ONE_36); + ).div(OLD_ONE_36); return val; } } else { @@ -810,16 +805,14 @@ export const _spotPriceAfterSwapTokenInForExactTokenOut = ( .mul(currentRate_36) .div(ONE_36) .toString() - ) - .div(OLD_ONE_36); + ).div(OLD_ONE_36); } else { const val = bnum( currentRate_36 .mul(ONE_36.sub(epsilon_36)) .div(ONE_36) .toString() - ) - .div(OLD_ONE_36); + ).div(OLD_ONE_36); return val; } } From 6a15ae40f41495a3ef9fca98f65e1b7a5a6f4a86 Mon Sep 17 00:00:00 2001 From: andreiashu Date: Mon, 3 Jul 2023 13:24:58 +0700 Subject: [PATCH 56/56] =?UTF-8?q?=F0=9F=8D=8E=20reverted=20the=20changes?= =?UTF-8?q?=20in=20`=5FderivativeSpotPriceAfterSwapExactTokenInForTokenOut?= =?UTF-8?q?`;=20universalNormalizedLiquidity=20should=20return=20higher=20?= =?UTF-8?q?values=20for=20more=20advantageous=20pools;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pools/xaveFxPool/fxPoolMath.ts | 19 ++++++++++--------- .../fxPool/fxPoolTestCases_43667355.json | 6 +++--- test/xaveFxPool.spec.ts | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/pools/xaveFxPool/fxPoolMath.ts b/src/pools/xaveFxPool/fxPoolMath.ts index 4ada035d..1fb8ac35 100644 --- a/src/pools/xaveFxPool/fxPoolMath.ts +++ b/src/pools/xaveFxPool/fxPoolMath.ts @@ -827,11 +827,16 @@ export const _derivativeSpotPriceAfterSwapExactTokenInForTokenOut = ( const y = _spotPriceAfterSwapExactTokenInForTokenOut(poolPairData, amount); const yMinusX = y.minus(x); const ans = yMinusX.div(x); - // if we're outside the Beta region the derivative will be negative - // but `UniversalNormalizedLiquidity` returns ZERO for negative values - // therefore we want to make sure this reflects the fact that we're - // moving outside of Beta region - return ans.abs(); + if (ans.isZero()) { + // swapping within beta region has no slippage + return bnum(1).div(ONE_18.toString()); + } else { + // if we're outside the Beta region the derivative will be negative + // but `UniversalNormalizedLiquidity` returns ZERO for negative values + // therefore we want to make sure this reflects the fact that we're + // moving outside of Beta region + return ans.abs(); + } }; // target swap @@ -843,9 +848,5 @@ export const _derivativeSpotPriceAfterSwapTokenInForExactTokenOut = ( const y = _spotPriceAfterSwapTokenInForExactTokenOut(poolPairData, amount); const yMinusX = y.minus(x); const ans = yMinusX.div(x); - // if we're outside the Beta region the derivative will be negative - // but `UniversalNormalizedLiquidity` returns ZERO for negative values - // therefore we want to make sure this reflects the fact that we're - // moving outside of Beta region return ans.abs(); }; diff --git a/test/testData/fxPool/fxPoolTestCases_43667355.json b/test/testData/fxPool/fxPoolTestCases_43667355.json index 5985f1d4..cf76c8a7 100644 --- a/test/testData/fxPool/fxPoolTestCases_43667355.json +++ b/test/testData/fxPool/fxPoolTestCases_43667355.json @@ -9,7 +9,7 @@ "expectedSpotPriceBeforeSwap": "0.741892668099999999", "expectedSpotPriceAfterSwap": "0.7418926680999999992673856294", "expectedSwapOutput": "134652.537477", - "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000100" }, { "testNo": "2", @@ -33,7 +33,7 @@ "expectedSpotPriceBeforeSwap": "0.741892668099999999", "expectedSpotPriceAfterSwap": "0.7418926680999999992673856294", "expectedSwapOutput": "74190.970975", - "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000100" }, { "testNo": "4", @@ -58,7 +58,7 @@ "expectedSpotPriceBeforeSwap": "0.741892668099999999", "expectedSpotPriceAfterSwap": "0.7418926680999999992673856294", "expectedSwapOutput": "269305.074955", - "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000000" + "expectedDerivativeSpotPriceAfterSwap": "0.00000000000000000100" }, { diff --git a/test/xaveFxPool.spec.ts b/test/xaveFxPool.spec.ts index 1e5f66ed..1f4cb4d9 100644 --- a/test/xaveFxPool.spec.ts +++ b/test/xaveFxPool.spec.ts @@ -97,7 +97,7 @@ describe('xaveFxPool: fxPools stub test', () => { expect( newPool.getNormalizedLiquidity(poolPairData).toString() - ).to.equals('0', 'getNormalizedLiquidity'); + ).to.equals('1000000000000000000', 'getNormalizedLiquidity'); }); });