Skip to content

Commit

Permalink
Merge pull request #478 from balancer/440-v3-price-impact-for-nested-…
Browse files Browse the repository at this point in the history
…pools

V3 price impact for nested pools
  • Loading branch information
johngrantuk authored Nov 7, 2024
2 parents 19248f1 + af74a48 commit 0454cae
Show file tree
Hide file tree
Showing 18 changed files with 428 additions and 374 deletions.
6 changes: 6 additions & 0 deletions .changeset/sour-bears-sin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@balancer/sdk": minor
---

Tidy nested types. Share common with boosted.
Support for V3 addLiquidityNested PI (including nested boosted).
55 changes: 37 additions & 18 deletions src/data/providers/balancer-api/modules/nested-pool-state/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { BalancerApiClient } from '../../client';
import { NestedPool, NestedPoolState } from '../../../../../entities';
import { MinimalToken } from '../../../../types';
import {
NestedPoolState,
NestedPoolV2,
NestedPoolV3,
PoolTokenWithUnderlying,
} from '../../../../../entities';
import { Address, Hex } from '../../../../../types';
import { mapPoolType } from '@/utils/poolTypeMapper';
import { API_CHAIN_NAMES, isSameAddress } from '@/utils';
Expand All @@ -19,17 +23,14 @@ export type PoolGetPool = {
};

export type UnderlyingToken = {
symbol: string;
address: Address;
decimals: number;
};

