Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: replace deprecated API pool.allTokens by allPoolTokens function #121

Merged
merged 5 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/lib/modules/pool/__mocks__/getPoolMock.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable max-len */
import fetch from 'cross-fetch'

import { visit } from 'graphql/language/visitor'
Expand Down Expand Up @@ -39,10 +40,9 @@ export async function getPoolMock(
.then(response => response.json())
.then(result => result.data)) as GetPoolQuery

if (!getPoolQuery.pool) {
throw new Error(
`Pool not found in api ${process.env.NEXT_PUBLIC_BALANCER_API_URL} network ${chain} poolId ${poolId}`
)
if (!getPoolQuery?.pool) {
const errorMessage = `Pool not found in api ${process.env.NEXT_PUBLIC_BALANCER_API_URL} network ${chain} poolId ${poolId}`
throw new Error(errorMessage)
}

return getPoolQuery.pool as GqlPoolElement
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/* eslint-disable max-len */
import {
balAddress,
bpt3PoolAddress,
daiAddress,
usdcAddress,
usdtAddress,
wETHAddress,
} from '@repo/lib/debug-helpers'
import { HumanTokenAmountWithAddress } from '@repo/lib/modules/tokens/token.types'
import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql'
import { getPoolMock } from '../__mocks__/getPoolMock'
import { allPoolTokens } from '../pool.helpers'
import { LiquidityActionHelpers } from './LiquidityActionHelpers'

describe('Calculates toInputAmounts from allPoolTokens', () => {
it('for v2 weighted pool with no nested tokens', async () => {
const poolId = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014' // 80BAL-20WETH

const pool = await getPoolMock(poolId, GqlChain.Mainnet)

const humanAmountsIn: HumanTokenAmountWithAddress[] = [
{ humanAmount: '100', tokenAddress: balAddress },
]

expect(allPoolTokens(pool).map(t => t.address)).toEqual([balAddress, wETHAddress])

const helpers = new LiquidityActionHelpers(pool)

expect(helpers.toInputAmounts(humanAmountsIn)).toEqual([
{
address: balAddress,
decimals: 18,
rawAmount: 100000000000000000000n,
},
])
})

it('for v2 pool with nested BPTs', async () => {
// Balancer 50WETH-50-3pool
const poolId = '0x08775ccb6674d6bdceb0797c364c2653ed84f3840002000000000000000004f0'
const nestedPool = await getPoolMock(poolId, GqlChain.Mainnet)
const humanAmountsIn: HumanTokenAmountWithAddress[] = [
{ humanAmount: '100', tokenAddress: daiAddress },
]

expect(allPoolTokens(nestedPool).map(t => t.address)).toEqual([
daiAddress,
bpt3PoolAddress,
usdcAddress,
usdtAddress,
wETHAddress,
])

const helpers = new LiquidityActionHelpers(nestedPool)

expect(helpers.toInputAmounts(humanAmountsIn)).toMatchInlineSnapshot(`
[
{
"address": "0x6b175474e89094c44da98b954eedeac495271d0f",
"decimals": 18,
"rawAmount": 100000000000000000000n,
},
]
`)
})

// Unskip when sepolia V3 pools are available in production api
it.skip('for v3 BOOSTED pool', async () => {
const poolId = '0x6dbdd7a36d900083a5b86a55583d90021e9f33e8' // Sepolia stataEthUSDC stataEthUSDT

const usdcSepoliaAddress = '0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8'
const usdtSepoliaAddress = '0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0'
const v3Pool = await getPoolMock(poolId, GqlChain.Sepolia)

expect(
allPoolTokens(v3Pool)
.map(t => t.address)
.sort()
).toMatchInlineSnapshot([usdcSepoliaAddress, usdtSepoliaAddress])

const humanAmountsIn: HumanTokenAmountWithAddress[] = [
{ humanAmount: '0.1', tokenAddress: usdcSepoliaAddress },
]
const helpers = new LiquidityActionHelpers(v3Pool)

expect(helpers.toInputAmounts(humanAmountsIn)).toEqual([
{
address: usdcSepoliaAddress,
decimals: 6,
rawAmount: 100000n,
},
])
})
})
25 changes: 14 additions & 11 deletions packages/lib/modules/pool/actions/LiquidityActionHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
import { HumanTokenAmountWithAddress } from '../../tokens/token.types'
import { Pool } from '../PoolProvider'
import {
allPoolTokens,
isAffectedByCspIssue,
isComposableStableV1,
isCowAmmPool,
Expand Down Expand Up @@ -78,7 +79,7 @@ export class LiquidityActionHelpers {

public getAmountsToApprove(
humanAmountsIn: HumanTokenAmountWithAddress[],
isPermit2 = false
isPermit2 = false,
): TokenAmountToApprove[] {
return this.toInputAmounts(humanAmountsIn).map(({ address, rawAmount }) => {
return {
Expand All @@ -105,12 +106,14 @@ export class LiquidityActionHelpers {
decimals,
}
}
const token = this.pool.allTokens.find(token => isSameAddress(token.address, tokenAddress))

const allTokens = allPoolTokens(this.pool)
const token = allTokens.find(token => isSameAddress(token.address, tokenAddress))
if (!token) {
throw new Error(
`Provided token address ${tokenAddress} not found in pool tokens [${Object.keys(
this.pool.allTokens.map(t => t.address)
).join(' , \n')}]`
allTokens.map(t => t.address),
).join(' , \n')}]`,
)
}
return {
Expand Down Expand Up @@ -161,14 +164,14 @@ export function toHumanAmount(tokenAmount: TokenAmount): HumanAmount {

export function ensureLastQueryResponse<Q>(
liquidityActionDescription: string,
queryResponse?: Q
queryResponse?: Q,
): Q {
if (!queryResponse) {
// This should never happen but this is a check against potential regression bugs
console.error(`Missing queryResponse in ${liquidityActionDescription}`)
throw new SentryError(
`Missing queryResponse.
It looks that you tried to call useBuildCallData before the last query finished generating queryResponse`
It looks that you tried to call useBuildCallData before the last query finished generating queryResponse`,
)
}

Expand Down Expand Up @@ -245,13 +248,13 @@ export function toPoolStateWithBalances(pool: Pool): PoolStateWithBalances {
export function filterHumanAmountsIn(
humanAmountsIn: HumanTokenAmountWithAddress[],
tokenAddress: Address,
chain: GqlChain
chain: GqlChain,
) {
return humanAmountsIn.filter(
amountIn =>
!isSameAddress(amountIn.tokenAddress, tokenAddress) &&
!(isNativeAsset(tokenAddress, chain) && isWrappedNativeAsset(amountIn.tokenAddress, chain)) &&
!(isNativeAsset(amountIn.tokenAddress, chain) && isWrappedNativeAsset(tokenAddress, chain))
!(isNativeAsset(amountIn.tokenAddress, chain) && isWrappedNativeAsset(tokenAddress, chain)),
)
}

Expand Down Expand Up @@ -279,7 +282,7 @@ export function shouldShowNativeWrappedSelector(token: GqlToken, poolType: GqlPo

export function replaceWrappedWithNativeAsset(
validTokens: GqlToken[],
nativeAsset: GqlToken | undefined
nativeAsset: GqlToken | undefined,
) {
if (!nativeAsset) return validTokens
return validTokens.map(token => {
Expand All @@ -294,10 +297,10 @@ export function replaceWrappedWithNativeAsset(
export function injectNativeAsset(
validTokens: GqlToken[],
nativeAsset: GqlToken | undefined,
pool: Pool
pool: Pool,
) {
const isWrappedNativeAssetInPool = validTokens.find(token =>
isWrappedNativeAsset(token.address as Address, pool.chain)
isWrappedNativeAsset(token.address as Address, pool.chain),
)

if (
Expand Down
53 changes: 45 additions & 8 deletions packages/lib/modules/pool/pool.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { supportsNestedActions } from './actions/LiquidityActionHelpers'
import { getLeafTokens, PoolToken } from '../tokens/token.helpers'
import { GetTokenFn } from '../tokens/TokensProvider'
import { vaultV3Abi } from '@balancer/sdk'
import { TokenCore } from './pool.types'

/**
* METHODS
Expand Down Expand Up @@ -136,7 +137,7 @@ export function noInitLiquidity(pool: GqlPoolBase): boolean {
return new BigNumber(pool.dynamicData.totalShares || '0').eq(0)
}
export function preMintedBptIndex(pool: GqlPoolBase): number | void {
return pool.allTokens.findIndex(token => isSameAddress(token.address, pool.address))
return allPoolTokens(pool).findIndex(token => isSameAddress(token.address, pool.address))
}

export function calcBptPrice(totalLiquidity: string, totalShares: string): string {
Expand Down Expand Up @@ -182,7 +183,7 @@ type Pool = GetPoolQuery['pool']
export function usePoolHelpers(pool: Pool, chain: GqlChain) {
const gaugeExplorerLink = getBlockExplorerAddressUrl(
pool?.staking?.gauge?.gaugeAddress as Address,
chain,
chain
)
const poolExplorerLink = getBlockExplorerAddressUrl(pool.address as Address, chain)

Expand Down Expand Up @@ -220,7 +221,7 @@ export function isNotSupported(pool: Pool) {
*/
export function isClaimableGauge(
gauge: GqlPoolStakingGauge | GqlPoolStakingOtherGauge,
chain: GqlChain | number,
chain: GqlChain | number
): boolean {
return !(gauge.version === 1 && isNotMainnet(chain))
}
Expand Down Expand Up @@ -375,16 +376,16 @@ export function getNestedBptTokens(poolTokens: PoolToken[]) {
export function getNestedBptParentToken(poolTokens: PoolToken[], childTokenAddress: Address) {
const nestedBptToken = getNestedBptTokens(poolTokens).find(token =>
token.nestedPool?.tokens.some(nestedToken =>
isSameAddress(nestedToken.address, childTokenAddress),
),
isSameAddress(nestedToken.address, childTokenAddress)
)
)
if (!nestedBptToken) {
throw new Error(
`Provided nestedTokenAddress ${childTokenAddress} does not belong to any underlying token amongst the nested pool/s (${getNestedBptTokens(
poolTokens,
poolTokens
)
.map(t => t.symbol)
.join(' ,')})`,
.join(' ,')})`
)
}

Expand Down Expand Up @@ -413,7 +414,7 @@ export function getChildTokens(pool: Pool, poolActionableTokens?: GqlToken[]): G
export function toGqlTokens(
poolTokens: PoolToken[],
getToken: GetTokenFn,
chain: GqlChain,
chain: GqlChain
): GqlToken[] {
return poolTokens
.map(token => getToken(token.address, chain))
Expand All @@ -433,3 +434,39 @@ export function isPoolSwapAllowed(pool: Pool, token1: Address, token2: Address):
if (!isStandardRootToken(pool, token1) && !isStandardRootToken(pool, token2)) return false
return true
}

/*
Returns all the top level tokens + children nested tokens + ERC4626 underlying tokens.
That is, the tokens that we can use in the pool's actions (add/remove/swap)
*/
export function allPoolTokens(pool: Pool | GqlPoolBase): TokenCore[] {
const underlyingTokens: TokenCore[] = pool.poolTokens.flatMap((token, index) =>
token.isErc4626 ? ({ ...token.underlyingToken, index } as TokenCore) : []
)

const nestedChildrenTokens: PoolToken[] = pool.poolTokens
.flatMap(token => (token.nestedPool ? token.nestedPool.tokens : []))
.filter((token): token is PoolToken => token !== undefined)

const standardTopLevelTokens: PoolToken[] = pool.poolTokens
.flatMap(token => (!token.hasNestedPool && !token.isErc4626 ? token : []))
.filter((token): token is PoolToken => token !== undefined)

return underlyingTokens.concat(
toTokenCores(nestedChildrenTokens),
toTokenCores(standardTopLevelTokens)
)
}

function toTokenCores(poolTokens: PoolToken[]): TokenCore[] {
return poolTokens.map(
t =>
({
address: t.address as Address,
name: t.name,
symbol: t.symbol,
decimals: t.decimals,
index: t.index,
}) as TokenCore
)
}
14 changes: 13 additions & 1 deletion packages/lib/modules/pool/pool.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
parseAsString,
parseAsStringEnum,
} from 'nuqs'
import { Hex } from 'viem'
import { Address, Hex } from 'viem'

export type PoolId = Hex

Expand Down Expand Up @@ -129,3 +129,15 @@ export const poolListQueryStateParsers = {
parseAsStringEnum<PoolCategoryType>(Object.values(poolCategoryFilters))
).withDefault([]),
}

/*
Core token info required for pool actions
PoolToken and GqlTokens are super sets of TokenCore
*/
export type TokenCore = {
address: Address
name: string
symbol: string
decimals: number
index: number
}
Loading