Skip to content

Commit

Permalink
Merge pull request #514 from balancer/full-native-support
Browse files Browse the repository at this point in the history
Add native token support to AddLiquidityNested and AddLiquidityBoosted
  • Loading branch information
brunoguerios authored Dec 11, 2024
2 parents ddff485 + 2abccdd commit 147e1ae
Show file tree
Hide file tree
Showing 16 changed files with 901 additions and 362 deletions.
5 changes: 5 additions & 0 deletions .changeset/eleven-masks-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@balancer/sdk": minor
---

Add native token support to AddLiquidityNested and AddLiquidityBoosted
9 changes: 7 additions & 2 deletions src/entities/addLiquidityBoosted/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getAmounts,
getBptAmountFromReferenceAmountBoosted,
getSortedTokens,
getValue,
} from '@/entities/utils';

import {
Expand Down Expand Up @@ -143,11 +144,12 @@ export class AddLiquidityBoostedV3 {
input: AddLiquidityBoostedBuildCallInput,
): AddLiquidityBuildCallOutput {
const amounts = getAmountsCall(input);
const wethIsEth = input.wethIsEth ?? false;
const args = [
input.poolId,
amounts.maxAmountsIn,
amounts.minimumBpt,
false,
wethIsEth,
input.userData,
] as const;
let callData: Hex;
Expand All @@ -172,10 +174,13 @@ export class AddLiquidityBoostedV3 {
throw new Error('SingleToken not supported');
}
}

const value = getValue(input.amountsIn, wethIsEth);

return {
callData,
to: BALANCER_COMPOSITE_LIQUIDITY_ROUTER[input.chainId],
value: 0n, // Default to 0 as native not supported
value,
minBptOut: TokenAmount.fromRawAmount(
input.bptOut.token,
amounts.minimumBpt,
Expand Down
1 change: 1 addition & 0 deletions src/entities/addLiquidityBoosted/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ export type AddLiquidityBoostedQueryOutput = {

export type AddLiquidityBoostedBuildCallInput = {
slippage: Slippage;
wethIsEth?: boolean;
} & AddLiquidityBoostedQueryOutput;
4 changes: 2 additions & 2 deletions src/entities/removeLiquidityBoosted/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class RemoveLiquidityBoostedV3 implements RemoveLiquidityBase {
input.poolId,
input.bptIn.amount,
amounts.minAmountsOut,
false,
input.wethIsEth ?? false,
input.userData,
],
});
Expand All @@ -120,7 +120,7 @@ export class RemoveLiquidityBoostedV3 implements RemoveLiquidityBase {
callData: callData,
to: BALANCER_COMPOSITE_LIQUIDITY_ROUTER[input.chainId],
value: 0n, // always has 0 value
maxBptIn: input.bptIn, //TokenAmount
maxBptIn: input.bptIn,
minAmountsOut: amounts.minAmountsOut.map((amount, i) => {
return TokenAmount.fromRawAmount(
input.amountsOut[i].token,
Expand Down
1 change: 1 addition & 0 deletions src/entities/removeLiquidityBoosted/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ export type RemoveLiquidityBoostedBuildCallInput = {
protocolVersion: 3;
chainId: number;
slippage: Slippage;
wethIsEth?: boolean;
};
175 changes: 174 additions & 1 deletion test/lib/utils/addLiquidityBoostedHelper.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import {
AddLiquidityBoostedInput,
AddLiquidityBoostedQueryOutput,
AddLiquidityBoostedUnbalancedInput,
AddLiquidityBoostedV3,
AddLiquidityBuildCallOutput,
AddLiquidityKind,
PoolStateWithUnderlyings,
Slippage,
Token,
TokenAmount,
} from '@/entities';
import {
BALANCER_COMPOSITE_LIQUIDITY_ROUTER,
ChainId,
NATIVE_ASSETS,
PERMIT2,
PublicWalletClient,
} from '@/utils';
import { Address, TestActions } from 'viem';
import { Address, TestActions, TransactionReceipt, zeroAddress } from 'viem';
import {
approveSpenderOnPermit2,
approveSpenderOnToken,
sendTransactionGetBalances,
setTokenBalances,
} from './helper';
import { areBigIntsWithinPercent } from './swapHelpers';

export async function GetBoostedBpt(
chainId: ChainId,
Expand Down Expand Up @@ -130,3 +136,170 @@ export const assertTokenMatch = (
expect(a.decimals).to.eq(tokenReturned[i].decimals);
});
};

export type AddLiquidityBoostedTxInput = {
client: PublicWalletClient & TestActions;
addLiquidityBoosted: AddLiquidityBoostedV3;
addLiquidityBoostedInput: AddLiquidityBoostedInput;
slippage: Slippage;
poolStateWithUnderlyings: PoolStateWithUnderlyings;
testAddress: Address;
wethIsEth?: boolean;
fromInternalBalance?: boolean;
usePermit2Signatures?: boolean;
};

export type AddLiquidityBoostedTxOutput = {
addLiquidityBoostedQueryOutput: AddLiquidityBoostedQueryOutput;
addLiquidityBuildCallOutput: AddLiquidityBuildCallOutput;
tokenAmountsForBalanceCheck: TokenAmount[];
txOutput: {
transactionReceipt: TransactionReceipt;
balanceDeltas: bigint[];
};
};

export const doAddLiquidityBoosted = async (
txInput: AddLiquidityBoostedTxInput,
): Promise<AddLiquidityBoostedTxOutput> => {
const {
addLiquidityBoosted,
poolStateWithUnderlyings,
addLiquidityBoostedInput,
testAddress,
client,
slippage,
wethIsEth,
} = txInput;

const addLiquidityBoostedQueryOutput = await addLiquidityBoosted.query(
addLiquidityBoostedInput,
poolStateWithUnderlyings,
);

const addLiquidityBoostedBuildInput = {
...addLiquidityBoostedQueryOutput,
slippage,
wethIsEth,
};

const addLiquidityBuildCallOutput = addLiquidityBoosted.buildCall(
addLiquidityBoostedBuildInput,
);

// send add liquidity transaction and check balance changes
const tokenAmountsForBalanceCheck = [
...addLiquidityBoostedQueryOutput.amountsIn,
addLiquidityBoostedQueryOutput.bptOut,
// add zero address so we can check for native token balance change
TokenAmount.fromRawAmount(
new Token(addLiquidityBoostedQueryOutput.chainId, zeroAddress, 18),
0n,
),
];

const txOutput = await sendTransactionGetBalances(
tokenAmountsForBalanceCheck.map((t) => t.token.address),
client,
testAddress,
addLiquidityBuildCallOutput.to,
addLiquidityBuildCallOutput.callData,
addLiquidityBuildCallOutput.value,
);

return {
addLiquidityBoostedQueryOutput,
addLiquidityBuildCallOutput,
tokenAmountsForBalanceCheck,
txOutput,
};
};

export const assertAddLiquidityBoostedUnbalanced = (
output: AddLiquidityBoostedTxOutput,
wethIsEth: boolean,
) => {
const {
addLiquidityBoostedQueryOutput,
addLiquidityBuildCallOutput,
tokenAmountsForBalanceCheck,
txOutput,
} = output;
const { chainId, amountsIn, bptOut, protocolVersion } =
addLiquidityBoostedQueryOutput;
const { to, value } = addLiquidityBuildCallOutput;
const { transactionReceipt, balanceDeltas } = txOutput;

expect(protocolVersion).toEqual(3);
expect(bptOut.amount > 0n).to.be.true;
expect(to).to.eq(BALANCER_COMPOSITE_LIQUIDITY_ROUTER[chainId]);
expect(transactionReceipt.status).to.eq('success');

// add one extra index for native token balance
const expectedDeltas = tokenAmountsForBalanceCheck.map((t) => t.amount);

// move native token balance to the extra index if wethIsEth
if (wethIsEth) {
const nativeAssetIndex = amountsIn.findIndex((a) =>
a.token.isSameAddress(NATIVE_ASSETS[chainId].wrapped),
);
expectedDeltas[nativeAssetIndex] = 0n;
expectedDeltas[expectedDeltas.length - 1] = value;
}

// check amountsIn against token balance changes
expect(expectedDeltas.slice(0, -2)).to.deep.eq(balanceDeltas.slice(0, -2));

// Here we check that output diff is within an acceptable tolerance.
// !!! This should only be used in the case of buffers as all other cases can be equal
areBigIntsWithinPercent(
balanceDeltas[balanceDeltas.length - 2],
expectedDeltas[expectedDeltas.length - 2],
0.001,
);

// check value against native token balance change
expect(value === expectedDeltas[expectedDeltas.length - 1]).to.be.true;
};

export const assertAddLiquidityBoostedProportional = (
output: AddLiquidityBoostedTxOutput,
wethIsEth: boolean,
) => {
const {
addLiquidityBoostedQueryOutput,
addLiquidityBuildCallOutput,
tokenAmountsForBalanceCheck,
txOutput,
} = output;
const { chainId, amountsIn, bptOut, protocolVersion } =
addLiquidityBoostedQueryOutput;
const { to, value } = addLiquidityBuildCallOutput;
const { transactionReceipt, balanceDeltas } = txOutput;

expect(protocolVersion).toEqual(3);
expect(bptOut.amount > 0n).to.be.true;
expect(to).to.eq(BALANCER_COMPOSITE_LIQUIDITY_ROUTER[chainId]);
expect(transactionReceipt.status).to.eq('success');

// add one extra index for native token balance
const expectedDeltas = tokenAmountsForBalanceCheck.map((t) => t.amount);

// move native token balance to the extra index if wethIsEth
if (wethIsEth) {
const nativeAssetIndex = amountsIn.findIndex((a) =>
a.token.isSameAddress(NATIVE_ASSETS[chainId].wrapped),
);
expectedDeltas[nativeAssetIndex] = 0n;
expectedDeltas[expectedDeltas.length - 1] = value;
}

for (let i = 0; i < expectedDeltas.length - 1; i++) {
// Here we check that output diff is within an acceptable tolerance.
// !!! This should only be used in the case of buffers as all other cases can be equal
areBigIntsWithinPercent(balanceDeltas[i], expectedDeltas[i], 0.001);
}

// check value against native token balance change
expect(value === expectedDeltas[expectedDeltas.length - 1]).to.be.true;
};
8 changes: 8 additions & 0 deletions test/lib/utils/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,14 @@ export const POOLS: Record<number, Record<string, TestPool>> = {
decimals: 18,
slot: 0,
},
// weth, stataUSDT
MOCK_BOOSTED_POOL_WETH: {
address: '0xf5a25990da505a9d4c3e0d876f3951e9edf9abc3',
id: '0xf5a25990da505a9d4c3e0d876f3951e9edf9abc3',
type: PoolType.Weighted,
decimals: 18,
slot: 0,
},
// boosted, weth
NESTED_WITH_BOOSTED_POOL: {
address: '0x693cc6a39bbf35464f53d6a5dbf7d6c2fa93741c',
Expand Down
Loading

0 comments on commit 147e1ae

Please sign in to comment.