export type Token = {
symbol: string;
index: number;
address: Address;
decimals: number;
isErc4626: boolean;
underlyingToken: UnderlyingToken | null;
nestedPool: {
id: Hex;
Expand All @@ -54,22 +55,17 @@ export class NestedPools {
}
poolTokens {
index
symbol
address
decimals
isErc4626
nestedPool {
id
address
type
tokens {
index
address
symbol
decimals
isErc4626
underlyingToken {
symbol
address
decimals
}
Expand Down Expand Up @@ -113,17 +109,21 @@ export class NestedPools {
}

export function mapPoolToNestedPoolStateV3(pool: PoolGetPool): NestedPoolState {
const pools: NestedPool[] = [
const pools: NestedPoolV3[] = [
{
id: pool.id,
address: pool.address,
type: mapPoolType(pool.type),
level: 1,
tokens: pool.poolTokens.map((t) => {
const minimalToken: MinimalToken = {
const minimalToken: PoolTokenWithUnderlying = {
address: t.address,
decimals: t.decimals,
index: t.index,
underlyingToken:
t.underlyingToken === null
? null
: { ...t.underlyingToken, index: t.index },
};
return minimalToken;
}),
Expand All @@ -145,7 +145,18 @@ export function mapPoolToNestedPoolStateV3(pool: PoolGetPool): NestedPoolState {
address: token.nestedPool.address,
level: 0,
type: mapPoolType(token.nestedPool.type),
tokens: token.nestedPool.tokens.map(getMainToken),
tokens: token.nestedPool.tokens.map((t) => {
const minimalToken: PoolTokenWithUnderlying = {
address: t.address,
decimals: t.decimals,
index: t.index,
underlyingToken:
t.underlyingToken === null
? null
: { ...t.underlyingToken, index: t.index },
};
return minimalToken;
}),
});
});

Expand Down Expand Up @@ -173,7 +184,7 @@ function getMainToken(token: Token): {
index: number;
} {
// If token has an underlying token, use that
if (token.isErc4626 && token.underlyingToken) {
if (token.underlyingToken) {
return {
index: token.index,
address: token.underlyingToken.address,
Expand All @@ -196,17 +207,21 @@ function getMainToken(token: Token): {
}

export function mapPoolToNestedPoolStateV2(pool: PoolGetPool): NestedPoolState {
const pools: NestedPool[] = [
const pools: NestedPoolV2[] = [
{
id: pool.id,
address: pool.address,
type: mapPoolType(pool.type),
level: 1,
tokens: pool.poolTokens.map((t) => {
const minimalToken: MinimalToken = {
const minimalToken: PoolTokenWithUnderlying = {
address: t.address,
decimals: t.decimals,
index: t.index,
underlyingToken:
t.underlyingToken === null
? null
: { ...t.underlyingToken, index: t.index },
};
return minimalToken;
}),
Expand All @@ -229,10 +244,14 @@ export function mapPoolToNestedPoolStateV2(pool: PoolGetPool): NestedPoolState {
level: 0,
type: mapPoolType(token.nestedPool.type),
tokens: token.nestedPool.tokens.map((t) => {
const minimalToken: MinimalToken = {
const minimalToken: PoolTokenWithUnderlying = {
address: t.address,
decimals: t.decimals,
index: t.index,
underlyingToken:
t.underlyingToken === null
? null
: { ...t.underlyingToken, index: t.index },
};
return minimalToken;
}),
Expand All @@ -249,7 +268,7 @@ export function mapPoolToNestedPoolStateV2(pool: PoolGetPool): NestedPoolState {
});

return {
protocolVersion: pool.protocolVersion,
protocolVersion: 2,
pools,
mainTokens,
};
Expand Down
4 changes: 0 additions & 4 deletions src/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,3 @@ export interface MinimalToken {
export interface PoolTokenWithBalance extends MinimalToken {
balance: HumanAmount;
}

export interface PoolTokenWithUnderlying extends MinimalToken {
underlyingToken: MinimalToken | null;
}
149 changes: 149 additions & 0 deletions src/entities/priceImpact/addLiquidityNested.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { InputAmount } from '@/types';
import { AddLiquidity } from '../addLiquidity';
import { AddLiquidityNestedInput } from '../addLiquidityNested/types';
import { PriceImpactAmount } from '../priceImpactAmount';
import {
NestedPoolState,
NestedPoolV2,
NestedPoolV3,
PoolState,
} from '../types';
import { isPoolToken } from '../utils/isPoolToken';
import {
AddLiquidityKind,
AddLiquidityUnbalancedInput,
} from '../addLiquidity/types';
import { PriceImpact } from '.';
import { ChainId } from '@/utils';
import { TokenAmount } from '../tokenAmount';
import { AddLiquidityBoostedUnbalancedInput } from '../addLiquidityBoosted/types';
import { AddLiquidityBoostedV3 } from '../addLiquidityBoosted';

type AddResult = {
priceImpactAmount: PriceImpactAmount;
bptOut: TokenAmount;
};

export async function addLiquidityNested(
input: AddLiquidityNestedInput,
nestedPoolState: NestedPoolState,
): Promise<PriceImpactAmount> {
const addLiquidity = new AddLiquidity();
const addLiquidityBoosted = new AddLiquidityBoostedV3();
// sort pools from child to parent
const sortedPools = nestedPoolState.pools.sort((a, b) => a.level - b.level);
// Price impact amounts from all add actions
const priceImpactAmounts: PriceImpactAmount[] = [];
// BPT out from each child pool add action
const childrenBptOuts: InputAmount[] = [];

// For each pool (including parent), find PI from the corresponding add operation
for (const pool of sortedPools) {
let amountsIn: InputAmount[] = [];
let isBoostedPool = false;
if (pool.level === 0) {
// A lower level pool
// Find any user input amount related to the current pool & check if pool is boosted
amountsIn = input.amountsIn.filter((a) => {
const poolToken = isPoolToken(pool.tokens, a.address);
if (poolToken.isPoolToken && poolToken.isUnderlyingToken)
isBoostedPool = true;
return poolToken.isPoolToken;
});
// skip pool if no relevant amountsIn
if (amountsIn.length === 0) continue;
} else {
// The parent pool
// Find any user input amount or bpt from child adds related to the current pool & check if pool is boosted
amountsIn = [...childrenBptOuts, ...input.amountsIn].filter((a) => {
const poolToken = isPoolToken(pool.tokens, a.address);
if (poolToken.isPoolToken && poolToken.isUnderlyingToken)
isBoostedPool = true;
return poolToken.isPoolToken;
});
}
let addResult: AddResult;
if (isBoostedPool)
addResult = await getAddBoostedUnbalancedResult(
addLiquidityBoosted,
input.chainId,
input.rpcUrl,
pool as NestedPoolV3,
amountsIn,
);
else
addResult = await getAddUnbalancedResult(
addLiquidity,
input.chainId,
input.rpcUrl,
pool as NestedPoolV2,
amountsIn,
nestedPoolState.protocolVersion,
);

priceImpactAmounts.push(addResult.priceImpactAmount);
childrenBptOuts.push(addResult.bptOut.toInputAmount());
}

const priceImpactSum = priceImpactAmounts.reduce(
(acc, cur) => acc + cur.amount,
0n,
);

return PriceImpactAmount.fromRawAmount(priceImpactSum);
}

async function getAddUnbalancedResult(
addLiquidity: AddLiquidity,
chainId: ChainId,
rpcUrl: string,
pool: NestedPoolV2,
amountsIn: InputAmount[],
protocolVersion: 1 | 2 | 3,
): Promise<AddResult> {
const addLiquidityInput: AddLiquidityUnbalancedInput = {
chainId,
rpcUrl,
amountsIn,
kind: AddLiquidityKind.Unbalanced,
};
const poolState: PoolState = {
...pool,
protocolVersion,
};
const priceImpactAmount = await PriceImpact.addLiquidityUnbalanced(
addLiquidityInput,
poolState,
);

const { bptOut } = await addLiquidity.query(addLiquidityInput, poolState);

return { priceImpactAmount, bptOut };
}

async function getAddBoostedUnbalancedResult(
addLiquidityBoosted: AddLiquidityBoostedV3,
chainId: ChainId,
rpcUrl: string,
pool: NestedPoolV3,
amountsIn: InputAmount[],
): Promise<AddResult> {
const addLiquidityInput: AddLiquidityBoostedUnbalancedInput = {
chainId,
rpcUrl,
amountsIn,
kind: AddLiquidityKind.Unbalanced,
};

const priceImpactAmount = await PriceImpact.addLiquidityUnbalancedBoosted(
addLiquidityInput,
{ ...pool, protocolVersion: 3 },
);

const { bptOut } = await addLiquidityBoosted.query(addLiquidityInput, {
...pool,
protocolVersion: 3,
});

return { priceImpactAmount, bptOut };
}
11 changes: 6 additions & 5 deletions src/entities/priceImpact/addLiquidityUnbalancedBoosted.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { abs, ChainId, max, min } from '../../utils';
import { abs, ChainId, isSameAddress, max, min } from '../../utils';
import { InputToken, SwapKind } from '../../types';
import { PriceImpactAmount } from '../priceImpactAmount';
import { RemoveLiquidityKind } from '../removeLiquidity/types';
import { Swap, SwapInput, TokenApi } from '../swap';
import { TokenAmount } from '../tokenAmount';
import { PoolStateWithUnderlyings } from '../types';
import { PoolStateWithUnderlyings, PoolTokenWithUnderlying } from '../types';
import { priceImpactABA } from '.';
import { AddLiquidityBoostedUnbalancedInput } from '../addLiquidityBoosted/types';
import { AddLiquidityBoostedV3 } from '../addLiquidityBoosted';
import { RemoveLiquidityBoostedV3 } from '../removeLiquidityBoosted';
import { RemoveLiquidityBoostedProportionalInput } from '../removeLiquidityBoosted/types';
import { Address, BaseError, ContractFunctionRevertedError } from 'viem';
import { Token } from '../token';
import { PoolTokenWithUnderlying } from '@/data';

/**
* Calculate price impact on add liquidity unbalanced operations
Expand Down Expand Up @@ -247,7 +246,9 @@ function getTokenWrapInfo(
tokens: PoolTokenWithUnderlying[],
token: InputToken,
): TokenWrapInfo {
const poolToken = tokens.find((t) => t.address === token.address);
const poolToken = tokens.find((t) =>
isSameAddress(t.address, token.address),
);
if (poolToken)
return {
token: poolToken,
Expand All @@ -257,7 +258,7 @@ function getTokenWrapInfo(

const wrapped = tokens
.filter((t) => t.underlyingToken !== null)
.find((t) => t.underlyingToken!.address === token.address);
.find((t) => isSameAddress(t.underlyingToken!.address, token.address));

if (!wrapped) throw Error(`Cannot map token to wrapped: ${token.address}`);
return {
Expand Down
Loading

0 comments on commit 0454cae

Please sign in to comment.