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

New composite router for boosted pool operations #572

Merged
merged 23 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0fa2dcd
hook up new composite router for boosted operations
MattPereira Jan 25, 2025
2ae3ba2
fix: type complaint
MattPereira Jan 25, 2025
e8e5abc
patch: fix type error by extending AddLiquidity class
MattPereira Jan 25, 2025
27ae6de
move block num for all sepolia tests
MattPereira Jan 27, 2025
5c9543b
update AddLiquidityTxInput types
MattPereira Jan 27, 2025
796a571
refactor tests to use helpers
MattPereira Jan 27, 2025
330b175
Merge branch 'main' of https://github.com/balancer/b-sdk into new-com…
MattPereira Jan 27, 2025
fcd259e
Fix add liquidity boosted tests
brunoguerios Jan 27, 2025
5112024
add tests for proportional query and unbalanced add with only one wrap
MattPereira Jan 27, 2025
0944a3e
fix boosted add liquidity input validator
MattPereira Jan 28, 2025
9dc084d
fix types for PriceImpact.addLiquidityNested
MattPereira Jan 28, 2025
12d5255
add tests for remove with only one unwrap
MattPereira Jan 28, 2025
187fac2
add changeset
MattPereira Jan 28, 2025
cdbb902
update changeset to major
MattPereira Jan 28, 2025
f3fbdc1
fix tokens returned by unbalanced query and add test
MattPereira Jan 28, 2025
6c6a261
infer wrapUnderlying instead of requiring it as user input
MattPereira Jan 28, 2025
ab1df90
fix partial boosted test
MattPereira Jan 29, 2025
347aded
infer unwrapWrapped for remove liquidity boosted
MattPereira Jan 29, 2025
512547c
fix unbalanced add liquidity boosted with fewer than total num of poo…
MattPereira Jan 29, 2025
10cbf6c
refactor for readability
MattPereira Jan 29, 2025
c75286d
add comments
MattPereira Jan 29, 2025
ab82723
Refactor types as Address
brunoguerios Jan 29, 2025
15626ee
Add extra input validation
brunoguerios Jan 29, 2025
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
111 changes: 59 additions & 52 deletions src/entities/addLiquidityBoosted/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,21 @@
// available:
// 1. Unbalanced - addLiquidityUnbalancedToERC4626Pool
// 2. Proportional - addLiquidityProportionalToERC4626Pool
import { encodeFunctionData, zeroAddress } from 'viem';
import { encodeFunctionData, zeroAddress, Address } from 'viem';
import { TokenAmount } from '@/entities/tokenAmount';

import { Permit2 } from '@/entities/permit2Helper';

import { getAmountsCall } from '../addLiquidity/helpers';

import { PoolStateWithUnderlyings } from '@/entities/types';

import {
getAmounts,
getBptAmountFromReferenceAmountBoosted,
getSortedTokens,
getValue,
} from '@/entities/utils';

import {
AddLiquidityBuildCallOutput,
AddLiquidityKind,
} from '../addLiquidity/types';

import { doAddLiquidityUnbalancedQuery } from './doAddLiquidityUnbalancedQuery';
import { doAddLiquidityProportionalQuery } from './doAddLiquidityPropotionalQuery';
import { Token } from '../token';
Expand All @@ -31,14 +25,17 @@ import {
balancerCompositeLiquidityRouterBoostedAbi,
balancerRouterAbi,
} from '@/abi';

import { Hex } from '@/types';
import {
AddLiquidityBoostedBuildCallInput,
AddLiquidityBoostedInput,
AddLiquidityBoostedQueryOutput,
} from './types';
import { InputValidator } from '../inputValidator/inputValidator';
import {
buildPoolStateTokenMap,
MinimalTokenWithIsUnderlyingFlag,
} from '@/entities/utils';

export class AddLiquidityBoostedV3 {
private readonly inputValidator: InputValidator = new InputValidator();
Expand All @@ -54,41 +51,52 @@ export class AddLiquidityBoostedV3 {
});

const bptToken = new Token(input.chainId, poolState.address, 18);
const wrapUnderlying: boolean[] = new Array(
poolState.tokens.length,
).fill(false);

let bptOut: TokenAmount;
let amountsIn: TokenAmount[];

const poolStateTokenMap = buildPoolStateTokenMap(poolState);

