import { BigNumber , formatFixed , parseFixed } from '@ethersproject/bignumber' ; import { WeiPerEther as ONE } from '@ethersproject/constants' ; import { isSameAddress } from '../../utils' ; import { BigNumber as OldBigNumber , bnum , scale , ZERO , } from '../../utils/bignumber' ; import { PoolBase , PoolTypes , SwapPairType , SwapTypes , SubgraphPoolBase , SubgraphToken , } from '../../types' ; import { getAddress } from '@ethersproject/address' ; import * as SDK from '@georgeroman/balancer-v2-pools' ; import * as phantomStableMath from '../phantomStablePool/phantomStableMath' ; import { MetaStablePoolPairData } from '../metaStablePool/metaStablePool' ; import cloneDeep from 'lodash.clonedeep' ; enum PairTypes { BptToToken , TokenToBpt , TokenToToken } type PhantomStablePoolToken = Pick < SubgraphToken , 'address' | 'balance' | 'decimals' | 'priceRate' > ; export type PhantomStablePoolPairData = MetaStablePoolPairData & { pairType : PairTypes ; bptIndex : number ; virtualBptSupply : BigNumber ; } ; export class PhantomStablePool implements PoolBase { poolType : PoolTypes = PoolTypes . MetaStable ; swapPairType : SwapPairType ; id : string ; address : string ; amp : BigNumber ; swapFee : BigNumber ; totalShares : BigNumber ; tokens : PhantomStablePoolToken [ ] ; tokensList : string [ ] ; ALMOST_ONE = parseFixed ( '0.99' , 18 ) ; MAX_TOKEN_BALANCE = BigNumber . from ( '2' ) . pow ( '112' ) . sub ( '1' ) ; static AMP_DECIMALS = 3 ; static fromPool ( pool ) { if ( ! pool . amp ) throw new Error ( 'PhantomStablePool missing amp factor' ) ; return new PhantomStablePool ( pool . id , pool . address , pool . amp , pool . swapFee , pool . totalShares , pool . tokens , pool . tokensList ) ; } static removeBPT ( poolPairData ) { const poolPairDataNoBpt = cloneDeep ( poolPairData ) ; const bptIndex = poolPairData . bptIndex ; if ( bptIndex != - 1 ) { poolPairDataNoBpt . allBalances . splice ( bptIndex , 1 ) ; poolPairDataNoBpt . allBalancesScaled . splice ( bptIndex , 1 ) ; if ( bptIndex < poolPairData . tokenIndexIn ) poolPairDataNoBpt . tokenIndexIn -= 1 ; if ( bptIndex < poolPairData . tokenIndexOut ) poolPairDataNoBpt . tokenIndexOut -= 1 ; } return poolPairDataNoBpt ; } constructor ( id , address , amp , swapFee , totalShares , tokens , tokensList ) { this . id = id ; this . address = address ; this . amp = parseFixed ( amp , PhantomStablePool . AMP_DECIMALS ) ; this . swapFee = parseFixed ( swapFee , 18 ) ; this . totalShares = parseFixed ( totalShares , 18 ) ; this . tokens = tokens ; this . tokensList = tokensList ; } setTypeForSwap ( type ) { this . swapPairType = type ; } parsePoolPairData ( tokenIn , tokenOut ) { const tokenIndexIn = this . tokens . findIndex ( ( t ) => getAddress ( t . address ) === getAddress ( tokenIn ) ) ; if ( tokenIndexIn < 0 ) throw 'Pool does not contain tokenIn' ; const tI = this . tokens [ tokenIndexIn ] ; const balanceIn = bnum ( tI . balance ) . times ( bnum ( tI . priceRate ) ) . dp ( tI . decimals ) . toString ( ) ; const decimalsIn = tI . decimals ; const tokenInPriceRate = parseFixed ( tI . priceRate , 18 ) ; const tokenIndexOut = this . tokens . findIndex ( ( t ) => getAddress ( t . address ) === getAddress ( tokenOut ) ) ; if ( tokenIndexOut < 0 ) throw 'Pool does not contain tokenOut' ; const tO = this . tokens [ tokenIndexOut ] ; const balanceOut = bnum ( tO . balance ) . times ( bnum ( tO . priceRate ) ) . dp ( tO . decimals ) . toString ( ) ; const decimalsOut = tO . decimals ; const tokenOutPriceRate = parseFixed ( tO . priceRate , 18 ) ; const allBalances = this . tokens . map ( ( { balance , priceRate } ) => bnum ( balance ) . times ( priceRate ) ) ; const allBalancesScaled = this . tokens . map ( ( { balance , priceRate } ) => parseFixed ( balance , 18 ) . mul ( parseFixed ( priceRate , 18 ) ) . div ( ONE ) ) ; let pairType ; if ( isSameAddress ( tokenIn , this . address ) ) { pairType = PairTypes . BptToToken ; } else if ( isSameAddress ( tokenOut , this . address ) ) { pairType = PairTypes . TokenToBpt ; } else { pairType = PairTypes . TokenToToken ; } const bptIndex = this . tokensList . indexOf ( this . address ) ; const inv = phantomStableMath . _invariant ( this . amp , allBalances ) ; const virtualBptSupply = this . MAX_TOKEN_BALANCE . sub ( allBalancesScaled [ bptIndex ] ) ; const poolPairData = { id : this . id , address : this . address , poolType : this . poolType , pairType : pairType , bptIndex : bptIndex , tokenIn : tokenIn , tokenOut : tokenOut , balanceIn : parseFixed ( balanceIn , decimalsIn ) , balanceOut : parseFixed ( balanceOut , decimalsOut ) , invariant : inv , swapFee : this . swapFee , allBalances , allBalancesScaled , amp : this . amp , tokenIndexIn : tokenIndexIn , tokenIndexOut : tokenIndexOut , decimalsIn : Number ( decimalsIn ) , decimalsOut : Number ( decimalsOut ) , tokenInPriceRate , tokenOutPriceRate , virtualBptSupply , } ; return PhantomStablePool . removeBPT ( poolPairData ) ; } getNormalizedLiquidity ( poolPairData ) { return bnum ( formatFixed ( poolPairData . balanceOut . mul ( poolPairData . amp ) , poolPairData . decimalsOut + PhantomStablePool . AMP_DECIMALS ) ) ; } getLimitAmountSwap ( poolPairData , swapType ) { if ( swapType === SwapTypes . SwapExactIn ) { return bnum ( formatFixed ( poolPairData . balanceOut . mul ( this . ALMOST_ONE ) . div ( poolPairData . tokenOutPriceRate ) , poolPairData . decimalsOut ) ) ; } else { return bnum ( formatFixed ( poolPairData . balanceOut . mul ( this . ALMOST_ONE ) . div ( poolPairData . tokenOutPriceRate ) , poolPairData . decimalsOut ) ) ; } } updateTokenBalanceForPool ( token , newBalance ) { const T = this . tokens . find ( ( t ) => isSameAddress ( t . address , token ) ) ; if ( ! T ) throw Error ( 'Pool does not contain this token' ) ; T . balance = formatFixed ( newBalance , T . decimals ) ; } _exactTokenInForTokenOut ( poolPairData , amount , exact ) { try { const amtScaled = scale ( amount , 18 ) ; const amtWithFee = this . subtractSwapFeeAmount ( BigNumber . from ( amtScaled . toString ( ) ) , poolPairData . swapFee ) ; const amountConverted = bnum ( amtWithFee . toString ( ) ) . times ( formatFixed ( poolPairData . tokenInPriceRate , 18 ) ) . dp ( 0 ) ; let amt ; if ( poolPairData . pairType === PairTypes . TokenToBpt ) { const amountsIn = Array ( poolPairData . allBalancesScaled . length ) . fill ( ZERO ) ; amountsIn [ poolPairData . tokenIndexIn ] = bnum ( amountConverted . toString ( ) ) ; amt = SDK . StableMath . _calcBptOutGivenExactTokensIn ( bnum ( this . amp . toString ( ) ) , poolPairData . allBalancesScaled . map ( ( b ) => bnum ( b . toString ( ) ) ) , amountsIn , bnum ( poolPairData . virtualBptSupply . toString ( ) ) , ZERO ) ; } else if ( poolPairData . pairType === PairTypes . BptToToken ) { amt = SDK . StableMath . _calcTokenOutGivenExactBptIn ( bnum ( this . amp . toString ( ) ) , poolPairData . allBalancesScaled . map ( ( b ) => bnum ( b . toString ( ) ) ) , poolPairData . tokenIndexOut , bnum ( amountConverted . toString ( ) ) , bnum ( poolPairData . virtualBptSupply . toString ( ) ) , ZERO ) ; } else { amt = SDK . StableMath . _calcOutGivenIn ( bnum ( this . amp . toString ( ) ) , poolPairData . allBalancesScaled . map ( ( b ) => bnum ( b . toString ( ) ) ) , poolPairData . tokenIndexIn , poolPairData . tokenIndexOut , bnum ( amountConverted . toString ( ) ) , ZERO ) ; } return scale ( amt . div ( formatFixed ( poolPairData . tokenOutPriceRate , 18 ) ) , - 18 ) . dp ( poolPairData . decimalsOut , 1 ) ; } catch ( err ) { console . error ( `PhantomStable _evmoutGivenIn: ${ err . message }` ) ; return ZERO ; } } _tokenInForExactTokenOut ( poolPairData , amount , exact ) { try { const amtScaled = scale ( amount , 18 ) ; const amountConverted = amtScaled . times ( formatFixed ( poolPairData . tokenOutPriceRate , 18 ) ) . dp ( 0 ) ; let amt ; if ( poolPairData . pairType === PairTypes . TokenToBpt ) { amt = SDK . StableMath . _calcTokenInGivenExactBptOut ( bnum ( this . amp . toString ( ) ) , poolPairData . allBalancesScaled . map ( ( b ) => bnum ( b . toString ( ) ) ) , poolPairData . tokenIndexIn , bnum ( amountConverted . toString ( ) ) , bnum ( poolPairData . virtualBptSupply . toString ( ) ) , ZERO ) ; } else if ( poolPairData . pairType === PairTypes . BptToToken ) { const amountsOut = Array ( poolPairData . allBalancesScaled . length ) . fill ( ZERO ) ; amountsOut [ poolPairData . tokenIndexOut ] = bnum ( amountConverted . toString ( ) ) ; amt = SDK . StableMath . _calcBptInGivenExactTokensOut ( bnum ( this . amp . toString ( ) ) , poolPairData . allBalancesScaled . map ( ( b ) => bnum ( b . toString ( ) ) ) , amountsOut , bnum ( poolPairData . virtualBptSupply . toString ( ) ) , ZERO ) ; } else { amt = SDK . StableMath . _calcInGivenOut ( bnum ( this . amp . toString ( ) ) , poolPairData . allBalancesScaled . map ( ( b ) => bnum ( b . toString ( ) ) ) , poolPairData . tokenIndexIn , poolPairData . tokenIndexOut , amountConverted , ZERO ) ; } const returnScaled = amt . div ( formatFixed ( poolPairData . tokenInPriceRate , 18 ) ) . dp ( 0 ) ; const returnWithFee = bnum ( this . addSwapFeeAmount ( BigNumber . from ( returnScaled . toString ( ) ) , poolPairData . swapFee ) . toString ( ) ) ; return scale ( returnWithFee , - 18 ) ; } catch ( err ) { console . error ( `PhantomStable _evminGivenOut: ${ err . message }` ) ; return ZERO ; } } _spotPriceAfterSwapExactTokenInForTokenOut ( poolPairData , amount ) { const priceRateIn = formatFixed ( poolPairData . tokenInPriceRate , 18 ) ; const priceRateOut = formatFixed ( poolPairData . tokenOutPriceRate , 18 ) ; const amountConverted = amount . times ( formatFixed ( poolPairData . tokenInPriceRate , 18 ) ) ; let result ; if ( poolPairData . pairType === PairTypes . TokenToBpt ) { result = phantomStableMath . _spotPriceAfterSwapExactTokenInForBPTOut ( amountConverted , poolPairData ) ; } else if ( poolPairData . pairType === PairTypes . BptToToken ) { result = phantomStableMath . _spotPriceAfterSwapExactBPTInForTokenOut ( amountConverted , poolPairData ) ; } else { result = phantomStableMath . _spotPriceAfterSwapExactTokenInForTokenOut ( amountConverted , poolPairData ) ; } return result . div ( priceRateIn ) . times ( priceRateOut ) ; } _spotPriceAfterSwapTokenInForExactTokenOut ( poolPairData , amount ) { const priceRateIn = formatFixed ( poolPairData . tokenInPriceRate , 18 ) ; const priceRateOut = formatFixed ( poolPairData . tokenOutPriceRate , 18 ) ; const amountConverted = amount . times ( formatFixed ( poolPairData . tokenOutPriceRate , 18 ) ) ; let result ; if ( poolPairData . pairType === PairTypes . TokenToBpt ) { result = phantomStableMath . _spotPriceAfterSwapTokenInForExactBPTOut ( amountConverted , poolPairData ) ; } else if ( poolPairData . pairType === PairTypes . BptToToken ) { result = phantomStableMath . _spotPriceAfterSwapBPTInForExactTokenOut ( amountConverted , poolPairData ) ; } else { result = phantomStableMath . _spotPriceAfterSwapTokenInForExactTokenOut ( amountConverted , poolPairData ) ; } return result . div ( priceRateIn ) . times ( priceRateOut ) ; } _derivativeSpotPriceAfterSwapExactTokenInForTokenOut ( poolPairData , amount ) { const priceRateOut = formatFixed ( poolPairData . tokenOutPriceRate , 18 ) ; const amountConverted = amount . times ( formatFixed ( poolPairData . tokenInPriceRate , 18 ) ) ; let result ; if ( poolPairData . pairType === PairTypes . TokenToBpt ) { result = phantomStableMath . _derivativeSpotPriceAfterSwapExactTokenInForBPTOut ( amountConverted , poolPairData ) ; } else if ( poolPairData . pairType === PairTypes . BptToToken ) { result = phantomStableMath . _derivativeSpotPriceAfterSwapExactBPTInForTokenOut ( amountConverted , poolPairData ) ; } else { result = phantomStableMath . _derivativeSpotPriceAfterSwapExactTokenInForTokenOut ( amountConverted , poolPairData ) ; } return result . times ( priceRateOut ) ; } _derivativeSpotPriceAfterSwapTokenInForExactTokenOut ( poolPairData , amount ) { const priceRateIn = formatFixed ( poolPairData . tokenInPriceRate , 18 ) ; const priceRateOut = formatFixed ( poolPairData . tokenOutPriceRate , 18 ) ; const amountConverted = amount . times ( formatFixed ( poolPairData . tokenOutPriceRate , 18 ) ) ; let result ; if ( poolPairData . pairType === PairTypes . TokenToBpt ) { result = phantomStableMath . _derivativeSpotPriceAfterSwapTokenInForExactBPTOut ( amountConverted , poolPairData ) ; } else if ( poolPairData . pairType === PairTypes . BptToToken ) { result = phantomStableMath . _derivativeSpotPriceAfterSwapBPTInForExactTokenOut ( amountConverted , poolPairData ) ; } else { result = phantomStableMath . _derivativeSpotPriceAfterSwapTokenInForExactTokenOut ( amountConverted , poolPairData ) ; } return result . div ( priceRateIn ) . times ( priceRateOut ) . times ( priceRateOut ) ; } subtractSwapFeeAmount ( amount , swapFee ) { const feeAmount = amount . mul ( swapFee ) . div ( ONE ) ; return amount . sub ( feeAmount ) ; } addSwapFeeAmount ( amount , swapFee ) { const feeAmount = ONE . sub ( swapFee ) ; return amount . mul ( ONE ) . add ( feeAmount . sub ( 1 ) ) . div ( feeAmount ) ; } }