From be82a1231fab72f551af60328e46220e7b21f666 Mon Sep 17 00:00:00 2001 From: guilherme-correa-s Date: Fri, 1 Dec 2023 16:30:18 -0300 Subject: [PATCH 1/7] feat: add simple implementation of a managed pool --- src/pools/weightedPool/managedPool.ts | 283 ++++++++++++++++++ src/pools/weightedPool/weightedMath.ts | 26 +- src/types.ts | 1 + test/managedPool.spec.ts | 174 +++++++++++ .../managedPools/managedPoolsTest.json | 35 +++ 5 files changed, 507 insertions(+), 12 deletions(-) create mode 100644 src/pools/weightedPool/managedPool.ts create mode 100644 test/managedPool.spec.ts create mode 100644 test/testData/managedPools/managedPoolsTest.json diff --git a/src/pools/weightedPool/managedPool.ts b/src/pools/weightedPool/managedPool.ts new file mode 100644 index 00000000..e5f3dbd1 --- /dev/null +++ b/src/pools/weightedPool/managedPool.ts @@ -0,0 +1,283 @@ +import { BigNumber, formatFixed, parseFixed } from '@ethersproject/bignumber'; +import { WeiPerEther as ONE, Zero } from '@ethersproject/constants'; +import { getAddress } from '@ethersproject/address'; +import { + BigNumber as OldBigNumber, + ZERO, + bnum, + scale, +} from '../../utils/bignumber'; +import { isSameAddress } from '../../utils'; +import { + PoolBase, + PoolPairBase, + PoolTypes, + SubgraphPoolBase, + SwapTypes, +} from '../../types'; +import { WeightedPoolToken } from './weightedPool'; +import { + _calcInGivenOut, + _calcOutGivenIn, + _derivativeSpotPriceAfterSwapExactTokenInForTokenOut, + _derivativeSpotPriceAfterSwapTokenInForExactTokenOut, + _spotPriceAfterSwapExactTokenInForTokenOut, + _spotPriceAfterSwapTokenInForExactTokenOut, +} from './weightedMath'; +import { universalNormalizedLiquidity } from '../liquidity'; + +export type ManagedPoolPairData = PoolPairBase & { + weightIn: BigNumber; + weightOut: BigNumber; +}; + +export class ManagedPool implements PoolBase { + poolType: PoolTypes = PoolTypes.Managed; + id: string; + address: string; + tokensList: string[]; + tokens: WeightedPoolToken[]; + totalWeight: BigNumber; + totalShares: BigNumber; + swapFee: BigNumber; + MAX_IN_RATIO = parseFixed('0.3', 18); + MAX_OUT_RATIO = parseFixed('0.3', 18); + mainIndex?: number | undefined; + isLBP?: boolean | undefined; + + constructor( + id: string, + address: string, + tokenList: string[], + tokens: WeightedPoolToken[], + totalWeight: string, + swapFee: string + ) { + this.id = id; + this.address = address; + this.tokensList = tokenList; + this.tokens = tokens; + this.totalWeight = parseFixed(totalWeight, 18); + this.swapFee = parseFixed(swapFee, 18); + } + + static fromPool(pool: SubgraphPoolBase): ManagedPool { + if (!pool.totalWeight) { + throw new Error('WeightedPool missing totalWeight'); + } + return new ManagedPool( + pool.id, + pool.address, + pool.tokensList, + pool.tokens as WeightedPoolToken[], + pool.totalWeight, + pool.swapFee + ); + } + + parsePoolPairData(tokenIn: string, tokenOut: string): ManagedPoolPairData { + if ( + isSameAddress(tokenIn, this.address) || + isSameAddress(tokenOut, this.address) + ) + throw 'Token cannot be BPT'; + 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 = tI.balance; + const decimalsIn = tI.decimals; + const weightIn = parseFixed(tI.weight, 18) + .mul(ONE) + .div(this.totalWeight); + 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 = tO.balance; + const decimalsOut = tO.decimals; + const weightOut = parseFixed(tO.weight, 18) + .mul(ONE) + .div(this.totalWeight); + + const poolPairData: ManagedPoolPairData = { + id: this.id, + address: this.address, + poolType: this.poolType, + swapFee: this.swapFee, + tokenIn: tokenIn, + tokenOut: tokenOut, + decimalsIn: Number(decimalsIn), + decimalsOut: Number(decimalsOut), + balanceIn: parseFixed(balanceIn, decimalsIn), + balanceOut: parseFixed(balanceOut, decimalsOut), + weightIn, + weightOut, + }; + + return poolPairData; + } + + getNormalizedLiquidity(poolPairData: ManagedPoolPairData): OldBigNumber { + return universalNormalizedLiquidity( + this._derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData, + ZERO + ) + ); + } + + getLimitAmountSwap( + poolPairData: PoolPairBase, + swapType: SwapTypes + ): OldBigNumber { + if (swapType === SwapTypes.SwapExactIn) { + return bnum( + formatFixed( + poolPairData.balanceIn.mul(this.MAX_IN_RATIO).div(ONE), + poolPairData.decimalsIn + ) + ); + } else { + return bnum( + formatFixed( + poolPairData.balanceOut.mul(this.MAX_OUT_RATIO).div(ONE), + poolPairData.decimalsOut + ) + ); + } + } + + updateTokenBalanceForPool(token: string, newBalance: BigNumber): void { + // token is BPT + if (isSameAddress(this.address, token)) { + this.updateTotalShares(newBalance); + } + // token is underlying in the pool + 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); + } + + updateTotalShares(newTotalShares: BigNumber): void { + this.totalShares = newTotalShares; + } + + _exactTokenInForTokenOut( + poolPairData: ManagedPoolPairData, + amount: OldBigNumber + ): OldBigNumber { + if (amount.isNaN()) return amount; + const amountIn = parseFixed(amount.dp(18, 1).toString(), 18).toBigInt(); + const decimalsIn = poolPairData.decimalsIn; + const decimalsOut = poolPairData.decimalsOut; + const balanceIn = parseFixed( + poolPairData.balanceIn.toString(), + 18 - decimalsIn + ).toBigInt(); + const balanceOut = parseFixed( + poolPairData.balanceOut.toString(), + 18 - decimalsOut + ).toBigInt(); + const normalizedWeightIn = poolPairData.weightIn.toBigInt(); + const normalizedWeightOut = poolPairData.weightOut.toBigInt(); + const swapFee = poolPairData.swapFee.toBigInt(); + try { + const returnAmt = _calcOutGivenIn( + balanceIn, + normalizedWeightIn, + balanceOut, + normalizedWeightOut, + amountIn, + swapFee + ); + return scale(bnum(returnAmt.toString()), -18); + } catch (err) { + return ZERO; + } + } + + _tokenInForExactTokenOut( + poolPairData: ManagedPoolPairData, + amount: OldBigNumber + ): OldBigNumber { + if (amount.isNaN()) return amount; + const amountOut = parseFixed( + amount.dp(18, 1).toString(), + 18 + ).toBigInt(); + const decimalsIn = poolPairData.decimalsIn; + const decimalsOut = poolPairData.decimalsOut; + const balanceIn = parseFixed( + poolPairData.balanceIn.toString(), + 18 - decimalsIn + ).toBigInt(); + const balanceOut = parseFixed( + poolPairData.balanceOut.toString(), + 18 - decimalsOut + ).toBigInt(); + const normalizedWeightIn = poolPairData.weightIn.toBigInt(); + const normalizedWeightOut = poolPairData.weightOut.toBigInt(); + const swapFee = poolPairData.swapFee.toBigInt(); + try { + const returnAmt = _calcInGivenOut( + balanceIn, + normalizedWeightIn, + balanceOut, + normalizedWeightOut, + amountOut, + swapFee + ); + // return human scaled + return scale(bnum(returnAmt.toString()), -18); + } catch (err) { + return ZERO; + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _calcTokensOutGivenExactBptIn(_: BigNumber): BigNumber[] { + return Array(this.tokensList.length).fill(Zero); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _calcBptOutGivenExactTokensIn(_: BigNumber[]): BigNumber { + return Zero; + } + + _spotPriceAfterSwapExactTokenInForTokenOut( + poolPairData: ManagedPoolPairData, + amount: OldBigNumber + ): OldBigNumber { + return _spotPriceAfterSwapExactTokenInForTokenOut(amount, poolPairData); + } + + _spotPriceAfterSwapTokenInForExactTokenOut( + poolPairData: ManagedPoolPairData, + amount: OldBigNumber + ): OldBigNumber { + return _spotPriceAfterSwapTokenInForExactTokenOut(amount, poolPairData); + } + + _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + poolPairData: ManagedPoolPairData, + amount: OldBigNumber + ): OldBigNumber { + return _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( + amount, + poolPairData + ); + } + + _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( + poolPairData: ManagedPoolPairData, + amount: OldBigNumber + ): OldBigNumber { + return _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( + amount, + poolPairData + ); + } +} diff --git a/src/pools/weightedPool/weightedMath.ts b/src/pools/weightedPool/weightedMath.ts index 142683a1..8c7b2861 100644 --- a/src/pools/weightedPool/weightedMath.ts +++ b/src/pools/weightedPool/weightedMath.ts @@ -5,6 +5,8 @@ import { MathSol, BZERO } from '../../utils/basicOperations'; const MAX_INVARIANT_RATIO = BigInt('3000000000000000000'); // 3e18 +type WeightedPoolMathPairData = Omit; + // The following function are BigInt versions implemented by Sergio. // BigInt was requested from integrators as it is more efficient. // Swap outcomes formulas should match exactly those from smart contracts. @@ -482,7 +484,7 @@ export function _calcDueProtocolSwapFeeBptAmount( // SwapType = 'swapExactIn' export function _spotPriceAfterSwapExactTokenInForTokenOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bi = parseFloat( formatFixed(poolPairData.balanceIn, poolPairData.decimalsIn) @@ -506,7 +508,7 @@ export function _spotPriceAfterSwapExactTokenInForTokenOut( // SwapType = 'swapExactOut' export function _spotPriceAfterSwapTokenInForExactTokenOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bi = parseFloat( formatFixed(poolPairData.balanceIn, poolPairData.decimalsIn) @@ -530,7 +532,7 @@ export function _spotPriceAfterSwapTokenInForExactTokenOut( // SwapType = 'swapExactIn' export function _spotPriceAfterSwapExactTokenInForBPTOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bi = parseFloat( formatFixed(poolPairData.balanceIn, poolPairData.decimalsIn) @@ -573,7 +575,7 @@ export function _spotPriceAfterSwapBptOutGivenExactTokenInBigInt( // SwapType = 'swapExactIn' export function _spotPriceAfterSwapExactBPTInForTokenOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bbpt = parseFloat( formatFixed(poolPairData.balanceIn, poolPairData.decimalsIn) @@ -597,7 +599,7 @@ export function _spotPriceAfterSwapExactBPTInForTokenOut( // SwapType = 'swapExactOut' export function _spotPriceAfterSwapBPTInForExactTokenOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bbpt = parseFloat(formatFixed(poolPairData.balanceIn, 18)); const Bo = parseFloat( @@ -619,7 +621,7 @@ export function _spotPriceAfterSwapBPTInForExactTokenOut( // SwapType = 'swapExactOut' export function _spotPriceAfterSwapTokenInForExactBPTOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bi = parseFloat( formatFixed(poolPairData.balanceIn, poolPairData.decimalsIn) @@ -642,7 +644,7 @@ export function _spotPriceAfterSwapTokenInForExactBPTOut( // SwapType = 'swapExactIn' export function _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bi = parseFloat( formatFixed(poolPairData.balanceIn, poolPairData.decimalsIn) @@ -661,7 +663,7 @@ export function _derivativeSpotPriceAfterSwapExactTokenInForTokenOut( // SwapType = 'swapExactOut' export function _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bi = parseFloat( formatFixed(poolPairData.balanceIn, poolPairData.decimalsIn) @@ -685,7 +687,7 @@ export function _derivativeSpotPriceAfterSwapTokenInForExactTokenOut( // SwapType = 'swapExactIn' export function _derivativeSpotPriceAfterSwapExactTokenInForBPTOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bi = parseFloat( formatFixed(poolPairData.balanceIn, poolPairData.decimalsIn) @@ -703,7 +705,7 @@ export function _derivativeSpotPriceAfterSwapExactTokenInForBPTOut( // SwapType = 'swapExactOut' export function _derivativeSpotPriceAfterSwapTokenInForExactBPTOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bi = parseFloat( formatFixed(poolPairData.balanceIn, poolPairData.decimalsIn) @@ -726,7 +728,7 @@ export function _derivativeSpotPriceAfterSwapTokenInForExactBPTOut( // SwapType = 'swapExactIn' export function _derivativeSpotPriceAfterSwapExactBPTInForTokenOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bbpt = parseFloat(formatFixed(poolPairData.balanceIn, 18)); const Bo = parseFloat( @@ -747,7 +749,7 @@ export function _derivativeSpotPriceAfterSwapExactBPTInForTokenOut( // SwapType = 'swapExactOut' export function _derivativeSpotPriceAfterSwapBPTInForExactTokenOut( amount: OldBigNumber, - poolPairData: WeightedPoolPairData + poolPairData: WeightedPoolMathPairData ): OldBigNumber { const Bbpt = parseFloat(formatFixed(poolPairData.balanceIn, 18)); const Bo = parseFloat( diff --git a/src/types.ts b/src/types.ts index daced257..7e0f27bc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,6 +32,7 @@ export enum PoolTypes { Gyro3, GyroE, Fx, + Managed, } export interface SwapOptions { diff --git a/test/managedPool.spec.ts b/test/managedPool.spec.ts new file mode 100644 index 00000000..91ca0a63 --- /dev/null +++ b/test/managedPool.spec.ts @@ -0,0 +1,174 @@ +import { expect } from 'chai'; +import cloneDeep from 'lodash.clonedeep'; +import { parseFixed } from '@ethersproject/bignumber'; +import { bnum } from '../src/utils/bignumber'; +import { SwapTypes } from '../src'; +import { ManagedPool } from '../src/pools/weightedPool/managedPool'; +import managedPools from './testData/managedPools/managedPoolsTest.json'; + +const MAX_RATIO = bnum(0.3); + +describe('ManagedPool', () => { + context('parsePoolPairData', () => { + it(`should correctly parse USDC > BAL`, async () => { + const pool = cloneDeep(managedPools).pools[0]; + const tokenIn = pool.tokens[0]; + const tokenOut = pool.tokens[1]; + const managedPool = ManagedPool.fromPool(pool); + const poolPairData = managedPool.parsePoolPairData( + tokenIn.address, + tokenOut.address + ); + + expect(poolPairData.swapFee.toString()).to.eq( + parseFixed(pool.swapFee, 18).toString() + ); + expect(poolPairData.id).to.eq(pool.id); + expect(poolPairData.tokenIn).to.eq(tokenIn.address); + expect(poolPairData.tokenOut).to.eq(tokenOut.address); + expect(poolPairData.balanceIn.toString()).to.eq( + parseFixed(tokenIn.balance, tokenIn.decimals).toString() + ); + expect(poolPairData.balanceOut.toString()).to.eq( + parseFixed(tokenOut.balance, tokenOut.decimals).toString() + ); + expect(poolPairData.weightIn.toString()).to.eq( + parseFixed(tokenIn.weight, 18).toString() + ); + expect(poolPairData.weightOut.toString()).to.eq( + parseFixed(tokenOut.weight, 18).toString() + ); + }); + }); + + context('limit amounts', () => { + it(`getLimitAmountSwap, BAL to USDC`, async () => { + const pool = cloneDeep(managedPools).pools[0]; + const tokenIn = pool.tokens[0]; + const tokenOut = pool.tokens[1]; + const managedPool = ManagedPool.fromPool(pool); + const poolPairData = managedPool.parsePoolPairData( + tokenIn.address, + tokenOut.address + ); + + let amount = managedPool.getLimitAmountSwap( + poolPairData, + SwapTypes.SwapExactIn + ); + + expect(amount.toString()).to.eq( + bnum(tokenIn.balance).times(MAX_RATIO).toString() + ); + + amount = managedPool.getLimitAmountSwap( + poolPairData, + SwapTypes.SwapExactOut + ); + + expect(amount.toString()).to.eq( + bnum(tokenOut.balance).times(MAX_RATIO).toString() + ); + }); + + it(`getLimitAmountSwap, USDC to BAL`, async () => { + const pool = cloneDeep(managedPools).pools[0]; + const tokenIn = pool.tokens[1]; + const tokenOut = pool.tokens[0]; + const managedPool = ManagedPool.fromPool(pool); + const poolPairData = managedPool.parsePoolPairData( + tokenIn.address, + tokenOut.address + ); + + let amount = managedPool.getLimitAmountSwap( + poolPairData, + SwapTypes.SwapExactIn + ); + + expect(amount.toString()).to.eq( + bnum(tokenIn.balance).times(MAX_RATIO).toString() + ); + + amount = managedPool.getLimitAmountSwap( + poolPairData, + SwapTypes.SwapExactOut + ); + + expect(amount.toString()).to.eq( + bnum(tokenOut.balance).times(MAX_RATIO).toString() + ); + }); + }); + + context('Test Swaps', () => { + context('_exactTokenInForTokenOut', () => { + it('BAL>USDC', async () => { + const pool = cloneDeep(managedPools).pools[0]; + const tokenIn = pool.tokens[0]; + const tokenOut = pool.tokens[1]; + const amountIn = bnum('1.5'); + const managedPool = ManagedPool.fromPool(pool); + const poolPairData = managedPool.parsePoolPairData( + tokenIn.address, + tokenOut.address + ); + const amountOut = managedPool._exactTokenInForTokenOut( + poolPairData, + amountIn + ); + expect(amountOut.toString()).to.eq('5.61881163893890533'); + }); + it('USDC>BAL', async () => { + const pool = cloneDeep(managedPools).pools[0]; + const tokenIn = pool.tokens[1]; + const tokenOut = pool.tokens[0]; + const amountIn = bnum('5.61881163893890533'); + const managedPool = ManagedPool.fromPool(pool); + const poolPairData = managedPool.parsePoolPairData( + tokenIn.address, + tokenOut.address + ); + const amountOut = managedPool._exactTokenInForTokenOut( + poolPairData, + amountIn + ); + expect(amountOut.toString()).to.eq('1.489550744765116'); + }); + }); + context('_tokenInForExactTokenOut', () => { + it('BAL>DAI', async () => { + const pool = cloneDeep(managedPools).pools[0]; + const tokenIn = pool.tokens[0]; + const tokenOut = pool.tokens[1]; + const amountOut = bnum('5.5'); + const managedPool = ManagedPool.fromPool(pool); + const poolPairData = managedPool.parsePoolPairData( + tokenIn.address, + tokenOut.address + ); + const amountIn = managedPool._tokenInForExactTokenOut( + poolPairData, + amountOut + ); + expect(amountIn.toString()).to.eq('1.468235525276634269'); + }); + it('DAI>BAL', async () => { + const pool = cloneDeep(managedPools).pools[0]; + const tokenIn = pool.tokens[1]; + const tokenOut = pool.tokens[0]; + const amountOut = bnum('1.5'); + const managedPool = ManagedPool.fromPool(pool); + const poolPairData = managedPool.parsePoolPairData( + tokenIn.address, + tokenOut.address + ); + const amountIn = managedPool._tokenInForExactTokenOut( + poolPairData, + amountOut + ); + expect(amountIn.toString()).to.eq('5.658287029780740079'); + }); + }); + }); +}); diff --git a/test/testData/managedPools/managedPoolsTest.json b/test/testData/managedPools/managedPoolsTest.json new file mode 100644 index 00000000..d6161d0d --- /dev/null +++ b/test/testData/managedPools/managedPoolsTest.json @@ -0,0 +1,35 @@ +{ + "pools": [ + { + "id": "managed-pool", + "address": "0x0000000000000000000000000000000000000000", + "swapFee": "0.002", + "swapEnabled": true, + "tokens": [ + { + "address": "0xba100000625a3754423978a60c9317c58a424e3d", + "balance": "1000.0", + "decimals": 18, + "weight": "0.5", + "priceRate": "1", + "symbol": "BAL" + }, + { + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "balance": "3759.0", + "decimals": 6, + "weight": "0.5", + "priceRate": "1", + "symbol": "USDC" + } + ], + "tokensList": [ + "0x6b175474e89094c44da98b954eedeac495271d0f", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + ], + "totalWeight": "1", + "totalShares": "10000000000000", + "poolType": "ManagedPool" + } + ] +} From 655156e1411325b997b9b353d58bcba72c29d909 Mon Sep 17 00:00:00 2001 From: guilherme-correa-s Date: Mon, 4 Dec 2023 18:08:55 -0300 Subject: [PATCH 2/7] rename managed pool --- .../kassandraManagedPool.ts} | 20 +++++++++++-------- ...l.spec.ts => kassandraManagedPool.spec.ts} | 20 +++++++++---------- ...st.json => kassandraManagedPoolsTest.json} | 2 +- 3 files changed, 23 insertions(+), 19 deletions(-) rename src/pools/{weightedPool/managedPool.ts => kassandraManaged/kassandraManagedPool.ts} (94%) rename test/{managedPool.spec.ts => kassandraManagedPool.spec.ts} (89%) rename test/testData/managedPools/{managedPoolsTest.json => kassandraManagedPoolsTest.json} (96%) diff --git a/src/pools/weightedPool/managedPool.ts b/src/pools/kassandraManaged/kassandraManagedPool.ts similarity index 94% rename from src/pools/weightedPool/managedPool.ts rename to src/pools/kassandraManaged/kassandraManagedPool.ts index e5f3dbd1..e97d3b20 100644 --- a/src/pools/weightedPool/managedPool.ts +++ b/src/pools/kassandraManaged/kassandraManagedPool.ts @@ -15,7 +15,7 @@ import { SubgraphPoolBase, SwapTypes, } from '../../types'; -import { WeightedPoolToken } from './weightedPool'; +import { WeightedPoolToken } from '../weightedPool/weightedPool'; import { _calcInGivenOut, _calcOutGivenIn, @@ -23,7 +23,7 @@ import { _derivativeSpotPriceAfterSwapTokenInForExactTokenOut, _spotPriceAfterSwapExactTokenInForTokenOut, _spotPriceAfterSwapTokenInForExactTokenOut, -} from './weightedMath'; +} from '../weightedPool/weightedMath'; import { universalNormalizedLiquidity } from '../liquidity'; export type ManagedPoolPairData = PoolPairBase & { @@ -31,8 +31,8 @@ export type ManagedPoolPairData = PoolPairBase & { weightOut: BigNumber; }; -export class ManagedPool implements PoolBase { - poolType: PoolTypes = PoolTypes.Managed; +export class KassandraManagedPool implements PoolBase { + poolType: PoolTypes = PoolTypes.KassandraManaged; id: string; address: string; tokensList: string[]; @@ -50,6 +50,7 @@ export class ManagedPool implements PoolBase { address: string, tokenList: string[], tokens: WeightedPoolToken[], + totalShares: string, totalWeight: string, swapFee: string ) { @@ -57,19 +58,21 @@ export class ManagedPool implements PoolBase { this.address = address; this.tokensList = tokenList; this.tokens = tokens; + this.totalShares = parseFixed(totalShares, 18); this.totalWeight = parseFixed(totalWeight, 18); this.swapFee = parseFixed(swapFee, 18); } - static fromPool(pool: SubgraphPoolBase): ManagedPool { + static fromPool(pool: SubgraphPoolBase): KassandraManagedPool { if (!pool.totalWeight) { throw new Error('WeightedPool missing totalWeight'); } - return new ManagedPool( + return new KassandraManagedPool( pool.id, pool.address, pool.tokensList, pool.tokens as WeightedPoolToken[], + pool.totalShares, pool.totalWeight, pool.swapFee ); @@ -79,8 +82,9 @@ export class ManagedPool implements PoolBase { if ( isSameAddress(tokenIn, this.address) || isSameAddress(tokenOut, this.address) - ) - throw 'Token cannot be BPT'; + ) { + throw new Error('Token cannot be BPT'); + } const tokenIndexIn = this.tokens.findIndex( (t) => getAddress(t.address) === getAddress(tokenIn) ); diff --git a/test/managedPool.spec.ts b/test/kassandraManagedPool.spec.ts similarity index 89% rename from test/managedPool.spec.ts rename to test/kassandraManagedPool.spec.ts index 91ca0a63..c5f257aa 100644 --- a/test/managedPool.spec.ts +++ b/test/kassandraManagedPool.spec.ts @@ -3,18 +3,18 @@ import cloneDeep from 'lodash.clonedeep'; import { parseFixed } from '@ethersproject/bignumber'; import { bnum } from '../src/utils/bignumber'; import { SwapTypes } from '../src'; -import { ManagedPool } from '../src/pools/weightedPool/managedPool'; -import managedPools from './testData/managedPools/managedPoolsTest.json'; +import { KassandraManagedPool } from '../src/pools/kassandraManaged/kassandraManagedPool'; +import managedPools from './testData/managedPools/kassandraManagedPoolsTest.json'; const MAX_RATIO = bnum(0.3); -describe('ManagedPool', () => { +describe('KassandraManagedPool', () => { context('parsePoolPairData', () => { it(`should correctly parse USDC > BAL`, async () => { const pool = cloneDeep(managedPools).pools[0]; const tokenIn = pool.tokens[0]; const tokenOut = pool.tokens[1]; - const managedPool = ManagedPool.fromPool(pool); + const managedPool = KassandraManagedPool.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -46,7 +46,7 @@ describe('ManagedPool', () => { const pool = cloneDeep(managedPools).pools[0]; const tokenIn = pool.tokens[0]; const tokenOut = pool.tokens[1]; - const managedPool = ManagedPool.fromPool(pool); + const managedPool = KassandraManagedPool.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -75,7 +75,7 @@ describe('ManagedPool', () => { const pool = cloneDeep(managedPools).pools[0]; const tokenIn = pool.tokens[1]; const tokenOut = pool.tokens[0]; - const managedPool = ManagedPool.fromPool(pool); + const managedPool = KassandraManagedPool.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -108,7 +108,7 @@ describe('ManagedPool', () => { const tokenIn = pool.tokens[0]; const tokenOut = pool.tokens[1]; const amountIn = bnum('1.5'); - const managedPool = ManagedPool.fromPool(pool); + const managedPool = KassandraManagedPool.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -124,7 +124,7 @@ describe('ManagedPool', () => { const tokenIn = pool.tokens[1]; const tokenOut = pool.tokens[0]; const amountIn = bnum('5.61881163893890533'); - const managedPool = ManagedPool.fromPool(pool); + const managedPool = KassandraManagedPool.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -142,7 +142,7 @@ describe('ManagedPool', () => { const tokenIn = pool.tokens[0]; const tokenOut = pool.tokens[1]; const amountOut = bnum('5.5'); - const managedPool = ManagedPool.fromPool(pool); + const managedPool = KassandraManagedPool.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -158,7 +158,7 @@ describe('ManagedPool', () => { const tokenIn = pool.tokens[1]; const tokenOut = pool.tokens[0]; const amountOut = bnum('1.5'); - const managedPool = ManagedPool.fromPool(pool); + const managedPool = KassandraManagedPool.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address diff --git a/test/testData/managedPools/managedPoolsTest.json b/test/testData/managedPools/kassandraManagedPoolsTest.json similarity index 96% rename from test/testData/managedPools/managedPoolsTest.json rename to test/testData/managedPools/kassandraManagedPoolsTest.json index d6161d0d..008529f7 100644 --- a/test/testData/managedPools/managedPoolsTest.json +++ b/test/testData/managedPools/kassandraManagedPoolsTest.json @@ -1,7 +1,7 @@ { "pools": [ { - "id": "managed-pool", + "id": "kassandra-managed-pool", "address": "0x0000000000000000000000000000000000000000", "swapFee": "0.002", "swapEnabled": true, From f531a6ac811a47c1956bc3c5767ee0f4fd067cd8 Mon Sep 17 00:00:00 2001 From: guilherme-correa-s Date: Mon, 4 Dec 2023 18:18:13 -0300 Subject: [PATCH 3/7] add balancer query address --- test/testScripts/constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/testScripts/constants.ts b/test/testScripts/constants.ts index 3d0436cf..d9dacbbd 100644 --- a/test/testScripts/constants.ts +++ b/test/testScripts/constants.ts @@ -370,6 +370,7 @@ export const ADDRESSES = { }, }, [Network.POLYGON]: { + balancerHelpers: '0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5', MATIC: { address: AddressZero, decimals: 18, From 0d88938ef0afb1df3f854719a59663666ad558aa Mon Sep 17 00:00:00 2001 From: guilherme-correa-s Date: Mon, 4 Dec 2023 18:26:25 -0300 Subject: [PATCH 4/7] add integration test --- src/pools/index.ts | 7 +- src/types.ts | 3 +- test/kassandraManagedPool.integration.spec.ts | 305 ++++++++++++++++++ test/lib/onchainData.ts | 18 +- 4 files changed, 329 insertions(+), 4 deletions(-) create mode 100644 test/kassandraManagedPool.integration.spec.ts diff --git a/src/pools/index.ts b/src/pools/index.ts index 45db5f49..912c0c39 100644 --- a/src/pools/index.ts +++ b/src/pools/index.ts @@ -10,6 +10,7 @@ import { Gyro3Pool } from './gyro3Pool/gyro3Pool'; import { GyroEPool } from './gyroEPool/gyroEPool'; import { GyroEV2Pool } from './gyroEV2Pool/gyroEV2Pool'; import { FxPool } from './xaveFxPool/fxPool'; +import { KassandraManagedPool } from './kassandraManaged/kassandraManagedPool'; import { BigNumber as OldBigNumber, INFINITY, @@ -41,6 +42,7 @@ export function parseNewPool( | GyroEPool | GyroEV2Pool | FxPool + | KassandraManagedPool | undefined { // We're not interested in any pools which don't allow swapping if (!pool.swapEnabled) return undefined; @@ -57,7 +59,8 @@ export function parseNewPool( | Gyro3Pool | GyroEPool | GyroEV2Pool - | FxPool; + | FxPool + | KassandraManagedPool; try { const isLinear = pool.poolType.toString().includes('Linear'); @@ -90,6 +93,8 @@ export function parseNewPool( newPool = GyroEPool.fromPool(pool); } } else if (pool.poolType === 'FX') newPool = FxPool.fromPool(pool); + else if (pool.poolType === 'KassandraManaged') + newPool = KassandraManagedPool.fromPool(pool); else { console.error( `Unknown pool type or type field missing: ${pool.poolType} ${pool.id}` diff --git a/src/types.ts b/src/types.ts index 7e0f27bc..a6db54ef 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,7 +32,7 @@ export enum PoolTypes { Gyro3, GyroE, Fx, - Managed, + KassandraManaged, } export interface SwapOptions { @@ -215,6 +215,7 @@ export enum PoolFilter { TetuLinear = 'TetuLinear', YearnLinear = 'YearnLinear', FX = 'FX', + KassandraManaged = 'KassandraManaged', } export interface PoolBase { diff --git a/test/kassandraManagedPool.integration.spec.ts b/test/kassandraManagedPool.integration.spec.ts new file mode 100644 index 00000000..091443d2 --- /dev/null +++ b/test/kassandraManagedPool.integration.spec.ts @@ -0,0 +1,305 @@ +import dotenv from 'dotenv'; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { SOR, SubgraphPoolBase, SwapTypes } from '../src'; +import { Network, vaultAddr } from './testScripts/constants'; +import { formatFixed, parseFixed } from '@ethersproject/bignumber'; +import { expect } from 'chai'; +import { KassandraManagedPool } from '../src/pools/kassandraManaged/kassandraManagedPool'; +import { setUp } from './testScripts/utils'; +import { WeightedPoolToken } from '../src/pools/weightedPool/weightedPool'; +import { Vault, Vault__factory } from '@balancer-labs/typechain'; +import { AddressZero } from '@ethersproject/constants'; + +dotenv.config(); + +const testPool: SubgraphPoolBase = { + id: '0x107cb7c6d67ad745c50d7d4627335c1c6a684003000100000000000000000c37', + address: '0x107cb7c6d67ad745c50d7d4627335c1c6a684003', + poolType: 'KassandraManaged', + swapFee: '0.003', + swapEnabled: true, + totalWeight: '1', + totalShares: '587.155942710616273381', + tokensList: [ + '0x107cb7c6d67ad745c50d7d4627335c1c6a684003', + '0x172370d5cd63279efa6d502dab29171933a610af', + '0x1a3acf6d19267e2d3e7f898f42803e90c9219062', + '0x50b728d8d964fd00c2d0aad81718b71311fef68a', + '0x6f7c932e7684666c9fd1d44527765433e01ff61d', + '0x8505b9d2254a7ae468c0e9dd10ccea3a837aef5c', + '0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3', + '0xb33eaad8d922b1083446dc23f610c2567fb5180f', + '0xc3c7d422809852031b44ab29eec9f1eff2a58756', + '0xd6df932a45c0f255f85145f286ea0b292b21c90b', + ], + tokens: [ + { + address: '0x172370d5cd63279efa6d502dab29171933a610af', + balance: '396.136772427774719658', + decimals: 18, + priceRate: '1', + weight: '0.0457', + }, + { + address: '0x1a3acf6d19267e2d3e7f898f42803e90c9219062', + balance: '32.15685999953081616', + decimals: 18, + priceRate: '1', + weight: '0.0482', + }, + { + address: '0x50b728d8d964fd00c2d0aad81718b71311fef68a', + balance: '128.635643494144033617', + decimals: 18, + priceRate: '1', + weight: '0.0885', + }, + { + address: '0x6f7c932e7684666c9fd1d44527765433e01ff61d', + balance: '0.376577387336367528', + decimals: 18, + priceRate: '1', + weight: '0.1187', + }, + { + address: '0x8505b9d2254a7ae468c0e9dd10ccea3a837aef5c', + balance: '3.075712849360558397', + decimals: 18, + priceRate: '1', + weight: '0.0322', + }, + { + address: '0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3', + balance: '22.14930620288425309', + decimals: 18, + priceRate: '1', + weight: '0.0169', + }, + { + address: '0xb33eaad8d922b1083446dc23f610c2567fb5180f', + balance: '300.69117873097954205', + decimals: 18, + priceRate: '1', + weight: '0.3911', + }, + { + address: '0xc3c7d422809852031b44ab29eec9f1eff2a58756', + balance: '331.466141781162778932', + decimals: 18, + priceRate: '1', + weight: '0.1648', + }, + { + address: '0xd6df932a45c0f255f85145f286ea0b292b21c90b', + balance: '4.675112151574476223', + decimals: 18, + priceRate: '1', + weight: '0.0939', + }, + ], +}; + +const networkId = Network.POLYGON; +const jsonRpcUrl = process.env.RPC_URL_POLYGON; +const rpcUrl = 'http://127.0.0.1:8137'; +const blockNumber = 50629622; +const provider = new JsonRpcProvider(rpcUrl, networkId); +let pool: KassandraManagedPool; +let sor: SOR; +let vault: Vault; + +const funds = { + sender: AddressZero, + recipient: AddressZero, + fromInternalBalance: false, + toInternalBalance: false, +}; + +describe('KassandraManaged', () => { + before(async function () { + sor = await setUp( + networkId, + provider, + [testPool], + jsonRpcUrl as string, + blockNumber + ); + await sor.fetchPools(); + const pools = sor.getPools(); + pool = KassandraManagedPool.fromPool(pools[0]); + vault = Vault__factory.connect(vaultAddr, provider); + }); + context('test swaps vs querySwap', () => { + context('single swap', () => { + context('exact in', () => { + it('should calc swap amount out CRV -> FXS', async () => { + const amountIn = parseFixed('1', 18); + + const swapInfo = await sor.getSwaps( + testPool.tokensList[1], + testPool.tokensList[2], + SwapTypes.SwapExactIn, + amountIn + ); + + const response = await vault.callStatic.queryBatchSwap( + SwapTypes.SwapExactIn, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + expect(swapInfo.swapAmount.toString()).eq( + response[0].toString() + ); + expect(swapInfo.returnAmount.toString()).eq( + response[1].abs().toString() + ); + }); + + it('should calc swap amount out BAL -> UNI', async () => { + const amountIn = parseFixed('0.1', 18); + + const swapInfo = await sor.getSwaps( + testPool.tokensList[6], + testPool.tokensList[7], + SwapTypes.SwapExactIn, + amountIn + ); + + const response = await vault.callStatic.queryBatchSwap( + SwapTypes.SwapExactIn, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + expect(swapInfo.swapAmount.toString()).eq( + response[0].toString() + ); + expect(swapInfo.returnAmount.toString()).eq( + response[1].abs().toString() + ); + }); + }); + + context('exact out', () => { + it('should calc swap amout in CRV -> FXS', async () => { + const amountIn = parseFixed('1', 18); + + const swapInfo = await sor.getSwaps( + testPool.tokensList[1], + testPool.tokensList[2], + SwapTypes.SwapExactOut, + amountIn + ); + + const response = await vault.callStatic.queryBatchSwap( + SwapTypes.SwapExactOut, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + expect(swapInfo.swapAmount.toString()).eq( + response[1].abs().toString() + ); + expect(swapInfo.returnAmount.toString()).eq( + response[0].abs().toString() + ); + }); + + it('should calc swap amount in BAL -> UNI', async () => { + const amountIn = parseFixed('1', 18); + + const swapInfo = await sor.getSwaps( + testPool.tokensList[6], + testPool.tokensList[7], + SwapTypes.SwapExactOut, + amountIn + ); + + const response = await vault.callStatic.queryBatchSwap( + SwapTypes.SwapExactOut, + swapInfo.swaps, + swapInfo.tokenAddresses, + funds + ); + expect(swapInfo.swapAmount.toString()).eq( + response[1].abs().toString() + ); + expect(swapInfo.returnAmount.toString()).eq( + response[0].abs().toString() + ); + }); + }); + }); + }); + context('test joins vs queryJoin', () => { + context('join with all tokens', () => { + it('should return zero in calc join call with all tokens', async () => { + const amountsIn = [ + parseFixed('0', 18), + parseFixed('0.123', 18), + parseFixed('0.456', 18), + parseFixed('0.123', 18), + parseFixed('0.456', 18), + parseFixed('0.123', 18), + parseFixed('0.456', 18), + parseFixed('0.123', 18), + parseFixed('0.456', 18), + parseFixed('0.123', 18), + ]; + const bptOut = pool._calcBptOutGivenExactTokensIn(amountsIn); + expect(bptOut.toString()).to.eq('0'); + }); + }); + context('join with single token', () => { + it('should returns zero in calc join call with single token', async () => { + const amountsIn = [parseFixed('0.789', 8), parseFixed('0', 18)]; + const bptOut = pool._calcBptOutGivenExactTokensIn(amountsIn); + + expect(bptOut.toString()).to.eq('0'); + }); + }); + }); + context('test exits vs queryExit', () => { + context('exit to single token', () => { + before(async function () { + const bptAsToken: WeightedPoolToken = { + address: pool.address, + balance: formatFixed(pool.totalShares, 18), + decimals: 18, + weight: '0', + }; + + pool.tokens.push(bptAsToken); + pool.tokensList.push(pool.address); + }); + it('should throw when token is BPT', async () => { + const tokenIndex = 0; + + const response = () => + pool.parsePoolPairData( + pool.address, + pool.tokensList[tokenIndex] + ); + + expect(response).throws('Token cannot be BPT'); + }); + after(async function () { + // Remove BPT that was artifically added to the pool + pool.tokens.pop(); + pool.tokensList.pop(); + }); + }); + context('exit to all tokens', async () => { + it('should return zero when calling calc exit with all tokens', async () => { + const bptIn = parseFixed('0.123', 18); + + const amountsOut = pool._calcTokensOutGivenExactBptIn(bptIn); + + amountsOut.forEach((amount) => { + expect(amount.toString()).to.eq('0'); + }); + }); + }); + }); +}); diff --git a/test/lib/onchainData.ts b/test/lib/onchainData.ts index 8313e77a..8e941bc5 100644 --- a/test/lib/onchainData.ts +++ b/test/lib/onchainData.ts @@ -92,6 +92,7 @@ export async function getOnChainBalances( // TO DO - Make this part of class to make more flexible? if ( pool.poolType === 'Weighted' || + pool.poolType === 'KassandraManaged' || pool.poolType === 'LiquidityBootstrapping' || pool.poolType === 'Investment' ) { @@ -277,14 +278,27 @@ export async function getOnChainBalances( subgraphPools[index].swapFee = formatFixed(swapFee, 18); - poolTokens.tokens.forEach((token, i) => { + const tokens = [...poolTokens.tokens]; + const balances = [...poolTokens.balances]; + + if (subgraphPools[index].poolType === 'KassandraManaged') { + tokens.shift(); + balances.shift(); + } + + tokens.forEach((token, i) => { + if ( + i === 0 && + subgraphPools[index].poolType === 'KassandraManaged' + ) + return; const T = subgraphPools[index].tokens.find((t) => isSameAddress(t.address, token) ); if (!T) throw `Pool Missing Expected Token: ${poolId} ${token}`; - T.balance = formatFixed(poolTokens.balances[i], T.decimals); + T.balance = formatFixed(balances[i], T.decimals); if (weights) { // Only expected for WeightedPools From df3efffcea07f40e88e4bdf6d795388829192306 Mon Sep 17 00:00:00 2001 From: guilherme-correa-s Date: Tue, 5 Dec 2023 10:29:29 -0300 Subject: [PATCH 5/7] refactor data of subgraph response --- src/pools/index.ts | 4 ++-- .../kassandraManagedPool.ts | 2 +- src/types.ts | 4 ++-- test/kassandraManagedPool.integration.spec.ts | 24 +++++++++---------- test/lib/onchainData.ts | 9 ++----- 5 files changed, 19 insertions(+), 24 deletions(-) rename src/pools/{kassandraManaged => managedPools}/kassandraManagedPool.ts (99%) diff --git a/src/pools/index.ts b/src/pools/index.ts index 912c0c39..0e254950 100644 --- a/src/pools/index.ts +++ b/src/pools/index.ts @@ -10,7 +10,7 @@ import { Gyro3Pool } from './gyro3Pool/gyro3Pool'; import { GyroEPool } from './gyroEPool/gyroEPool'; import { GyroEV2Pool } from './gyroEV2Pool/gyroEV2Pool'; import { FxPool } from './xaveFxPool/fxPool'; -import { KassandraManagedPool } from './kassandraManaged/kassandraManagedPool'; +import { KassandraManagedPool } from './managedPools/kassandraManagedPool'; import { BigNumber as OldBigNumber, INFINITY, @@ -93,7 +93,7 @@ export function parseNewPool( newPool = GyroEPool.fromPool(pool); } } else if (pool.poolType === 'FX') newPool = FxPool.fromPool(pool); - else if (pool.poolType === 'KassandraManaged') + else if (pool.poolType === 'Managed') newPool = KassandraManagedPool.fromPool(pool); else { console.error( diff --git a/src/pools/kassandraManaged/kassandraManagedPool.ts b/src/pools/managedPools/kassandraManagedPool.ts similarity index 99% rename from src/pools/kassandraManaged/kassandraManagedPool.ts rename to src/pools/managedPools/kassandraManagedPool.ts index e97d3b20..f1ce0d47 100644 --- a/src/pools/kassandraManaged/kassandraManagedPool.ts +++ b/src/pools/managedPools/kassandraManagedPool.ts @@ -32,7 +32,7 @@ export type ManagedPoolPairData = PoolPairBase & { }; export class KassandraManagedPool implements PoolBase { - poolType: PoolTypes = PoolTypes.KassandraManaged; + poolType: PoolTypes = PoolTypes.Managed; id: string; address: string; tokensList: string[]; diff --git a/src/types.ts b/src/types.ts index a6db54ef..6df831f3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,7 +32,7 @@ export enum PoolTypes { Gyro3, GyroE, Fx, - KassandraManaged, + Managed, } export interface SwapOptions { @@ -215,7 +215,7 @@ export enum PoolFilter { TetuLinear = 'TetuLinear', YearnLinear = 'YearnLinear', FX = 'FX', - KassandraManaged = 'KassandraManaged', + Managed = 'Managed', } export interface PoolBase { diff --git a/test/kassandraManagedPool.integration.spec.ts b/test/kassandraManagedPool.integration.spec.ts index 091443d2..2cb13870 100644 --- a/test/kassandraManagedPool.integration.spec.ts +++ b/test/kassandraManagedPool.integration.spec.ts @@ -4,7 +4,7 @@ import { SOR, SubgraphPoolBase, SwapTypes } from '../src'; import { Network, vaultAddr } from './testScripts/constants'; import { formatFixed, parseFixed } from '@ethersproject/bignumber'; import { expect } from 'chai'; -import { KassandraManagedPool } from '../src/pools/kassandraManaged/kassandraManagedPool'; +import { KassandraManagedPool } from '../src/pools/managedPools/kassandraManagedPool'; import { setUp } from './testScripts/utils'; import { WeightedPoolToken } from '../src/pools/weightedPool/weightedPool'; import { Vault, Vault__factory } from '@balancer-labs/typechain'; @@ -15,7 +15,7 @@ dotenv.config(); const testPool: SubgraphPoolBase = { id: '0x107cb7c6d67ad745c50d7d4627335c1c6a684003000100000000000000000c37', address: '0x107cb7c6d67ad745c50d7d4627335c1c6a684003', - poolType: 'KassandraManaged', + poolType: 'Managed', swapFee: '0.003', swapEnabled: true, totalWeight: '1', @@ -38,63 +38,63 @@ const testPool: SubgraphPoolBase = { balance: '396.136772427774719658', decimals: 18, priceRate: '1', - weight: '0.0457', + weight: null, }, { address: '0x1a3acf6d19267e2d3e7f898f42803e90c9219062', balance: '32.15685999953081616', decimals: 18, priceRate: '1', - weight: '0.0482', + weight: null, }, { address: '0x50b728d8d964fd00c2d0aad81718b71311fef68a', balance: '128.635643494144033617', decimals: 18, priceRate: '1', - weight: '0.0885', + weight: null, }, { address: '0x6f7c932e7684666c9fd1d44527765433e01ff61d', balance: '0.376577387336367528', decimals: 18, priceRate: '1', - weight: '0.1187', + weight: null, }, { address: '0x8505b9d2254a7ae468c0e9dd10ccea3a837aef5c', balance: '3.075712849360558397', decimals: 18, priceRate: '1', - weight: '0.0322', + weight: null, }, { address: '0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3', balance: '22.14930620288425309', decimals: 18, priceRate: '1', - weight: '0.0169', + weight: null, }, { address: '0xb33eaad8d922b1083446dc23f610c2567fb5180f', balance: '300.69117873097954205', decimals: 18, priceRate: '1', - weight: '0.3911', + weight: null, }, { address: '0xc3c7d422809852031b44ab29eec9f1eff2a58756', balance: '331.466141781162778932', decimals: 18, priceRate: '1', - weight: '0.1648', + weight: null, }, { address: '0xd6df932a45c0f255f85145f286ea0b292b21c90b', balance: '4.675112151574476223', decimals: 18, priceRate: '1', - weight: '0.0939', + weight: null, }, ], }; @@ -115,7 +115,7 @@ const funds = { toInternalBalance: false, }; -describe('KassandraManaged', () => { +describe('Managed', () => { before(async function () { sor = await setUp( networkId, diff --git a/test/lib/onchainData.ts b/test/lib/onchainData.ts index 8e941bc5..e3667842 100644 --- a/test/lib/onchainData.ts +++ b/test/lib/onchainData.ts @@ -92,7 +92,7 @@ export async function getOnChainBalances( // TO DO - Make this part of class to make more flexible? if ( pool.poolType === 'Weighted' || - pool.poolType === 'KassandraManaged' || + pool.poolType === 'Managed' || pool.poolType === 'LiquidityBootstrapping' || pool.poolType === 'Investment' ) { @@ -281,17 +281,12 @@ export async function getOnChainBalances( const tokens = [...poolTokens.tokens]; const balances = [...poolTokens.balances]; - if (subgraphPools[index].poolType === 'KassandraManaged') { + if (subgraphPools[index].poolType === 'Managed') { tokens.shift(); balances.shift(); } tokens.forEach((token, i) => { - if ( - i === 0 && - subgraphPools[index].poolType === 'KassandraManaged' - ) - return; const T = subgraphPools[index].tokens.find((t) => isSameAddress(t.address, token) ); From 29c19d3454b9fc8b4827107aaec9c2ba5db5732b Mon Sep 17 00:00:00 2001 From: guilherme-correa-s Date: Tue, 12 Dec 2023 16:46:30 -0300 Subject: [PATCH 6/7] refactor: rename KassandraManagedPool and consider totalWeigth is "1" always --- src/pools/index.ts | 8 ++++---- ...aManagedPool.ts => MaganedPoolKassandra.ts} | 10 ++++------ ...> MaganedPoolKassandra.integration.spec.ts} | 8 ++++---- ...ol.spec.ts => MaganedPoolKassandra.spec.ts} | 18 +++++++++--------- 4 files changed, 21 insertions(+), 23 deletions(-) rename src/pools/managedPools/{kassandraManagedPool.ts => MaganedPoolKassandra.ts} (96%) rename test/{kassandraManagedPool.integration.spec.ts => MaganedPoolKassandra.integration.spec.ts} (98%) rename test/{kassandraManagedPool.spec.ts => MaganedPoolKassandra.spec.ts} (91%) diff --git a/src/pools/index.ts b/src/pools/index.ts index 0e254950..97e0062e 100644 --- a/src/pools/index.ts +++ b/src/pools/index.ts @@ -10,7 +10,7 @@ import { Gyro3Pool } from './gyro3Pool/gyro3Pool'; import { GyroEPool } from './gyroEPool/gyroEPool'; import { GyroEV2Pool } from './gyroEV2Pool/gyroEV2Pool'; import { FxPool } from './xaveFxPool/fxPool'; -import { KassandraManagedPool } from './managedPools/kassandraManagedPool'; +import { MaganedPoolKassandra } from './managedPools/MaganedPoolKassandra'; import { BigNumber as OldBigNumber, INFINITY, @@ -42,7 +42,7 @@ export function parseNewPool( | GyroEPool | GyroEV2Pool | FxPool - | KassandraManagedPool + | MaganedPoolKassandra | undefined { // We're not interested in any pools which don't allow swapping if (!pool.swapEnabled) return undefined; @@ -60,7 +60,7 @@ export function parseNewPool( | GyroEPool | GyroEV2Pool | FxPool - | KassandraManagedPool; + | MaganedPoolKassandra; try { const isLinear = pool.poolType.toString().includes('Linear'); @@ -94,7 +94,7 @@ export function parseNewPool( } } else if (pool.poolType === 'FX') newPool = FxPool.fromPool(pool); else if (pool.poolType === 'Managed') - newPool = KassandraManagedPool.fromPool(pool); + newPool = MaganedPoolKassandra.fromPool(pool); else { console.error( `Unknown pool type or type field missing: ${pool.poolType} ${pool.id}` diff --git a/src/pools/managedPools/kassandraManagedPool.ts b/src/pools/managedPools/MaganedPoolKassandra.ts similarity index 96% rename from src/pools/managedPools/kassandraManagedPool.ts rename to src/pools/managedPools/MaganedPoolKassandra.ts index f1ce0d47..a7d93161 100644 --- a/src/pools/managedPools/kassandraManagedPool.ts +++ b/src/pools/managedPools/MaganedPoolKassandra.ts @@ -31,7 +31,7 @@ export type ManagedPoolPairData = PoolPairBase & { weightOut: BigNumber; }; -export class KassandraManagedPool implements PoolBase { +export class MaganedPoolKassandra implements PoolBase { poolType: PoolTypes = PoolTypes.Managed; id: string; address: string; @@ -51,7 +51,6 @@ export class KassandraManagedPool implements PoolBase { tokenList: string[], tokens: WeightedPoolToken[], totalShares: string, - totalWeight: string, swapFee: string ) { this.id = id; @@ -59,21 +58,20 @@ export class KassandraManagedPool implements PoolBase { this.tokensList = tokenList; this.tokens = tokens; this.totalShares = parseFixed(totalShares, 18); - this.totalWeight = parseFixed(totalWeight, 18); + this.totalWeight = parseFixed('1', 18); this.swapFee = parseFixed(swapFee, 18); } - static fromPool(pool: SubgraphPoolBase): KassandraManagedPool { + static fromPool(pool: SubgraphPoolBase): MaganedPoolKassandra { if (!pool.totalWeight) { throw new Error('WeightedPool missing totalWeight'); } - return new KassandraManagedPool( + return new MaganedPoolKassandra( pool.id, pool.address, pool.tokensList, pool.tokens as WeightedPoolToken[], pool.totalShares, - pool.totalWeight, pool.swapFee ); } diff --git a/test/kassandraManagedPool.integration.spec.ts b/test/MaganedPoolKassandra.integration.spec.ts similarity index 98% rename from test/kassandraManagedPool.integration.spec.ts rename to test/MaganedPoolKassandra.integration.spec.ts index 2cb13870..09f12700 100644 --- a/test/kassandraManagedPool.integration.spec.ts +++ b/test/MaganedPoolKassandra.integration.spec.ts @@ -4,7 +4,7 @@ import { SOR, SubgraphPoolBase, SwapTypes } from '../src'; import { Network, vaultAddr } from './testScripts/constants'; import { formatFixed, parseFixed } from '@ethersproject/bignumber'; import { expect } from 'chai'; -import { KassandraManagedPool } from '../src/pools/managedPools/kassandraManagedPool'; +import { MaganedPoolKassandra } from '../src/pools/managedPools/MaganedPoolKassandra'; import { setUp } from './testScripts/utils'; import { WeightedPoolToken } from '../src/pools/weightedPool/weightedPool'; import { Vault, Vault__factory } from '@balancer-labs/typechain'; @@ -18,7 +18,7 @@ const testPool: SubgraphPoolBase = { poolType: 'Managed', swapFee: '0.003', swapEnabled: true, - totalWeight: '1', + totalWeight: '0', totalShares: '587.155942710616273381', tokensList: [ '0x107cb7c6d67ad745c50d7d4627335c1c6a684003', @@ -104,7 +104,7 @@ const jsonRpcUrl = process.env.RPC_URL_POLYGON; const rpcUrl = 'http://127.0.0.1:8137'; const blockNumber = 50629622; const provider = new JsonRpcProvider(rpcUrl, networkId); -let pool: KassandraManagedPool; +let pool: MaganedPoolKassandra; let sor: SOR; let vault: Vault; @@ -126,7 +126,7 @@ describe('Managed', () => { ); await sor.fetchPools(); const pools = sor.getPools(); - pool = KassandraManagedPool.fromPool(pools[0]); + pool = MaganedPoolKassandra.fromPool(pools[0]); vault = Vault__factory.connect(vaultAddr, provider); }); context('test swaps vs querySwap', () => { diff --git a/test/kassandraManagedPool.spec.ts b/test/MaganedPoolKassandra.spec.ts similarity index 91% rename from test/kassandraManagedPool.spec.ts rename to test/MaganedPoolKassandra.spec.ts index c5f257aa..2a7f82ec 100644 --- a/test/kassandraManagedPool.spec.ts +++ b/test/MaganedPoolKassandra.spec.ts @@ -3,18 +3,18 @@ import cloneDeep from 'lodash.clonedeep'; import { parseFixed } from '@ethersproject/bignumber'; import { bnum } from '../src/utils/bignumber'; import { SwapTypes } from '../src'; -import { KassandraManagedPool } from '../src/pools/kassandraManaged/kassandraManagedPool'; +import { MaganedPoolKassandra } from '../src/pools/managedPools/MaganedPoolKassandra'; import managedPools from './testData/managedPools/kassandraManagedPoolsTest.json'; const MAX_RATIO = bnum(0.3); -describe('KassandraManagedPool', () => { +describe('MaganedPoolKassandra', () => { context('parsePoolPairData', () => { it(`should correctly parse USDC > BAL`, async () => { const pool = cloneDeep(managedPools).pools[0]; const tokenIn = pool.tokens[0]; const tokenOut = pool.tokens[1]; - const managedPool = KassandraManagedPool.fromPool(pool); + const managedPool = MaganedPoolKassandra.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -46,7 +46,7 @@ describe('KassandraManagedPool', () => { const pool = cloneDeep(managedPools).pools[0]; const tokenIn = pool.tokens[0]; const tokenOut = pool.tokens[1]; - const managedPool = KassandraManagedPool.fromPool(pool); + const managedPool = MaganedPoolKassandra.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -75,7 +75,7 @@ describe('KassandraManagedPool', () => { const pool = cloneDeep(managedPools).pools[0]; const tokenIn = pool.tokens[1]; const tokenOut = pool.tokens[0]; - const managedPool = KassandraManagedPool.fromPool(pool); + const managedPool = MaganedPoolKassandra.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -108,7 +108,7 @@ describe('KassandraManagedPool', () => { const tokenIn = pool.tokens[0]; const tokenOut = pool.tokens[1]; const amountIn = bnum('1.5'); - const managedPool = KassandraManagedPool.fromPool(pool); + const managedPool = MaganedPoolKassandra.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -124,7 +124,7 @@ describe('KassandraManagedPool', () => { const tokenIn = pool.tokens[1]; const tokenOut = pool.tokens[0]; const amountIn = bnum('5.61881163893890533'); - const managedPool = KassandraManagedPool.fromPool(pool); + const managedPool = MaganedPoolKassandra.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -142,7 +142,7 @@ describe('KassandraManagedPool', () => { const tokenIn = pool.tokens[0]; const tokenOut = pool.tokens[1]; const amountOut = bnum('5.5'); - const managedPool = KassandraManagedPool.fromPool(pool); + const managedPool = MaganedPoolKassandra.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address @@ -158,7 +158,7 @@ describe('KassandraManagedPool', () => { const tokenIn = pool.tokens[1]; const tokenOut = pool.tokens[0]; const amountOut = bnum('1.5'); - const managedPool = KassandraManagedPool.fromPool(pool); + const managedPool = MaganedPoolKassandra.fromPool(pool); const poolPairData = managedPool.parsePoolPairData( tokenIn.address, tokenOut.address From d7477d6cbabd142c8f5bcfbfa8d7a22d288deb6b Mon Sep 17 00:00:00 2001 From: guilherme-correa-s Date: Thu, 14 Dec 2023 10:34:38 -0300 Subject: [PATCH 7/7] refactor: remove unnecessary checks --- src/pools/managedPools/MaganedPoolKassandra.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pools/managedPools/MaganedPoolKassandra.ts b/src/pools/managedPools/MaganedPoolKassandra.ts index a7d93161..6aaf7cc8 100644 --- a/src/pools/managedPools/MaganedPoolKassandra.ts +++ b/src/pools/managedPools/MaganedPoolKassandra.ts @@ -63,9 +63,6 @@ export class MaganedPoolKassandra implements PoolBase { } static fromPool(pool: SubgraphPoolBase): MaganedPoolKassandra { - if (!pool.totalWeight) { - throw new Error('WeightedPool missing totalWeight'); - } return new MaganedPoolKassandra( pool.id, pool.address,