switch (input.kind) {
case AddLiquidityKind.Unbalanced: {
// Use poolState to add token index to amountsIn
const tokensIn = input.amountsIn.map((amountIn) => {
const tokenIn = poolState.tokens.find((t) => {
// Use amountsIn provided by use to infer if token should be wrapped
const tokensIn: MinimalTokenWithIsUnderlyingFlag[] =
input.amountsIn.map((amountIn) => {
const amountInAddress = amountIn.address.toLowerCase();
return (
t.address.toLowerCase() === amountInAddress ||
(t.underlyingToken &&
t.underlyingToken.address.toLowerCase() ===
amountInAddress)
);
const token = poolStateTokenMap[amountInAddress];
if (!token) {
throw new Error(
`Token not found in poolState: ${amountInAddress}`,
);
}
return token;
});

if (!tokenIn) {
throw new Error(
`Token not found in poolState: ${amountIn.address}`,
);
}

const matchedToken =
tokenIn.underlyingToken?.address.toLowerCase() ===
amountIn.address.toLowerCase()
? tokenIn.underlyingToken
: tokenIn;
// if user provides fewer than the number of pool tokens,
// fill remaining indexes because composite router requires
// length of pool tokens to match maxAmountsIn and wrapUnderlying
if (tokensIn.length < poolState.tokens.length) {
const existingIndices = new Set(
tokensIn.map((t) => t.index),
);
poolState.tokens.forEach((poolToken) => {
if (!existingIndices.has(poolToken.index)) {
tokensIn.push({
index: poolToken.index,
decimals: poolToken.decimals,
address: poolToken.address,
isUnderlyingToken: false,
});
}
});
}

return {
address: matchedToken.address,
decimals: matchedToken.decimals,
index: matchedToken.index,
};
// wrap if token is underlying
tokensIn.forEach((t) => {
wrapUnderlying[t.index] = t.isUnderlyingToken;
});

// It is allowed not not provide the same amount of TokenAmounts as inputs
Expand All @@ -103,7 +111,7 @@ export class AddLiquidityBoostedV3 {
input.sender ?? zeroAddress,
input.userData ?? '0x',
poolState.address,
input.wrapUnderlying,
wrapUnderlying,
maxAmountsIn,
MattPereira marked this conversation as resolved.
Show resolved Hide resolved
block,
);
Expand All @@ -112,12 +120,24 @@ export class AddLiquidityBoostedV3 {
amountsIn = sortedTokens.map((t, i) =>
TokenAmount.fromRawAmount(t, maxAmountsIn[i]),
);

break;
}
case AddLiquidityKind.Proportional: {
// User provides addresses via input.tokensIn so we can infer if they need to be wrapped
input.tokensIn.forEach((t) => {
const tokenIn =
poolStateTokenMap[t.toLowerCase() as Address];
if (!tokenIn) {
throw new Error(`Invalid token address: ${t}`);
}
wrapUnderlying[tokenIn.index] = tokenIn.isUnderlyingToken;
});

const bptAmount = await getBptAmountFromReferenceAmountBoosted(
input,
poolState,
wrapUnderlying,
);

const [tokensIn, exactAmountsInNumbers] =
Expand All @@ -128,26 +148,13 @@ export class AddLiquidityBoostedV3 {
input.userData ?? '0x',
poolState.address,
bptAmount.rawAmount,
input.wrapUnderlying,
wrapUnderlying,
block,
);

amountsIn = tokensIn.map((tokenInAddress, i) => {
const decimals = poolState.tokens.find((t) => {
const tokenAddress = tokenInAddress.toLowerCase();
return (
t.address.toLowerCase() === tokenAddress ||
(t.underlyingToken &&
t.underlyingToken.address.toLowerCase() ===
tokenAddress)
);
})?.decimals;

if (!decimals)
throw new Error(
`Token decimals missing for ${tokenInAddress}`,
);

amountsIn = tokensIn.map((t, i) => {
const tokenInAddress = t.toLowerCase() as `0x${string}`;
brunoguerios marked this conversation as resolved.
Show resolved Hide resolved
const { decimals } = poolStateTokenMap[tokenInAddress];
const token = new Token(
input.chainId,
tokenInAddress,
Expand All @@ -172,7 +179,7 @@ export class AddLiquidityBoostedV3 {
poolId: poolState.id,
poolType: poolState.type,
addLiquidityKind: input.kind,
wrapUnderlying: input.wrapUnderlying,
wrapUnderlying,
bptOut,
amountsIn,
chainId: input.chainId,
Expand Down
3 changes: 1 addition & 2 deletions src/entities/addLiquidityBoosted/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { Slippage } from '../slippage';
export type AddLiquidityBoostedProportionalInput = {
chainId: number;
rpcUrl: string;
tokensIn: Address[];
referenceAmount: InputAmount;
kind: AddLiquidityKind.Proportional;
wrapUnderlying: boolean[];
sender?: Address;
userData?: Hex;
};
Expand All @@ -19,7 +19,6 @@ export type AddLiquidityBoostedUnbalancedInput = {
rpcUrl: string;
amountsIn: InputAmount[];
kind: AddLiquidityKind.Unbalanced;
wrapUnderlying: boolean[];
sender?: Address;
userData?: Hex;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Address, Hex } from 'viem';
import { AddLiquidityNestedBaseInput } from '../types';

export type AddLiquidityNestedInputV3 = AddLiquidityNestedBaseInput & {
wrapUnderlying?: boolean[];
sender?: Address;
userData?: Hex;
};
Expand Down
5 changes: 1 addition & 4 deletions src/entities/priceImpact/addLiquidityNested.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,13 @@ export async function addLiquidityNested(
});
}
let addResult: AddResult;
if (isBoostedPool && 'wrapUnderlying' in input && input.wrapUnderlying)
if (isBoostedPool)
addResult = await getAddBoostedUnbalancedResult(
addLiquidityBoosted,
input.chainId,
input.rpcUrl,
pool as NestedPoolV3,
amountsIn,
input.wrapUnderlying,
);
else
addResult = await getAddUnbalancedResult(
Expand Down Expand Up @@ -129,14 +128,12 @@ async function getAddBoostedUnbalancedResult(
rpcUrl: string,
pool: NestedPoolV3,
amountsIn: InputAmount[],
wrapUnderlying: boolean[],
): Promise<AddResult> {
const addLiquidityInput: AddLiquidityBoostedUnbalancedInput = {
chainId,
rpcUrl,
amountsIn,
kind: AddLiquidityKind.Unbalanced,
wrapUnderlying,
};

const priceImpactAmount = await addLiquidityUnbalancedBoosted(
Expand Down
2 changes: 1 addition & 1 deletion src/entities/priceImpact/addLiquidityUnbalancedBoosted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export async function addLiquidityUnbalancedBoosted(
chainId: input.chainId,
rpcUrl: input.rpcUrl,
bptIn: bptOut.toInputAmount(),
unwrapWrapped: input.wrapUnderlying,
tokensOut: poolTokens.map((t) => t.address),
brunoguerios marked this conversation as resolved.
Show resolved Hide resolved
kind: RemoveLiquidityKind.Proportional,
};
const { amountsOut } = await removeLiquidity.query(
Expand Down
36 changes: 20 additions & 16 deletions src/entities/removeLiquidityBoosted/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { encodeFunctionData, zeroAddress } from 'viem';
import { Address, encodeFunctionData, zeroAddress } from 'viem';

import {
RemoveLiquidityBase,
Expand All @@ -25,6 +25,7 @@ import {
RemoveLiquidityBoostedQueryOutput,
} from './types';
import { InputValidator } from '../inputValidator/inputValidator';
import { buildPoolStateTokenMap } from '@/entities/utils';

export class RemoveLiquidityBoostedV3 implements RemoveLiquidityBase {
private readonly inputValidator: InputValidator = new InputValidator();
Expand All @@ -39,6 +40,20 @@ export class RemoveLiquidityBoostedV3 implements RemoveLiquidityBase {
type: 'Boosted',
});

const poolStateTokenMap = buildPoolStateTokenMap(poolState);

// Infer if token should be unwrapped using the tokensOut provided by user
const unwrapWrapped = input.tokensOut
.map((t) => {
const tokenOut = poolStateTokenMap[t.toLowerCase() as Address];
if (!tokenOut) {
throw new Error(`Invalid token address: ${t}`);
}
return tokenOut;
})
.sort((a, b) => a.index - b.index) // sort by index to match the order of the pool tokens
.map((t) => t.isUnderlyingToken);

const [tokensOut, underlyingAmountsOut] =
await doRemoveLiquidityProportionalQuery(
input.rpcUrl,
Expand All @@ -47,25 +62,14 @@ export class RemoveLiquidityBoostedV3 implements RemoveLiquidityBase {
input.sender ?? zeroAddress,
input.userData ?? '0x',
poolState.address,
input.unwrapWrapped,
unwrapWrapped,
block,
);

// tokens out can be underlying or yield-bearing variant
const amountsOut = underlyingAmountsOut.map((amount, i) => {
const tokenOut = tokensOut[i];

const decimals = poolState.tokens.find((t) => {
return (
t.address.toLowerCase() === tokenOut.toLowerCase() ||
(t.underlyingToken &&
t.underlyingToken.address.toLowerCase() ===
tokenOut.toLowerCase())
);
})?.decimals;

if (!decimals)
throw new Error(`Token decimals missing for ${tokenOut}`);
const { decimals } =
poolStateTokenMap[tokenOut.toLowerCase() as Address];

const token = new Token(input.chainId, tokenOut, decimals);
return TokenAmount.fromRawAmount(token, amount);
Expand All @@ -77,7 +81,7 @@ export class RemoveLiquidityBoostedV3 implements RemoveLiquidityBase {
to: BALANCER_COMPOSITE_LIQUIDITY_ROUTER_BOOSTED[input.chainId],
poolType: poolState.type,
poolId: poolState.address,
unwrapWrapped: input.unwrapWrapped,
unwrapWrapped,
removeLiquidityKind: RemoveLiquidityKind.Proportional,
bptIn: TokenAmount.fromRawAmount(bptToken, input.bptIn.rawAmount),
amountsOut,
Expand Down
2 changes: 1 addition & 1 deletion src/entities/removeLiquidityBoosted/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export type RemoveLiquidityBoostedProportionalInput = {
chainId: number;
rpcUrl: string;
bptIn: InputAmount;
unwrapWrapped: boolean[];
tokensOut: Address[];
kind: RemoveLiquidityKind.Proportional;
sender?: Address;
userData?: Hex;
Expand Down
Loading