Skip to content

Commit

Permalink
Merge pull request #530 from balancer/init-buffer
Browse files Browse the repository at this point in the history
Add support for InitBuffer
  • Loading branch information
brunoguerios authored Dec 13, 2024
2 parents bead884 + 6017e8c commit 6c396b9
Show file tree
Hide file tree
Showing 16 changed files with 490 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/hungry-roses-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@balancer/sdk": minor
---

Add support for InitBuffer
9 changes: 0 additions & 9 deletions src/entities/addLiquidityBuffer/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
// A user can add liquidity to a boosted pool in various forms. The following ways are
// available:
// 1. Unbalanced - addLiquidityUnbalancedToERC4626Pool
// 2. Proportional - addLiquidityProportionalToERC4626Pool

import { encodeFunctionData } from 'viem';
import { TokenAmount } from '@/entities/tokenAmount';

Expand All @@ -15,8 +10,6 @@ import { Token } from '../token';
import { BALANCER_BUFFER_ROUTER } from '@/utils';
import { balancerBufferRouterAbi, balancerRouterAbi } from '@/abi';

import { InputValidator } from '../inputValidator/inputValidator';

import {
AddLiquidityBufferBuildCallInput,
AddLiquidityBufferBuildCallOutput,
Expand All @@ -25,8 +18,6 @@ import {
} from './types';

export class AddLiquidityBufferV3 {
private readonly inputValidator: InputValidator = new InputValidator();

async query(
input: AddLiquidityBufferInput,
bufferState: BufferState,
Expand Down
2 changes: 2 additions & 0 deletions src/entities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export * from './addLiquidityNested/addLiquidityNestedV2/types';
export * from './addLiquidityNested/addLiquidityNestedV3/types';
export * from './createPool';
export * from './encoders';
export * from './initBuffer';
export * from './initBuffer/types';
export * from './initPool';
export * from './permitHelper';
export * from './permit2Helper';
Expand Down
35 changes: 35 additions & 0 deletions src/entities/initBuffer/doInitBufferQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createPublicClient, http } from 'viem';
import { BALANCER_BUFFER_ROUTER, ChainId, CHAINS } from '@/utils';
import { Address } from '@/types';
import {
balancerBufferRouterAbi,
permit2Abi,
vaultExtensionAbi_V3,
vaultV3Abi,
} from '@/abi';

export const doInitBufferQuery = async (
rpcUrl: string,
chainId: ChainId,
wrappedToken: Address,
exactAmountUnderlyingIn: bigint,
exactAmountWrappedIn: bigint,
): Promise<{ issuedShares: bigint }> => {
const client = createPublicClient({
transport: http(rpcUrl),
chain: CHAINS[chainId],
});

const { result: issuedShares } = await client.simulateContract({
address: BALANCER_BUFFER_ROUTER[chainId],
abi: [
...balancerBufferRouterAbi,
...vaultV3Abi,
...vaultExtensionAbi_V3,
...permit2Abi,
],
functionName: 'queryInitializeBuffer',
args: [wrappedToken, exactAmountUnderlyingIn, exactAmountWrappedIn],
});
return { issuedShares };
};
94 changes: 94 additions & 0 deletions src/entities/initBuffer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { encodeFunctionData } from 'viem';
import { TokenAmount } from '@/entities/tokenAmount';

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

import { doInitBufferQuery } from './doInitBufferQuery';
import { BALANCER_BUFFER_ROUTER } from '@/utils';
import { balancerBufferRouterAbi, balancerRouterAbi } from '@/abi';

import {
InitBufferBuildCallInput,
InitBufferBuildCallOutput,
InitBufferInput,
InitBufferQueryOutput,
} from './types';

export class InitBufferV3 {
async query(input: InitBufferInput): Promise<InitBufferQueryOutput> {
const { issuedShares } = await doInitBufferQuery(
input.rpcUrl,
input.chainId,
input.wrappedAmountIn.address,
input.underlyingAmountIn.rawAmount,
input.wrappedAmountIn.rawAmount,
);
const underlyingAmountIn = TokenAmount.fromInputAmount(
input.underlyingAmountIn,
input.chainId,
);
const wrappedAmountIn = TokenAmount.fromInputAmount(
input.wrappedAmountIn,
input.chainId,
);

const output: InitBufferQueryOutput = {
issuedShares,
underlyingAmountIn,
wrappedAmountIn,
chainId: input.chainId,
protocolVersion: 3,
to: BALANCER_BUFFER_ROUTER[input.chainId],
};

return output;
}

buildCall(input: InitBufferBuildCallInput): InitBufferBuildCallOutput {
const minIssuedShares = input.slippage.applyTo(input.issuedShares, -1);

const callData = encodeFunctionData({
abi: balancerBufferRouterAbi,
functionName: 'initializeBuffer',
args: [
input.wrappedAmountIn.token.address,
input.underlyingAmountIn.amount,
input.wrappedAmountIn.amount,
minIssuedShares,
] as const,
});
return {
callData,
to: BALANCER_BUFFER_ROUTER[input.chainId],
value: 0n, // Default to 0 as native not supported
minIssuedShares,
};
}

public buildCallWithPermit2(
input: InitBufferBuildCallInput,
permit2: Permit2,
): InitBufferBuildCallOutput {
// generate same calldata as buildCall
const buildCallOutput = this.buildCall(input);

const args = [
[],
[],
permit2.batch,
permit2.signature,
[buildCallOutput.callData],
] as const;

const callData = encodeFunctionData({
abi: balancerRouterAbi,
functionName: 'permitBatchAndCall',
args,
});

return {
...buildCallOutput,
callData,
};
}
}
31 changes: 31 additions & 0 deletions src/entities/initBuffer/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Address, Hex } from 'viem';
import { Slippage } from '../slippage';
import { TokenAmount } from '../tokenAmount';
import { InputAmount } from '@/types';

export type InitBufferInput = {
chainId: number;
rpcUrl: string;
wrappedAmountIn: InputAmount;
underlyingAmountIn: InputAmount;
};

export type InitBufferQueryOutput = {
issuedShares: bigint;
wrappedAmountIn: TokenAmount;
underlyingAmountIn: TokenAmount;
chainId: number;
protocolVersion: 3;
to: Address;
};

export type InitBufferBuildCallInput = {
slippage: Slippage;
} & InitBufferQueryOutput;

export type InitBufferBuildCallOutput = {
callData: Hex;
to: Address;
value: bigint;
minIssuedShares: bigint;
};
41 changes: 41 additions & 0 deletions src/entities/permit2Helper/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { TokenAmount } from '../tokenAmount';
import { permit2Abi } from '@/abi';
import { getAmountsCall } from '../addLiquidity/helpers';
import { AddLiquidityBufferBuildCallInput } from '../addLiquidityBuffer/types';
import { InitBufferBuildCallInput } from '../initBuffer/types';

export * from './allowanceTransfer';
export * from './constants';
Expand Down Expand Up @@ -195,6 +196,46 @@ export class Permit2Helper {
return signPermit2(input.client, input.owner, spender, details);
}

static async signInitBufferApproval(
input: InitBufferBuildCallInput & {
client: PublicWalletClient;
owner: Address;
nonces?: number[];
expirations?: number[];
},
): Promise<Permit2> {
if (input.nonces && input.nonces.length !== 2) {
throw new Error("Nonces length doesn't match amountsIn length");
}
if (input.expirations && input.expirations.length !== 2) {
throw new Error(
"Expirations length doesn't match amountsIn length",
);
}
const spender = BALANCER_BUFFER_ROUTER[input.chainId];
const details: PermitDetails[] = [
await getDetails(
input.client,
input.wrappedAmountIn.token.address,
input.owner,
spender,
input.wrappedAmountIn.amount,
input.expirations ? input.expirations[0] : undefined,
input.nonces ? input.nonces[0] : undefined,
),
await getDetails(
input.client,
input.underlyingAmountIn.token.address,
input.owner,
spender,
input.underlyingAmountIn.amount,
input.expirations ? input.expirations[1] : undefined,
input.nonces ? input.nonces[1] : undefined,
),
];
return signPermit2(input.client, input.owner, spender, details);
}

static async signSwapApproval(
input: SwapBuildCallInputBase & {
client: PublicWalletClient;
Expand Down
8 changes: 8 additions & 0 deletions src/entities/tokenAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ export class TokenAmount {
return new TokenAmount(token, rawAmount);
}

public static fromInputAmount(
input: InputAmount,
chainId: number,
): TokenAmount {
const token = new Token(chainId, input.address, input.decimals);
return new TokenAmount(token, input.rawAmount);
}

protected constructor(token: Token, amount: BigintIsh) {
this.decimalScale = DECIMAL_SCALES[token.decimals];
this.token = token;
Expand Down
2 changes: 1 addition & 1 deletion test/anvil/anvil-global-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const ANVIL_NETWORKS: Record<NetworksWithFork, NetworkSetup> = {
rpcEnv: 'ETHEREUM_RPC_URL',
fallBackRpc: 'https://mainnet.gateway.tenderly.co',
port: ANVIL_PORTS.MAINNET,
forkBlockNumber: 18980070n,
forkBlockNumber: 21373640n,
},
POLYGON: {
rpcEnv: 'POLYGON_RPC_URL',
Expand Down
4 changes: 4 additions & 0 deletions test/lib/utils/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export const TOKENS: Record<number, Record<string, TestToken>> = {
address: '0xae78736cd615f374d3085123a210448e74fc6393',
decimals: 18,
},
gearboxUSDC: {
address: '0xda00000035fef4082F78dEF6A8903bee419FbF8E',
decimals: 6,
},
},
[ChainId.OPTIMISM]: {
FRAX: {
Expand Down
13 changes: 10 additions & 3 deletions test/v2/addLiquidity/stable.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ import {
} from 'test/lib/utils';
import { ANVIL_NETWORKS, startFork } from 'test/anvil/anvil-global-setup';

const { rpcUrl } = await startFork(ANVIL_NETWORKS.MAINNET);
const chainId = ChainId.MAINNET;
const poolId = POOLS[chainId].wstETH_wETH_MSP.id; // B-stETH-STABLE

describe('add liquidity stable test', () => {
let txInput: AddLiquidityTxInput;
let poolState: PoolState;
let rpcUrl: string;

beforeAll(async () => {
// setup mock api
Expand All @@ -56,6 +56,13 @@ describe('add liquidity stable test', () => {
// get pool state from api
poolState = await api.getPool(poolId);

// TODO: figure out why these tests fail when udpating blockNumber to 21373640n
({ rpcUrl } = await startFork(
ANVIL_NETWORKS.MAINNET,
undefined,
18980070n,
));

const client = createTestClient({
mode: 'anvil',
chain: CHAINS[chainId],
Expand Down Expand Up @@ -137,7 +144,7 @@ describe('add liquidity stable test', () => {
addLiquidityOutput,
txInput.slippage,
chainId,
poolState.protocolVersion,
poolState.protocolVersion as 2 | 3,
wethIsEth,
);
});
Expand Down Expand Up @@ -190,7 +197,7 @@ describe('add liquidity stable test', () => {
addLiquidityOutput,
txInput.slippage,
chainId,
poolState.protocolVersion,
poolState.protocolVersion as 2 | 3,
wethIsEth,
);
});
Expand Down
9 changes: 7 additions & 2 deletions test/v2/createPool/composableStable.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@ import { ANVIL_NETWORKS, startFork } from '../../anvil/anvil-global-setup';
import { doCreatePool } from '../../lib/utils/createPoolHelper';
import { CreatePoolTxInput } from '../../lib/utils/types';

const { rpcUrl } = await startFork(ANVIL_NETWORKS.MAINNET);

describe('Create Composable Stable Pool tests', () => {
const chainId = ChainId.MAINNET;
let txInput: CreatePoolTxInput;
let poolAddress: Address;
let createPoolComposableStableInput: CreatePoolV2ComposableStableInput;
let rpcUrl: string;

beforeAll(async () => {
({ rpcUrl } = await startFork(
ANVIL_NETWORKS.MAINNET,
undefined,
18980070n,
));
const client = createTestClient({
mode: 'anvil',
chain: CHAINS[chainId],
Expand Down
6 changes: 5 additions & 1 deletion test/v2/initPool/composableStable.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import { doCreatePool } from '../../lib/utils/createPoolHelper';
import { forkSetup } from '../../lib/utils/helper';
import { assertInitPool, doInitPool } from '../../lib/utils/initPoolHelper';

const { rpcUrl } = await startFork(ANVIL_NETWORKS.MAINNET);
const { rpcUrl } = await startFork(
ANVIL_NETWORKS.MAINNET,
undefined,
18980070n,
);
const chainId = ChainId.MAINNET;

describe('Composable Stable Pool - Init Pool tests', async () => {
Expand Down
4 changes: 2 additions & 2 deletions test/v2/removeLiquidity/composableStable.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe('composable stable remove liquidity test', () => {
.filter((_, index) => index !== bptIndex);

amountsOut = poolTokensWithoutBpt.map((t) => ({
rawAmount: parseUnits('20', t.decimals),
rawAmount: parseUnits('2', t.decimals),
decimals: t.decimals,
address: t.address,
}));
Expand Down Expand Up @@ -152,7 +152,7 @@ describe('composable stable remove liquidity test', () => {
let amountOut: InputAmount;
beforeAll(() => {
amountOut = {
rawAmount: parseUnits('20', wETH.decimals),
rawAmount: parseUnits('2', wETH.decimals),
decimals: wETH.decimals,
address: wETH.address,
};
Expand Down
Loading

0 comments on commit 6c396b9

Please sign in to comment.