Skip to content

Commit

Permalink
feat: v3 boosted adds/removes (#146)
Browse files Browse the repository at this point in the history
* feat: unbalanced v3 boosted adds

* chore: skip sepolia test-api related tests

* fix: add wrapping tokens to boosted actionable

* feat: explicit handler for boosted proportional removes

* fix: use explicit boosted sign function
  • Loading branch information
agualis authored Nov 7, 2024
1 parent 55f7468 commit ca1f61b
Show file tree
Hide file tree
Showing 29 changed files with 724 additions and 132 deletions.
7 changes: 5 additions & 2 deletions apps/frontend-v3/app/(app)/debug/pools/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ export default function DebugPools() {
<Link as={NextLink} href="/pools/sepolia/v3/0x75F49D54978d08E4E76a873dA6c78E8f6b2901C2">
Sepolia WEIGHTED with Proportional joins (Balancer 50 BAL 50 WETH -ExitFee Hook)
</Link>
<Link as={NextLink} href="/pools/sepolia/v3/0xec9821d4a8b0976353e85fa872cedeffbbf306f2">
Sepolia reference BOOSTED pool (Balancer DAI/USDC/USDT)
<Link as={NextLink} href="/pools/sepolia/v3/0x6dbdd7a36d900083a5b86a55583d90021e9f33e8">
Sepolia reference BOOSTED pool (Balancer USDC/USDT)
</Link>
<Link as={NextLink} href="/pools/sepolia/v3/0x0270daf4ee12ccb1abc8aa365054eecb1b7f4f6b">
Sepolia reference NESTED pool (Balancer 50 WETH 50 USD)
</Link>
</VStack>

Expand Down
1 change: 1 addition & 0 deletions packages/lib/config/config.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface ContractsConfig {
*/
router?: Address
batchRouter?: Address
compositeLiquidityRouter?: Address
relayerV6: Address
minter: Address
}
Expand Down
9 changes: 8 additions & 1 deletion packages/lib/config/networks/sepolia.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { GqlChain } from '@repo/lib/shared/services/api/generated/graphql'
import { NetworkConfig } from '../config.types'
import { convertHexToLowerCase } from '@repo/lib/shared/utils/objects'
import { BALANCER_BATCH_ROUTER, BALANCER_ROUTER, PERMIT2, VAULT_V3 } from '@balancer/sdk'
import {
BALANCER_BATCH_ROUTER,
BALANCER_COMPOSITE_LIQUIDITY_ROUTER,
BALANCER_ROUTER,
PERMIT2,
VAULT_V3,
} from '@balancer/sdk'
import { sepolia } from 'viem/chains'

const networkConfig: NetworkConfig = {
Expand Down Expand Up @@ -36,6 +42,7 @@ const networkConfig: NetworkConfig = {
vaultV3: VAULT_V3[sepolia.id],
router: BALANCER_ROUTER[sepolia.id],
batchRouter: BALANCER_BATCH_ROUTER[sepolia.id],
compositeLiquidityRouter: BALANCER_COMPOSITE_LIQUIDITY_ROUTER[sepolia.id],
relayerV6: '0x7852fB9d0895e6e8b3EedA553c03F6e2F9124dF9',
minter: '0x1783Cd84b3d01854A96B4eD5843753C2CcbD574A',
},
Expand Down
2 changes: 2 additions & 0 deletions packages/lib/debug-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const vaultV3Address = sepoliaNetworkConfig.contracts.balancer.vaultV3 as
export const poolId = '0x68e3266c9c8bbd44ad9dca5afbfe629022aee9fe000200000000000000000512' as const // Balancer Weighted wjAura and WETH

export const sepoliaRouter = sepoliaNetworkConfig.contracts.balancer.router
export const sepoliaCompositeRouter =
sepoliaNetworkConfig.contracts.balancer.compositeLiquidityRouter

/*
Used to pretty print objects when debugging
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
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 { GqlChain, GqlPoolElement } from '@repo/lib/shared/services/api/generated/graphql'
import { getPoolMock } from '../__mocks__/getPoolMock'
import { allPoolTokens } from '../pool.helpers'
import { LiquidityActionHelpers } from './LiquidityActionHelpers'
Expand Down Expand Up @@ -46,12 +46,16 @@ describe('Calculates toInputAmounts from allPoolTokens', () => {
{ humanAmount: '100', tokenAddress: daiAddress },
]

expect(allPoolTokens(nestedPool).map(t => t.address)).toEqual([
usdcDaiUsdtBptAddress, // Phantom BPT
expect(
allPoolTokens(nestedPool)
.map(t => t.address)
.sort()
).toEqual([
daiAddress,
usdcDaiUsdtBptAddress, // Phantom BPT
usdcAddress,
usdtAddress,
wETHAddress,
usdtAddress,
])

const helpers = new LiquidityActionHelpers(nestedPool)
Expand Down Expand Up @@ -91,26 +95,160 @@ describe('Calculates toInputAmounts from allPoolTokens', () => {
},
])
})
})

// Unskip when sepolia V3 pools are available in production api
describe.skip('Liquidity helpers for V3 Boosted pools', async () => {
const poolId = '0x6dbdd7a36d900083a5b86a55583d90021e9f33e8' // Sepolia stataEthUSDC stataEthUSDT

const usdcSepoliaAddress = '0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8'
const usdtSepoliaAddress = '0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0'
// const v3Pool = await getPoolMock(poolId, GqlChain.Sepolia)
const v3Pool = {} as GqlPoolElement
const helpers = new LiquidityActionHelpers(v3Pool)

// Unskip when sepolia V3 pools are available in production api
it.skip('for v3 BOOSTED pool', async () => {
const poolId = '0x6dbdd7a36d900083a5b86a55583d90021e9f33e8' // Sepolia stataEthUSDC stataEthUSDT
const humanAmountsIn: HumanTokenAmountWithAddress[] = [
{ humanAmount: '0.1', tokenAddress: usdcSepoliaAddress },
]

const usdcSepoliaAddress = '0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8'
const usdtSepoliaAddress = '0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0'
const v3Pool = await getPoolMock(poolId, GqlChain.Sepolia)
it('allPoolTokens', async () => {
expect(allPoolTokens(v3Pool).map(t => t.address)).toEqual([
usdcSepoliaAddress,
usdtSepoliaAddress,
])
})

it('allPoolTokens snapshot', async () => {
expect(allPoolTokens(v3Pool)).toMatchInlineSnapshot(`
[
{
"address": "0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8",
"decimals": 6,
"index": 0,
"name": "USDC (AAVE Faucet)",
"symbol": "usdc-aave",
},
{
"address": "0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0",
"decimals": 6,
"index": 1,
"name": "USDT (AAVE Faucet)",
"symbol": "usdt-aave",
},
]
`)
})

it('toInputAmounts', async () => {
expect(helpers.toInputAmounts(humanAmountsIn)).toEqual([
{
address: usdcSepoliaAddress,
decimals: 6,
rawAmount: 100000n,
},
])
})

it('boostedPoolState', async () => {
const helpers = new LiquidityActionHelpers(v3Pool)
expect(helpers.boostedPoolState).toMatchObject({
address: '0x6dbdd7a36d900083a5b86a55583d90021e9f33e8',
id: '0x6dbdd7a36d900083a5b86a55583d90021e9f33e8',
protocolVersion: 3,
tokens: [
{
address: '0x8a88124522dbbf1e56352ba3de1d9f78c143751e',
balance: expect.any(String),
balanceUSD: expect.any(String),
decimals: 6,
hasNestedPool: false,
id: '0x6dbdd7a36d900083a5b86a55583d90021e9f33e8-0x8a88124522dbbf1e56352ba3de1d9f78c143751e',
index: 0,
isAllowed: true,
isErc4626: true,
name: 'Static Aave Ethereum USDC',
nestedPool: null,
priceRate: expect.any(String),
priceRateProvider: '0x34101091673238545de8a846621823d9993c3085',
priceRateProviderData: null,
symbol: 'stataEthUSDC',
underlyingToken: {
address: '0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8',
decimals: 6,
index: 0,
name: 'USDC (AAVE Faucet)',
symbol: 'usdc-aave',
},
weight: null,
},
{
address: '0x978206fae13faf5a8d293fb614326b237684b750',
balance: expect.any(String),
balanceUSD: expect.any(String),
decimals: 6,
hasNestedPool: false,
id: '0x6dbdd7a36d900083a5b86a55583d90021e9f33e8-0x978206fae13faf5a8d293fb614326b237684b750',
index: 1,
isAllowed: true,
isErc4626: true,
name: 'Static Aave Ethereum USDT',
nestedPool: null,
priceRate: expect.any(String),
priceRateProvider: '0xb1b171a07463654cc1fe3df4ec05f754e41f0a65',
priceRateProviderData: null,
symbol: 'stataEthUSDT',
underlyingToken: {
address: '0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0',
decimals: 6,
index: 1,
name: 'USDT (AAVE Faucet)',
symbol: 'usdt-aave',
},
weight: null,
},
],
type: 'Stable',
})
})
})

// Unskip when sepolia V3 pools are available in production api
describe.skip('Liquidity helpers for V3 NESTED pool', async () => {
const poolId = '0x0270daf4ee12ccb1abc8aa365054eecb1b7f4f6b' // Sepolia Balancer 50 WETH 50 USD

const usdcSepoliaAddress = '0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8'
const usdtSepoliaAddress = '0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0'
// const v3Pool = await getPoolMock(poolId, GqlChain.Sepolia)
const v3Pool = {} as GqlPoolElement

const helpers = new LiquidityActionHelpers(v3Pool)
const wethAddress = '0x7b79995e5f793a07bc00c21412e50ecae098e7f9'

const usdcUsdtSepoliaBptAddress = '0x6dbdd7a36d900083a5b86a55583d90021e9f33e8'

const aaveUSDCAddress = '0x8a88124522dbbf1e56352ba3de1d9f78c143751e'
const aaveUSDTAddress = '0x978206fae13faf5a8d293fb614326b237684b750'

const humanAmountsIn: HumanTokenAmountWithAddress[] = [
{ humanAmount: '0.1', tokenAddress: usdcSepoliaAddress },
]

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

const humanAmountsIn: HumanTokenAmountWithAddress[] = [
{ humanAmount: '0.1', tokenAddress: usdcSepoliaAddress },
]
const helpers = new LiquidityActionHelpers(v3Pool)
).toEqual([
usdcUsdtSepoliaBptAddress,
wethAddress,
aaveUSDCAddress,
usdcSepoliaAddress,
aaveUSDTAddress,
usdtSepoliaAddress,
])
})

it('toInputAmounts', async () => {
expect(helpers.toInputAmounts(humanAmountsIn)).toEqual([
{
address: usdcSepoliaAddress,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ it('returns poolState for non nested pools', () => {

it('returns NestedPoolState for nested pools', () => {
const helpers = new LiquidityActionHelpers(nestedPoolMock)
const nestedPoolState = helpers.nestedPoolState
const nestedPoolState = helpers.nestedPoolStateV2

expect(nestedPoolState.pools).toHaveLength(2)
const firstPool = nestedPoolState.pools[0]
Expand Down
37 changes: 36 additions & 1 deletion packages/lib/modules/pool/actions/LiquidityActionHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import {
PoolGetPool,
PoolState,
PoolStateWithBalances,
PoolStateWithUnderlyings,
PoolTokenWithUnderlying,
Token,
TokenAmount,
mapPoolToNestedPoolStateV2,
mapPoolToNestedPoolStateV3,
mapPoolType,
} from '@balancer/sdk'
import BigNumber from 'bignumber.js'
Expand Down Expand Up @@ -61,12 +64,44 @@ export class LiquidityActionHelpers {
}

/* Used by default nested SDK handlers */
public get nestedPoolState(): NestedPoolState {
public get nestedPoolStateV2(): NestedPoolState {
const result = mapPoolToNestedPoolStateV2(this.pool as PoolGetPool)
result.protocolVersion = 2
return result
}

/* Used by default nested SDK handlers */
public get nestedPoolStateV3(): NestedPoolState {
const result = mapPoolToNestedPoolStateV3(this.pool as PoolGetPool)
result.protocolVersion = 3
return result
}

/* Used by V3 boosted SDK handlers */
public get boostedPoolState(): PoolStateWithUnderlyings & { totalShares: HumanAmount } {
const poolTokensWithUnderlyings: PoolTokenWithUnderlying[] = this.pool.poolTokens.map(
(token, index) => ({
...token,
address: token.address as Address,
underlyingToken: {
...token.underlyingToken,
address: token.underlyingToken?.address as Address,
decimals: token.underlyingToken?.decimals as number,
index, //TODO: review that this index is always the expected one
},
})
)
const state: PoolStateWithUnderlyings & { totalShares: HumanAmount } = {
id: this.pool.id as Hex,
address: this.pool.address as Address,
protocolVersion: 3,
type: mapPoolType(this.pool.type),
tokens: poolTokensWithUnderlyings,
totalShares: this.pool.dynamicData.totalShares as HumanAmount,
}
return state
}

public get poolStateWithBalances(): PoolStateWithBalances {
return toPoolStateWithBalances(this.pool)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { ConnectWallet } from '@repo/lib/modules/web3/ConnectWallet'
import { BalAlert } from '@repo/lib/shared/components/alerts/BalAlert'
import { SafeAppAlert } from '@repo/lib/shared/components/alerts/SafeAppAlert'
import { useTokens } from '@repo/lib/modules/tokens/TokensProvider'
import { isBoosted } from '../../../pool.helpers'

// small wrapper to prevent out of context error
export function AddLiquidityForm() {
Expand Down Expand Up @@ -188,7 +189,11 @@ function AddLiquidityMainForm() {
<BalAlert content="You cannot add because the pool has no liquidity" status="warning" />
)}
<SafeAppAlert />
{!nestedAddLiquidityEnabled ? (
{/* //TODO:
Avoid proportional inputs to avoid error above until SDK calculateProportionalAmounts for boosted is implemented
https://github.com/balancer/b-sdk/issues/468
*/}
{!nestedAddLiquidityEnabled && !isBoosted(pool) ? (
<TokenInputsWithAddable
requiresProportionalInput={requiresProportionalInput(pool)}
tokenSelectDisclosureOpen={() => tokenSelectDisclosure.onOpen()}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* eslint-disable max-len */
import { getNetworkConfig } from '@repo/lib/config/app.config'
import { HumanTokenAmountWithAddress } from '@repo/lib/modules/tokens/token.types'
import { GqlChain, GqlPoolElement } from '@repo/lib/shared/services/api/generated/graphql'
import { defaultTestUserAccount } from '@repo/lib/test/anvil/anvil-setup'
import { getPoolMock } from '../../../__mocks__/getPoolMock'
import { BoostedUnbalancedAddLiquidityV3Handler } from './BoostedUnbalancedAddLiquidityV3.handler'
import { selectAddLiquidityHandler } from './selectAddLiquidityHandler'

// TODO: unskip this test when sepolia V3 pools are available in production api
describe.skip('When adding unbalanced liquidity for a V3 BOOSTED pool', async () => {
// Sepolia
const poolId = '0x6dbdd7a36d900083a5b86a55583d90021e9f33e8' // Sepolia stataEthUSDC stataEthUSDT

const usdcAaveAddress = '0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8' // Sepolia underlying usdcAave faucet address (temporary until we have the real one)
const usdtAaveAddress = '0xaA8E23Fb1079EA71e0a56F48a2aA51851D8433D0' // Sepolia underlying usdcAave faucet address (temporary until we have the real one)
// const v3Pool = await getPoolMock(poolId, GqlChain.Sepolia)
const v3Pool = {} as GqlPoolElement

const handler = selectAddLiquidityHandler(v3Pool) as BoostedUnbalancedAddLiquidityV3Handler

const humanAmountsIn: HumanTokenAmountWithAddress[] = [
{ humanAmount: '0.1', tokenAddress: usdcAaveAddress },
{ humanAmount: '0.1', tokenAddress: usdtAaveAddress },
]

it('calculates price impact', async () => {
const priceImpact = await handler.getPriceImpact(humanAmountsIn)
expect(priceImpact).toBe(0)
})

it('queries bptOut', async () => {
const result = await handler.simulate(humanAmountsIn)

expect(result.bptOut.amount).toBeGreaterThan(100000000000000n)
expect(result.bptOut.token.address).toBe(poolId)
})

it('builds Tx Config', async () => {
const queryOutput = await handler.simulate(humanAmountsIn)

const result = await handler.buildCallData({
humanAmountsIn,
account: defaultTestUserAccount,
slippagePercent: '0.2',
queryOutput,
})
const router = getNetworkConfig(GqlChain.Sepolia).contracts.balancer.compositeLiquidityRouter
expect(result.to).toBe(router)
expect(result.data).toBeDefined()
})
})
Loading

0 comments on commit ca1f61b

Please sign in to comment.