From 521e6e4eb8987de383a7285ed68b3f004b582e3a Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 9 Jul 2024 10:53:23 +0100 Subject: [PATCH 1/6] feat: Add buffer support to swaps. --- src/abi/index.ts | 1 + src/data/providers/balancer-api/index.ts | 1 - src/entities/swap/paths/pathHelpers.ts | 25 + src/entities/swap/paths/pathWithAmount.ts | 5 + src/entities/swap/paths/types.ts | 1 + src/entities/swap/swaps/v3/index.ts | 5 +- test/entities/swaps/swap.test.ts | 67 ++ .../swaps/v3/swapV3.integration.test.ts | 769 +++++++++++------- test/entities/swaps/v3/swapV3.test.ts | 157 +++- test/lib/utils/addresses.ts | 27 + 10 files changed, 744 insertions(+), 314 deletions(-) diff --git a/src/abi/index.ts b/src/abi/index.ts index dbfb2c99..06711662 100644 --- a/src/abi/index.ts +++ b/src/abi/index.ts @@ -1,5 +1,6 @@ export * from './authorizer'; export * from './balancerQueries'; +export * from './balancerBatchRouter'; export * from './balancerRelayer'; export * from './balancerRouter'; export * from './batchRelayerLibrary'; diff --git a/src/data/providers/balancer-api/index.ts b/src/data/providers/balancer-api/index.ts index c51b0f24..e015d042 100644 --- a/src/data/providers/balancer-api/index.ts +++ b/src/data/providers/balancer-api/index.ts @@ -4,7 +4,6 @@ import { ChainId } from '../../../utils'; import { NestedPools } from './modules/nested-pool-state'; import { SorSwapPaths } from './modules/sorSwapPaths'; -export { SorInput as GetQuoteInput } from './modules/sorSwapPaths'; export { mapPoolToNestedPoolState } from './modules/nested-pool-state'; export class BalancerApi { diff --git a/src/entities/swap/paths/pathHelpers.ts b/src/entities/swap/paths/pathHelpers.ts index 7309c4ad..54a77101 100644 --- a/src/entities/swap/paths/pathHelpers.ts +++ b/src/entities/swap/paths/pathHelpers.ts @@ -34,6 +34,9 @@ export function validatePaths(paths: Path[]) { if (paths.length === 0) throw new Error('Invalid swap: must contain at least 1 path.'); + validateBufferVersion(paths); + validateBufferLength(paths); + const protocolVersion = paths[0].protocolVersion; if (!paths.every((p) => p.protocolVersion === protocolVersion)) throw new Error( @@ -56,3 +59,25 @@ export function validatePaths(paths: Path[]) { ); } } + +function validateBufferVersion(paths: Path[]) { + if ( + !paths.every((p) => { + return p.isBuffer ? p.protocolVersion === 3 : true; + }) + ) { + throw new Error('Unsupported swap: buffers not supported in V2.'); + } +} + +function validateBufferLength(paths: Path[]) { + if ( + !paths.every((p) => { + return p.isBuffer ? p.isBuffer.length === p.pools.length : true; + }) + ) { + throw new Error( + 'Unsupported swap: buffers and pools must have same length.', + ); + } +} diff --git a/src/entities/swap/paths/pathWithAmount.ts b/src/entities/swap/paths/pathWithAmount.ts index 25b5dd29..a1386f66 100644 --- a/src/entities/swap/paths/pathWithAmount.ts +++ b/src/entities/swap/paths/pathWithAmount.ts @@ -5,6 +5,7 @@ import { TokenApi } from './types'; export class PathWithAmount { public readonly pools: Address[]; + public readonly isBuffer: boolean[]; public readonly tokens: TokenApi[]; public readonly outputAmount: TokenAmount; public readonly inputAmount: TokenAmount; @@ -15,6 +16,7 @@ export class PathWithAmount { pools: Address[], inputAmountRaw: bigint, outputAmountRaw: bigint, + isBuffer: boolean[] | undefined, ) { if (pools.length === 0 || tokens.length < 2) { throw new Error( @@ -38,6 +40,9 @@ export class PathWithAmount { tokens[tokens.length - 1].decimals, ); this.pools = pools; + this.isBuffer = isBuffer + ? isBuffer + : new Array(this.pools.length).fill(false); this.tokens = tokens; this.inputAmount = TokenAmount.fromRawAmount(tokenIn, inputAmountRaw); this.outputAmount = TokenAmount.fromRawAmount( diff --git a/src/entities/swap/paths/types.ts b/src/entities/swap/paths/types.ts index 1633254c..9437ac09 100644 --- a/src/entities/swap/paths/types.ts +++ b/src/entities/swap/paths/types.ts @@ -5,6 +5,7 @@ export type TokenApi = Omit; export type Path = { pools: Address[] | Hex[]; + isBuffer?: boolean[]; tokens: TokenApi[]; outputAmountRaw: bigint; inputAmountRaw: bigint; diff --git a/src/entities/swap/swaps/v3/index.ts b/src/entities/swap/swaps/v3/index.ts index f04975cf..1acb0640 100644 --- a/src/entities/swap/swaps/v3/index.ts +++ b/src/entities/swap/swaps/v3/index.ts @@ -54,6 +54,7 @@ export class SwapV3 implements SwapBase { p.pools, p.inputAmountRaw, p.outputAmountRaw, + p.isBuffer, ), ); @@ -532,7 +533,7 @@ export class SwapV3 implements SwapBase { return { pool: pool, tokenOut: p.tokens[i + 1].address, - isBuffer: false, + isBuffer: p.isBuffer[i], }; }), }; @@ -548,7 +549,7 @@ export class SwapV3 implements SwapBase { return { pool: pool, tokenOut: p.tokens[i + 1].address, - isBuffer: false, + isBuffer: p.isBuffer[i], }; }), }; diff --git a/test/entities/swaps/swap.test.ts b/test/entities/swaps/swap.test.ts index dd4b29fb..594f9f60 100644 --- a/test/entities/swaps/swap.test.ts +++ b/test/entities/swaps/swap.test.ts @@ -110,6 +110,73 @@ describe('Swap', () => { 'Unsupported swap: all paths must start/end with same token.', ); }); + describe('buffers', () => { + const pathWithBuffers: Path = { + protocolVersion: 3, + tokens: [ + { + address: + '0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8', + decimals: 18, + }, // DAI + { + address: + '0x8a88124522dbbf1e56352ba3de1d9f78c143751e', + decimals: 18, + }, // stataToken + { + address: + '0xde46e43f46ff74a23a65ebb0580cbe3dfe684a17', + decimals: 6, + }, // stataToken + { + address: + '0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357', + decimals: 6, + }, // USDC + ], + pools: [ + '0x8a88124522dbbf1e56352ba3de1d9f78c143751e', // buffer + '0x90a46864cb1f042060554592038367e9c97e17f3', + '0xde46e43f46ff74a23a65ebb0580cbe3dfe684a17', // buffer + ], + isBuffer: [true, false, true], + inputAmountRaw: 1000000000000000000n, + outputAmountRaw: 1000000n, + }; + test('should throw if buffers used for V2', () => { + expect(() => { + new Swap({ + chainId, + paths: [ + { + ...pathWithBuffers, + protocolVersion: 2, + }, + ], + swapKind: SwapKind.GivenIn, + }); + }).toThrowError( + 'Unsupported swap: buffers not supported in V2.', + ); + }); + test('should throw if buffers not same length as pools', () => { + expect(() => { + new Swap({ + chainId, + paths: [ + { + ...pathWithBuffers, + isBuffer: [true, false], + }, + ], + swapKind: SwapKind.GivenIn, + }); + }).toThrowError( + 'Unsupported swap: buffers and pools must have same length.', + ); + }); + }); }); }); diff --git a/test/entities/swaps/v3/swapV3.integration.test.ts b/test/entities/swaps/v3/swapV3.integration.test.ts index 78c64e4f..e518b5fd 100644 --- a/test/entities/swaps/v3/swapV3.integration.test.ts +++ b/test/entities/swaps/v3/swapV3.integration.test.ts @@ -12,6 +12,7 @@ import { PublicActions, TestActions, WalletActions, + Hex, } from 'viem'; import { CHAINS, @@ -41,20 +42,27 @@ import { const protocolVersion = 3; const chainId = ChainId.SEPOLIA; // blockNo shouldn't change as checks depend on token balances -const blockNo = 6188394n; +const blockNo = 6238034n; const BAL = TOKENS[chainId].BAL; const WETH = TOKENS[chainId].WETH; const USDC = TOKENS[chainId].USDC; const DAI = TOKENS[chainId].DAI; +const aaveUSDC = TOKENS[chainId].aaveUSDC; +const aaveDAI = TOKENS[chainId].aaveDAI; const USDC_DAI_BPT = POOLS[chainId].MOCK_USDC_DAI_POOL; +const stataUSDC = TOKENS[chainId].stataUSDC; +const stataDAI = TOKENS[chainId].stataDAI; + +type Override = { Parameters: Hex[]; ReturnType: Hex }; describe('SwapV3', () => { let client: Client & PublicActions & TestActions & WalletActions; let testAddress: Address; let rpcUrl: string; + let snapshot: Hex; - beforeEach(async () => { + beforeAll(async () => { // resetting the fork between each test avoids changing block state await stopAnvilFork(ANVIL_NETWORKS.SEPOLIA, undefined, blockNo); const fork = await startFork( @@ -75,110 +83,111 @@ describe('SwapV3', () => { await forkSetup( client, testAddress, - [WETH.address, BAL.address, USDC.address], - // [WETH.slot as number, BAL.slot as number, undefined], - [WETH.slot as number, BAL.slot as number, USDC.slot as number], - [parseEther('100'), parseEther('100'), 100000000n], + [WETH.address, BAL.address, USDC.address, aaveUSDC.address], + [ + WETH.slot as number, + BAL.slot as number, + USDC.slot as number, + aaveUSDC.slot as number, + ], + [parseEther('100'), parseEther('100'), 100000000n, 100000000n], undefined, protocolVersion, ); - }); - - describe('single swap', () => { - const pathBalWeth: Path = { - protocolVersion: 3, - tokens: [ - { - address: BAL.address, - decimals: BAL.decimals, - }, - { - address: WETH.address, - decimals: WETH.decimals, - }, - ], - pools: [POOLS[chainId].MOCK_WETH_BAL_POOL.id], - inputAmountRaw: 100000000000n, - outputAmountRaw: 100000000000n, - }; - describe('query method should return correct updated', () => { - test('GivenIn', async () => { - const swap = new Swap({ - chainId, - paths: [pathBalWeth], - swapKind: SwapKind.GivenIn, - }); - - const expected = (await swap.query( - rpcUrl, - )) as ExactInQueryOutput; - - const wethToken = new Token( - chainId, - WETH.address, - WETH.decimals, - ); - expect(expected.expectedAmountOut.token).to.deep.eq(wethToken); - expect(expected.expectedAmountOut.amount).to.eq(123749996n); - }); - test('GivenOut', async () => { - const swap = new Swap({ - chainId, - paths: [pathBalWeth], - swapKind: SwapKind.GivenOut, - }); - const expected = (await swap.query( - rpcUrl, - )) as ExactOutQueryOutput; + // Uses Special RPC methods to revert state back to same snapshot for each test + // https://github.com/trufflesuite/ganache-cli-archive/blob/master/README.md + snapshot = await client.request({ + method: 'evm_snapshot', + params: [], + }); + }); - const balToken = new Token(chainId, BAL.address, BAL.decimals); - expect(expected.expectedAmountIn.token).to.deep.eq(balToken); - expect(expected.expectedAmountIn.amount).to.eq(80801616032325n); - }); + beforeEach(async () => { + await client.request({ + method: 'evm_revert', + params: [snapshot], }); - describe('swap should be executed correctly', () => { - describe('wethIsEth: false', () => { - const swapParams = { - chainId, - paths: [pathBalWeth], - wethIsEth: false, - }; + snapshot = await client.request({ + method: 'evm_snapshot', + params: [], + }); + }); + + describe('non-boosted', () => { + describe('single swap', () => { + const pathBalWeth: Path = { + protocolVersion: 3, + tokens: [ + { + address: BAL.address, + decimals: BAL.decimals, + }, + { + address: WETH.address, + decimals: WETH.decimals, + }, + ], + pools: [POOLS[chainId].MOCK_WETH_BAL_POOL.id], + inputAmountRaw: 100000000000n, + outputAmountRaw: 100000000000n, + }; + describe('query method should return correct updated', () => { test('GivenIn', async () => { const swap = new Swap({ - ...swapParams, + chainId, + paths: [pathBalWeth], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_ROUTER[chainId], - client, + + const expected = (await swap.query( rpcUrl, + )) as ExactInQueryOutput; + + const wethToken = new Token( chainId, - swap, - false, + WETH.address, + WETH.decimals, + ); + expect(expected.expectedAmountOut.token).to.deep.eq( + wethToken, ); + expect(expected.expectedAmountOut.amount).to.eq(123749996n); }); test('GivenOut', async () => { const swap = new Swap({ - ...swapParams, + chainId, + paths: [pathBalWeth], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_ROUTER[chainId], - client, + + const expected = (await swap.query( rpcUrl, + )) as ExactOutQueryOutput; + + const balToken = new Token( chainId, - swap, - false, + BAL.address, + BAL.decimals, + ); + expect(expected.expectedAmountIn.token).to.deep.eq( + balToken, + ); + expect(expected.expectedAmountIn.amount).to.eq( + 80801616032325n, ); }); }); - describe('wethIsEth: true', () => { - describe('eth out', async () => { + describe('swap should be executed correctly', () => { + describe('wethIsEth: false', () => { + const swapParams = { + chainId, + paths: [pathBalWeth], + wethIsEth: false, + }; test('GivenIn', async () => { const swap = new Swap({ - chainId, - paths: [pathBalWeth], + ...swapParams, swapKind: SwapKind.GivenIn, }); await assertSwapExactIn( @@ -187,13 +196,12 @@ describe('SwapV3', () => { rpcUrl, chainId, swap, - true, + false, ); }); test('GivenOut', async () => { const swap = new Swap({ - chainId, - paths: [pathBalWeth], + ...swapParams, swapKind: SwapKind.GivenOut, }); await assertSwapExactOut( @@ -202,168 +210,422 @@ describe('SwapV3', () => { rpcUrl, chainId, swap, - true, + false, ); }); }); - describe('eth in', () => { + describe('wethIsEth: true', () => { + describe('eth out', async () => { + test('GivenIn', async () => { + const swap = new Swap({ + chainId, + paths: [pathBalWeth], + swapKind: SwapKind.GivenIn, + }); + await assertSwapExactIn( + BALANCER_ROUTER[chainId], + client, + rpcUrl, + chainId, + swap, + true, + ); + }); + test('GivenOut', async () => { + const swap = new Swap({ + chainId, + paths: [pathBalWeth], + swapKind: SwapKind.GivenOut, + }); + await assertSwapExactOut( + BALANCER_ROUTER[chainId], + client, + rpcUrl, + chainId, + swap, + true, + ); + }); + }); + describe('eth in', () => { + test('GivenIn', async () => { + const pathWethBal = { + ...pathBalWeth, + tokens: [...pathBalWeth.tokens].reverse(), + }; + const swap = new Swap({ + chainId, + paths: [pathWethBal], + swapKind: SwapKind.GivenIn, + }); + await assertSwapExactIn( + BALANCER_ROUTER[chainId], + client, + rpcUrl, + chainId, + swap, + true, + ); + }); + test('GivenOut', async () => { + const pathWethBal = { + ...pathBalWeth, + tokens: [...pathBalWeth.tokens].reverse(), + }; + const swap = new Swap({ + chainId, + paths: [pathWethBal], + swapKind: SwapKind.GivenOut, + }); + await assertSwapExactOut( + BALANCER_ROUTER[chainId], + client, + rpcUrl, + chainId, + swap, + true, + ); + }); + }); + }); + }); + }); + + describe('multi-hop swap', () => { + // weth > bal > dai > usdc + const pathMultiSwap: Path = { + protocolVersion: 3, + tokens: [ + { + address: WETH.address, + decimals: WETH.decimals, + }, + { + address: BAL.address, + decimals: BAL.decimals, + }, + { + address: DAI.address, + decimals: DAI.decimals, + }, + { + address: USDC.address, + decimals: USDC.decimals, + }, + ], + pools: [ + POOLS[chainId].MOCK_WETH_BAL_POOL.id, + POOLS[chainId].MOCK_BAL_DAI_POOL.id, + POOLS[chainId].MOCK_USDC_DAI_POOL.id, + ], + inputAmountRaw: 100000000000000n, + outputAmountRaw: 2000000n, + }; + // weth > bpt > usdc + const pathWithExit: Path = { + protocolVersion: 3, + tokens: [ + { + address: WETH.address, + decimals: WETH.decimals, + }, + { + address: USDC_DAI_BPT.address, + decimals: USDC_DAI_BPT.decimals, + }, + { + address: USDC.address, + decimals: USDC.decimals, + }, + ], + pools: [ + POOLS[chainId].MOCK_NESTED_POOL.id, + POOLS[chainId].MOCK_USDC_DAI_POOL.id, + ], + inputAmountRaw: 100000000000000n, + outputAmountRaw: 6000000n, + }; + + describe('query method should return correct updated', () => { + test('GivenIn', async () => { + const swap = new Swap({ + chainId, + paths: [pathMultiSwap, pathWithExit], + swapKind: SwapKind.GivenIn, + }); + + const expected = (await swap.query( + rpcUrl, + )) as ExactInQueryOutput; + + const usdcToken = new Token( + chainId, + USDC.address, + USDC.decimals, + ); + expect(expected.swapKind).to.eq(SwapKind.GivenIn); + expect(expected.pathAmounts).to.deep.eq([151734n, 779073n]); + expect(expected.expectedAmountOut.token).to.deep.eq( + usdcToken, + ); + expect(expected.expectedAmountOut.amount).to.eq(930807n); + }); + test('GivenOut', async () => { + const swap = new Swap({ + chainId, + paths: [pathMultiSwap, pathWithExit], + swapKind: SwapKind.GivenOut, + }); + + const expected = (await swap.query( + rpcUrl, + )) as ExactOutQueryOutput; + + const wethToken = new Token( + chainId, + WETH.address, + WETH.decimals, + ); + expect(expected.swapKind).to.eq(SwapKind.GivenOut); + expect(expected.pathAmounts).to.deep.eq([ + 1827358022063780n, + 841746490376824n, + ]); + expect(expected.expectedAmountIn.token).to.deep.eq( + wethToken, + ); + expect(expected.expectedAmountIn.amount).to.eq( + 2669104512440604n, + ); + }); + }); + describe('swap should be executed correctly', () => { + describe('wethIsEth: false', () => { + const swapParams = { + chainId, + paths: [pathMultiSwap, pathWithExit], + wethIsEth: false, + }; test('GivenIn', async () => { - const pathWethBal = { - ...pathBalWeth, - tokens: [...pathBalWeth.tokens].reverse(), - }; const swap = new Swap({ - chainId, - paths: [pathWethBal], + ...swapParams, swapKind: SwapKind.GivenIn, }); await assertSwapExactIn( - BALANCER_ROUTER[chainId], + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, + false, ); }); test('GivenOut', async () => { - const pathWethBal = { - ...pathBalWeth, - tokens: [...pathBalWeth.tokens].reverse(), - }; const swap = new Swap({ - chainId, - paths: [pathWethBal], + ...swapParams, swapKind: SwapKind.GivenOut, }); await assertSwapExactOut( - BALANCER_ROUTER[chainId], + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, + false, ); }); }); + describe('wethIsEth: true', () => { + describe('eth in', async () => { + test('GivenIn', async () => { + const swap = new Swap({ + chainId, + paths: [pathMultiSwap, pathWithExit], + swapKind: SwapKind.GivenIn, + }); + await assertSwapExactIn( + BALANCER_BATCH_ROUTER[chainId], + client, + rpcUrl, + chainId, + swap, + true, + ); + }); + test('GivenOut', async () => { + const swap = new Swap({ + chainId, + paths: [pathMultiSwap, pathWithExit], + swapKind: SwapKind.GivenOut, + }); + await assertSwapExactOut( + BALANCER_BATCH_ROUTER[chainId], + client, + rpcUrl, + chainId, + swap, + true, + ); + }); + }); + describe('eth out', () => { + // usdc > dai > bal > weth + const pathUsdcWethMulti = { + ...pathMultiSwap, + tokens: [...pathMultiSwap.tokens].reverse(), + pools: [...pathMultiSwap.pools].reverse(), + inputAmountRaw: 100000n, + outputAmountRaw: 40000000000000n, + }; + // usdc > bpt > weth + const pathUsdcWethJoin = { + ...pathWithExit, + tokens: [...pathWithExit.tokens].reverse(), + pools: [...pathWithExit.pools].reverse(), + inputAmountRaw: 6000000n, + outputAmountRaw: 600000000000000n, + }; + test('GivenIn', async () => { + const swap = new Swap({ + chainId, + paths: [pathUsdcWethMulti, pathUsdcWethJoin], + swapKind: SwapKind.GivenIn, + }); + await assertSwapExactIn( + BALANCER_BATCH_ROUTER[chainId], + client, + rpcUrl, + chainId, + swap, + true, + ); + }); + test('GivenOut', async () => { + const swap = new Swap({ + chainId, + paths: [pathUsdcWethMulti, pathUsdcWethJoin], + swapKind: SwapKind.GivenOut, + }); + await assertSwapExactOut( + BALANCER_BATCH_ROUTER[chainId], + client, + rpcUrl, + chainId, + swap, + true, + ); + }); + }); + }); }); }); }); + describe('boosted', () => { + describe('multi-hop swap', () => { + // USDC[wrap]aUSDC[swap]aDAI[unwrap]DAI + const pathWithBuffers = { + protocolVersion: 3, + tokens: [ + { + address: aaveUSDC.address, + decimals: aaveUSDC.decimals, + }, + { + address: stataUSDC.address, + decimals: stataUSDC.decimals, + }, + { + address: stataDAI.address, + decimals: stataDAI.decimals, + }, + { + address: aaveDAI.address, + decimals: aaveDAI.decimals, + }, + ], + pools: [ + stataUSDC.address, + POOLS[chainId].MOCK_BOOSTED_POOL.id, + stataDAI.address, + ], + isBuffer: [true, false, true], + }; - describe('multi-hop swap', () => { - // weth > bal > dai > usdc - const pathMultiSwap: Path = { - protocolVersion: 3, - tokens: [ - { - address: WETH.address, - decimals: WETH.decimals, - }, - { - address: BAL.address, - decimals: BAL.decimals, - }, - { - address: DAI.address, - decimals: DAI.decimals, - }, - { - address: USDC.address, - decimals: USDC.decimals, - }, - ], - pools: [ - POOLS[chainId].MOCK_WETH_BAL_POOL.id, - POOLS[chainId].MOCK_BAL_DAI_POOL.id, - POOLS[chainId].MOCK_USDC_DAI_POOL.id, - ], - inputAmountRaw: 100000000000000n, - outputAmountRaw: 2000000n, - }; - // weth > bpt > usdc - const pathWithExit: Path = { - protocolVersion: 3, - tokens: [ - { - address: WETH.address, - decimals: WETH.decimals, - }, - { - address: USDC_DAI_BPT.address, - decimals: USDC_DAI_BPT.decimals, - }, - { - address: USDC.address, - decimals: USDC.decimals, - }, - ], - pools: [ - POOLS[chainId].MOCK_NESTED_POOL.id, - POOLS[chainId].MOCK_USDC_DAI_POOL.id, - ], - inputAmountRaw: 100000000000000n, - outputAmountRaw: 6000000n, - }; - - describe('query method should return correct updated', () => { - test('GivenIn', async () => { - const swap = new Swap({ - chainId, - paths: [pathMultiSwap, pathWithExit], - swapKind: SwapKind.GivenIn, - }); + describe('query method should return correct updated', () => { + test('GivenIn', async () => { + const swap = new Swap({ + chainId, + paths: [ + { + ...pathWithBuffers, + inputAmountRaw: 1000000n, + outputAmountRaw: 0n, + } as Path, + ], + swapKind: SwapKind.GivenIn, + }); - const expected = (await swap.query( - rpcUrl, - )) as ExactInQueryOutput; + const expected = (await swap.query( + rpcUrl, + )) as ExactInQueryOutput; - const usdcToken = new Token( - chainId, - USDC.address, - USDC.decimals, - ); - expect(expected.swapKind).to.eq(SwapKind.GivenIn); - expect(expected.pathAmounts).to.deep.eq([151734n, 779073n]); - expect(expected.expectedAmountOut.token).to.deep.eq(usdcToken); - expect(expected.expectedAmountOut.amount).to.eq(930807n); - }); - test('GivenOut', async () => { - const swap = new Swap({ - chainId, - paths: [pathMultiSwap, pathWithExit], - swapKind: SwapKind.GivenOut, + const daiToken = new Token( + chainId, + aaveDAI.address, + aaveDAI.decimals, + ); + expect(expected.swapKind).to.eq(SwapKind.GivenIn); + expect(expected.pathAmounts).to.deep.eq([ + 858147188476657666n, + ]); + expect(expected.expectedAmountOut.token).to.deep.eq( + daiToken, + ); + expect(expected.expectedAmountOut.amount).to.eq( + 858147188476657666n, + ); }); + test('GivenOut', async () => { + const swap = new Swap({ + chainId, + paths: [ + { + ...pathWithBuffers, + inputAmountRaw: 0n, + outputAmountRaw: 1000000000000000000n, + } as Path, + ], + swapKind: SwapKind.GivenOut, + }); - const expected = (await swap.query( - rpcUrl, - )) as ExactOutQueryOutput; + const expected = (await swap.query( + rpcUrl, + )) as ExactOutQueryOutput; - const wethToken = new Token( - chainId, - WETH.address, - WETH.decimals, - ); - expect(expected.swapKind).to.eq(SwapKind.GivenOut); - expect(expected.pathAmounts).to.deep.eq([ - 1827358022063780n, - 841746490376824n, - ]); - expect(expected.expectedAmountIn.token).to.deep.eq(wethToken); - expect(expected.expectedAmountIn.amount).to.eq( - 2669104512440604n, - ); + const usdcToken = new Token( + chainId, + aaveUSDC.address, + aaveUSDC.decimals, + ); + expect(expected.swapKind).to.eq(SwapKind.GivenOut); + expect(expected.pathAmounts).to.deep.eq([1165185n]); + expect(expected.expectedAmountIn.token).to.deep.eq( + usdcToken, + ); + expect(expected.expectedAmountIn.amount).to.eq(1165185n); + }); }); - }); - describe('swap should be executed correctly', () => { - describe('wethIsEth: false', () => { - const swapParams = { - chainId, - paths: [pathMultiSwap, pathWithExit], - wethIsEth: false, - }; + describe('swap should be executed correctly', () => { test('GivenIn', async () => { const swap = new Swap({ - ...swapParams, + chainId, + paths: [ + { + ...pathWithBuffers, + inputAmountRaw: 1000000n, + outputAmountRaw: 0n, + } as Path, + ], swapKind: SwapKind.GivenIn, }); await assertSwapExactIn( @@ -377,7 +639,14 @@ describe('SwapV3', () => { }); test('GivenOut', async () => { const swap = new Swap({ - ...swapParams, + chainId, + paths: [ + { + ...pathWithBuffers, + inputAmountRaw: 0n, + outputAmountRaw: 10000000000000000000n, + } as Path, + ], swapKind: SwapKind.GivenOut, }); await assertSwapExactOut( @@ -390,88 +659,6 @@ describe('SwapV3', () => { ); }); }); - describe('wethIsEth: true', () => { - describe('eth in', async () => { - test('GivenIn', async () => { - const swap = new Swap({ - chainId, - paths: [pathMultiSwap, pathWithExit], - swapKind: SwapKind.GivenIn, - }); - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], - client, - rpcUrl, - chainId, - swap, - true, - ); - }); - test('GivenOut', async () => { - const swap = new Swap({ - chainId, - paths: [pathMultiSwap, pathWithExit], - swapKind: SwapKind.GivenOut, - }); - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], - client, - rpcUrl, - chainId, - swap, - true, - ); - }); - }); - describe('eth out', () => { - // usdc > dai > bal > weth - const pathUsdcWethMulti = { - ...pathMultiSwap, - tokens: [...pathMultiSwap.tokens].reverse(), - pools: [...pathMultiSwap.pools].reverse(), - inputAmountRaw: 100000n, - outputAmountRaw: 40000000000000n, - }; - // usdc > bpt > weth - const pathUsdcWethJoin = { - ...pathWithExit, - tokens: [...pathWithExit.tokens].reverse(), - pools: [...pathWithExit.pools].reverse(), - inputAmountRaw: 6000000n, - outputAmountRaw: 600000000000000n, - }; - test('GivenIn', async () => { - const swap = new Swap({ - chainId, - paths: [pathUsdcWethMulti, pathUsdcWethJoin], - swapKind: SwapKind.GivenIn, - }); - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], - client, - rpcUrl, - chainId, - swap, - true, - ); - }); - test('GivenOut', async () => { - const swap = new Swap({ - chainId, - paths: [pathUsdcWethMulti, pathUsdcWethJoin], - swapKind: SwapKind.GivenOut, - }); - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], - client, - rpcUrl, - chainId, - swap, - true, - ); - }); - }); - }); }); }); }); diff --git a/test/entities/swaps/v3/swapV3.test.ts b/test/entities/swaps/v3/swapV3.test.ts index 69ede96e..023769b6 100644 --- a/test/entities/swaps/v3/swapV3.test.ts +++ b/test/entities/swaps/v3/swapV3.test.ts @@ -10,28 +10,28 @@ import { Path, TokenApi } from '@/entities/swap/paths/types'; import { TOKENS } from 'test/lib/utils/addresses'; +const tokens: TokenApi[] = [ + { + address: TOKENS[1].WETH.address, + decimals: TOKENS[1].WETH.decimals, + }, + { + address: TOKENS[1].DAI.address, + decimals: TOKENS[1].DAI.decimals, + }, + { + address: TOKENS[1].USDC.address, + decimals: TOKENS[1].USDC.decimals, + }, + { + address: TOKENS[1].USDT.address, + decimals: TOKENS[1].USDT.decimals, + }, +]; + describe('SwapV3', () => { - describe('getSwaps', () => { + describe('getSwaps - non-boosted', () => { describe('batchSwap', () => { - const tokens: TokenApi[] = [ - { - address: TOKENS[1].WETH.address, - decimals: TOKENS[1].WETH.decimals, - }, - { - address: TOKENS[1].DAI.address, - decimals: TOKENS[1].DAI.decimals, - }, - { - address: TOKENS[1].USDC.address, - decimals: TOKENS[1].USDC.decimals, - }, - { - address: TOKENS[1].USDT.address, - decimals: TOKENS[1].USDT.decimals, - }, - ]; - const path1hop: Path = { protocolVersion: 3, tokens: [tokens[0], tokens[3]], @@ -139,6 +139,123 @@ describe('SwapV3', () => { ], }; + expect(swap.swaps).to.deep.eq([ + expected1hop, + expected3hops, + ]); + }); + }); + }); + }); + describe('getSwaps - boosted', () => { + describe('batchSwap', () => { + const path1hop: Path = { + protocolVersion: 3, + tokens: [tokens[0], tokens[3]], + pools: [tokens[0].address], + isBuffer: [true], + inputAmountRaw: 2000000000000000000n, + outputAmountRaw: 2000000n, + }; + const path3hops: Path = { + protocolVersion: 3, + tokens, + pools: [ + tokens[0].address, + '0xcb444e90d8198415266c6a2724b7900fb12fc56e000000000000000000000011', + tokens[3].address, + ], + isBuffer: [true, false, true], + inputAmountRaw: 1000000000000000000n, + outputAmountRaw: 1000000n, + }; + + describe('GivenIn', () => { + test('swaps should be created correctly', () => { + const swap = new SwapV3({ + chainId: ChainId.MAINNET, + paths: [path1hop, path3hops], + swapKind: SwapKind.GivenIn, + }); + const expected1hop: SwapPathExactAmountIn = { + tokenIn: path1hop.tokens[0].address, + exactAmountIn: path1hop.inputAmountRaw, + steps: [ + { + pool: path1hop.pools[0], + tokenOut: path1hop.tokens[1].address, + isBuffer: true, + }, + ], + }; + const expected3hops: SwapPathExactAmountIn = { + tokenIn: path3hops.tokens[0].address, + exactAmountIn: path3hops.inputAmountRaw, + steps: [ + { + pool: path3hops.pools[0], + tokenOut: path3hops.tokens[1].address, + isBuffer: true, + }, + { + pool: path3hops.pools[1], + tokenOut: path3hops.tokens[2].address, + isBuffer: false, + }, + { + pool: path3hops.pools[2], + tokenOut: path3hops.tokens[3].address, + isBuffer: true, + }, + ], + }; + + expect(swap.swaps).to.deep.eq([ + expected1hop, + expected3hops, + ]); + }); + }); + describe('GivenOut', () => { + test('swaps should be created correctly', () => { + const swap = new SwapV3({ + chainId: ChainId.MAINNET, + paths: [path1hop, path3hops], + swapKind: SwapKind.GivenOut, + }); + const expected1hop: SwapPathExactAmountOut = { + tokenIn: path1hop.tokens[0].address, + exactAmountOut: path1hop.outputAmountRaw, + steps: [ + { + pool: path1hop.pools[0], + tokenOut: path1hop.tokens[1].address, + isBuffer: true, + }, + ], + }; + const expected3hops: SwapPathExactAmountOut = { + tokenIn: path3hops.tokens[0].address, + exactAmountOut: path3hops.outputAmountRaw, + steps: [ + { + pool: path3hops.pools[0], + tokenOut: path3hops.tokens[1].address, + isBuffer: true, + }, + { + pool: path3hops.pools[1], + tokenOut: path3hops.tokens[2].address, + isBuffer: false, + }, + { + pool: path3hops.pools[2], + tokenOut: path3hops.tokens[3].address, + isBuffer: true, + }, + ], + }; + expect(swap.swaps).to.deep.eq([ expected1hop, expected3hops, diff --git a/test/lib/utils/addresses.ts b/test/lib/utils/addresses.ts index 9cfc743c..7123993b 100644 --- a/test/lib/utils/addresses.ts +++ b/test/lib/utils/addresses.ts @@ -116,6 +116,26 @@ export const TOKENS: Record> = { decimals: 18, slot: 0, }, + aaveUSDC: { + address: '0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8', + decimals: 6, + slot: 0, + }, + aaveDAI: { + address: '0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357', + decimals: 18, + slot: 0, + }, + stataUSDC: { + address: '0x8a88124522dbbf1e56352ba3de1d9f78c143751e', + decimals: 6, + slot: 0, + }, + stataDAI: { + address: '0xde46e43f46ff74a23a65ebb0580cbe3dfe684a17', + decimals: 18, + slot: 0, + }, }, }; @@ -234,5 +254,12 @@ export const POOLS: Record> = { decimals: 18, slot: 0, }, + MOCK_BOOSTED_POOL: { + address: '0x90a46864cb1f042060554592038367e9c97e17f3', + id: '0x90a46864cb1f042060554592038367e9c97e17f3', + type: PoolType.Stable, + decimals: 18, + slot: 0, + }, }, }; From a7d31926707231f36eace26a516a813c3a2afcaf Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 11 Jul 2024 11:55:15 +0100 Subject: [PATCH 2/6] fix: Update V2 swap to have buffer param. --- src/entities/swap/swaps/v2/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/entities/swap/swaps/v2/index.ts b/src/entities/swap/swaps/v2/index.ts index 6b23f97d..96fadfdc 100644 --- a/src/entities/swap/swaps/v2/index.ts +++ b/src/entities/swap/swaps/v2/index.ts @@ -55,6 +55,7 @@ export class SwapV2 implements SwapBase { p.pools.map((pool) => pool.toLowerCase() as Address), p.inputAmountRaw, p.outputAmountRaw, + undefined, ), ); From 76df407107559033c583366a080efc61ac589271 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Fri, 12 Jul 2024 14:39:29 +0100 Subject: [PATCH 3/6] test: Add boosted pool tests. --- .../swaps/v3/swapV3.integration.test.ts | 146 +++++++++++++++++- test/lib/utils/addresses.ts | 4 +- 2 files changed, 147 insertions(+), 3 deletions(-) diff --git a/test/entities/swaps/v3/swapV3.integration.test.ts b/test/entities/swaps/v3/swapV3.integration.test.ts index 172fed5f..6ddb2e3c 100644 --- a/test/entities/swaps/v3/swapV3.integration.test.ts +++ b/test/entities/swaps/v3/swapV3.integration.test.ts @@ -45,6 +45,8 @@ const WETH = TOKENS[chainId].WETH; const USDC = TOKENS[chainId].USDC_AAVE; const DAI = TOKENS[chainId].DAI_AAVE; const USDC_DAI_BPT = POOLS[chainId].MOCK_USDC_DAI_POOL; +const stataUSDC = TOKENS[chainId].stataUSDC; +const stataDAI = TOKENS[chainId].stataDAI; type Override = { Parameters: Hex[]; ReturnType: Hex }; @@ -76,7 +78,7 @@ describe('SwapV3', () => { [WETH.address, BAL.address, USDC.address], // [WETH.slot as number, BAL.slot as number, undefined], [WETH.slot as number, BAL.slot as number, USDC.slot as number], - [parseEther('100'), parseEther('100'), 100000000n], + [parseEther('100'), parseEther('100'), 10000000000n], undefined, protocolVersion, ); @@ -655,4 +657,146 @@ describe('SwapV3', () => { }); }); }); + + describe('boosted', () => { + describe('multi-hop swap', () => { + // USDC[wrap]aUSDC[swap]aDAI[unwrap]DAI + const pathWithBuffers = { + protocolVersion: 3, + tokens: [ + { + address: USDC.address, + decimals: USDC.decimals, + }, + { + address: stataUSDC.address, + decimals: stataUSDC.decimals, + }, + { + address: stataDAI.address, + decimals: stataDAI.decimals, + }, + { + address: DAI.address, + decimals: DAI.decimals, + }, + ], + pools: [ + stataUSDC.address, + POOLS[chainId].MOCK_BOOSTED_POOL.id, + stataDAI.address, + ], + isBuffer: [true, false, true], + }; + + describe('query method should return correct updated', () => { + test('GivenIn', async () => { + const swap = new Swap({ + chainId, + paths: [ + { + ...pathWithBuffers, + inputAmountRaw: 100000000n, + outputAmountRaw: 0n, + } as Path, + ], + swapKind: SwapKind.GivenIn, + }); + + const expected = (await swap.query( + rpcUrl, + )) as ExactInQueryOutput; + + const daiToken = new Token( + chainId, + DAI.address, + DAI.decimals, + ); + expect(expected.swapKind).to.eq(SwapKind.GivenIn); + expect(expected.pathAmounts).to.deep.eq([ + 98985649125345246328n, + ]); + expect(expected.expectedAmountOut.token).to.deep.eq( + daiToken, + ); + expect(expected.expectedAmountOut.amount).to.eq( + 98985649125345246328n, + ); + }); + test('GivenOut', async () => { + const swap = new Swap({ + chainId, + paths: [ + { + ...pathWithBuffers, + inputAmountRaw: 0n, + outputAmountRaw: 100000000000000000000n, + } as Path, + ], + swapKind: SwapKind.GivenOut, + }); + + const expected = (await swap.query( + rpcUrl, + )) as ExactOutQueryOutput; + + const usdcToken = new Token( + chainId, + USDC.address, + USDC.decimals, + ); + expect(expected.swapKind).to.eq(SwapKind.GivenOut); + expect(expected.pathAmounts).to.deep.eq([101014646n]); + expect(expected.expectedAmountIn.token).to.deep.eq( + usdcToken, + ); + expect(expected.expectedAmountIn.amount).to.eq(101014646n); + }); + }); + describe('swap should be executed correctly', () => { + test('GivenIn', async () => { + const swap = new Swap({ + chainId, + paths: [ + { + ...pathWithBuffers, + inputAmountRaw: 100000000n, + outputAmountRaw: 0n, + } as Path, + ], + swapKind: SwapKind.GivenIn, + }); + await assertSwapExactIn( + BALANCER_BATCH_ROUTER[chainId], + client, + rpcUrl, + chainId, + swap, + false, + ); + }); + test('GivenOut', async () => { + const swap = new Swap({ + chainId, + paths: [ + { + ...pathWithBuffers, + inputAmountRaw: 0n, + outputAmountRaw: 100000000000000000000n, + } as Path, + ], + swapKind: SwapKind.GivenOut, + }); + await assertSwapExactOut( + BALANCER_BATCH_ROUTER[chainId], + client, + rpcUrl, + chainId, + swap, + false, + ); + }); + }); + }); + }); }); diff --git a/test/lib/utils/addresses.ts b/test/lib/utils/addresses.ts index 662d1c33..62f676cc 100644 --- a/test/lib/utils/addresses.ts +++ b/test/lib/utils/addresses.ts @@ -255,8 +255,8 @@ export const POOLS: Record> = { slot: 0, }, MOCK_BOOSTED_POOL: { - address: '0x90a46864cb1f042060554592038367e9c97e17f3', - id: '0x90a46864cb1f042060554592038367e9c97e17f3', + address: '0x302b75a27e5e157f93c679dd7a25fdfcdbc1473c', + id: '0x302b75a27e5e157f93c679dd7a25fdfcdbc1473c', type: PoolType.Stable, decimals: 18, slot: 0, From 7f1bd683838eb2c16a50cdbfc5cd353957111d8d Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 6 Aug 2024 11:17:58 +0100 Subject: [PATCH 4/6] chore: Update tests to work with non-exact result. --- .../swaps/v2/swapV2.integration.test.ts | 36 +-- .../swaps/v3/swapV3.integration.test.ts | 261 ++++++++++-------- test/lib/utils/swapHelpers.ts | 127 +++++++-- 3 files changed, 263 insertions(+), 161 deletions(-) diff --git a/test/entities/swaps/v2/swapV2.integration.test.ts b/test/entities/swaps/v2/swapV2.integration.test.ts index fe18d60b..d900943a 100644 --- a/test/entities/swaps/v2/swapV2.integration.test.ts +++ b/test/entities/swaps/v2/swapV2.integration.test.ts @@ -137,28 +137,28 @@ describe('SwapV2', () => { ...swapParams, swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - vault, + await assertSwapExactIn({ + contractToCall: vault, client, rpcUrl, chainId, swap, wethIsEth, - ); + }); }); test('GivenOut', async () => { const swap = new Swap({ ...swapParams, swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - vault, + await assertSwapExactOut({ + contractToCall: vault, client, rpcUrl, chainId, swap, wethIsEth, - ); + }); }); }); describe('wethIsEth: true', () => { @@ -170,14 +170,14 @@ describe('SwapV2', () => { paths: [pathBalWeth], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - vault, + await assertSwapExactIn({ + contractToCall: vault, client, rpcUrl, chainId, swap, wethIsEth, - ); + }); }); test('GivenOut', async () => { const swap = new Swap({ @@ -185,14 +185,14 @@ describe('SwapV2', () => { paths: [pathBalWeth], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - vault, + await assertSwapExactOut({ + contractToCall: vault, client, rpcUrl, chainId, swap, wethIsEth, - ); + }); }); }); describe('eth in', () => { @@ -206,14 +206,14 @@ describe('SwapV2', () => { paths: [pathWethBal], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - vault, + await assertSwapExactIn({ + contractToCall: vault, client, rpcUrl, chainId, swap, wethIsEth, - ); + }); }); test('GivenOut', async () => { const pathWethBal = { @@ -225,14 +225,14 @@ describe('SwapV2', () => { paths: [pathWethBal], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - vault, + await assertSwapExactOut({ + contractToCall: vault, client, rpcUrl, chainId, swap, wethIsEth, - ); + }); }); }); }); diff --git a/test/entities/swaps/v3/swapV3.integration.test.ts b/test/entities/swaps/v3/swapV3.integration.test.ts index 2d4bedc9..c8579412 100644 --- a/test/entities/swaps/v3/swapV3.integration.test.ts +++ b/test/entities/swaps/v3/swapV3.integration.test.ts @@ -181,7 +181,7 @@ describe('SwapV3', () => { testAddress, tokens, [WETH.slot, BAL.slot, USDC.slot] as number[], - [parseEther('100'), parseEther('100'), 100000000n], + [parseEther('100'), parseEther('100'), 100000000000n], ); await approveSpenderOnTokens( @@ -362,14 +362,14 @@ describe('SwapV3', () => { paths: [pathBalWeth], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: BALANCER_ROUTER[chainId], client, rpcUrl, chainId, swap, - false, - ); + wethIsEth: false, + }); }); test('GivenOut', async () => { const swap = new Swap({ @@ -377,14 +377,14 @@ describe('SwapV3', () => { paths: [pathBalWeth], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: BALANCER_ROUTER[chainId], client, rpcUrl, chainId, swap, - false, - ); + wethIsEth: false, + }); }); }); describe('wethIsEth: true', () => { @@ -395,14 +395,14 @@ describe('SwapV3', () => { paths: [pathBalWeth], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: BALANCER_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, - ); + wethIsEth: true, + }); }); test('GivenOut', async () => { const swap = new Swap({ @@ -410,14 +410,14 @@ describe('SwapV3', () => { paths: [pathBalWeth], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: BALANCER_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, - ); + wethIsEth: true, + }); }); }); describe('eth in', () => { @@ -431,14 +431,14 @@ describe('SwapV3', () => { paths: [pathWethBal], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: BALANCER_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, - ); + wethIsEth: true, + }); }); test('GivenOut', async () => { const pathWethBal = { @@ -450,14 +450,14 @@ describe('SwapV3', () => { paths: [pathWethBal], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: BALANCER_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, - ); + wethIsEth: true, + }); }); }); }); @@ -470,64 +470,66 @@ describe('SwapV3', () => { describe('wethIsEth: false', () => { const wethIsEth = false; test('GivenIn', async () => { - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, - new Swap({ + swap: new Swap({ chainId, paths: [pathMultiSwap], swapKind: SwapKind.GivenIn, }), wethIsEth, - ); + }); }); test('GivenOut', async () => { - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, - new Swap({ + swap: new Swap({ chainId, paths: [pathMultiSwap], swapKind: SwapKind.GivenOut, }), wethIsEth, - ); + }); }); }); describe('wethIsEth: true', () => { const wethIsEth = true; describe('eth in', async () => { test('GivenIn', async () => { - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, - new Swap({ + swap: new Swap({ chainId, paths: [pathMultiSwap], swapKind: SwapKind.GivenIn, }), wethIsEth, - ); + }); }); test('GivenOut', async () => { - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, - new Swap({ + swap: new Swap({ chainId, paths: [pathMultiSwap], swapKind: SwapKind.GivenOut, }), wethIsEth, - ); + }); }); }); describe('eth out', () => { @@ -537,14 +539,15 @@ describe('SwapV3', () => { paths: [pathUsdcWethMulti], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, wethIsEth, - ); + }); }); test('GivenOut', async () => { const swap = new Swap({ @@ -552,14 +555,15 @@ describe('SwapV3', () => { paths: [pathUsdcWethMulti], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, wethIsEth, - ); + }); }); }); }); @@ -572,14 +576,14 @@ describe('SwapV3', () => { paths: [pathMultiSwap, pathWithExit], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - false, - ); + wethIsEth: false, + }); }); test('GivenOut', async () => { const swap = new Swap({ @@ -587,14 +591,14 @@ describe('SwapV3', () => { paths: [pathMultiSwap, pathWithExit], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - false, - ); + wethIsEth: false, + }); }); }); describe('wethIsEth: true', () => { @@ -605,14 +609,15 @@ describe('SwapV3', () => { paths: [pathMultiSwap, pathWithExit], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, - ); + wethIsEth: true, + }); }); test('GivenOut', async () => { const swap = new Swap({ @@ -620,14 +625,15 @@ describe('SwapV3', () => { paths: [pathMultiSwap, pathWithExit], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, - ); + wethIsEth: true, + }); }); }); describe('eth out', () => { @@ -640,14 +646,15 @@ describe('SwapV3', () => { ], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, - ); + wethIsEth: true, + }); }); test('GivenOut', async () => { const swap = new Swap({ @@ -658,14 +665,15 @@ describe('SwapV3', () => { ], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, - ); + wethIsEth: true, + }); }); }); }); @@ -685,15 +693,15 @@ describe('SwapV3', () => { paths: [pathBalWeth], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: BALANCER_ROUTER[chainId], client, rpcUrl, chainId, swap, - false, + wethIsEth: false, usePermit2Signatures, - ); + }); }); test('GivenOut', async () => { const swap = new Swap({ @@ -701,15 +709,15 @@ describe('SwapV3', () => { paths: [pathBalWeth], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: BALANCER_ROUTER[chainId], client, rpcUrl, chainId, swap, - false, + wethIsEth: false, usePermit2Signatures, - ); + }); }); }); describe('wethIsEth: true', () => { @@ -720,15 +728,15 @@ describe('SwapV3', () => { swapKind: SwapKind.GivenIn, }); await expect(() => - assertSwapExactIn( - BALANCER_ROUTER[chainId], + assertSwapExactIn({ + contractToCall: BALANCER_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, + wethIsEth: true, usePermit2Signatures, - ), + }), ).rejects.toThrowError(buildCallWithPermit2ETHError); }); }); @@ -741,53 +749,54 @@ describe('SwapV3', () => { describe('wethIsEth: false', () => { const wethIsEth = false; test('GivenIn', async () => { - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, - new Swap({ + swap: new Swap({ chainId, paths: [pathMultiSwap], swapKind: SwapKind.GivenIn, }), wethIsEth, usePermit2Signatures, - ); + }); }); test('GivenOut', async () => { - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, - new Swap({ + swap: new Swap({ chainId, paths: [pathMultiSwap], swapKind: SwapKind.GivenOut, }), wethIsEth, usePermit2Signatures, - ); + }); }); }); describe('wethIsEth: true', () => { const wethIsEth = true; test('should throw', async () => { await expect(() => - assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + assertSwapExactIn({ + contractToCall: + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, - new Swap({ + swap: new Swap({ chainId, paths: [pathMultiSwap], swapKind: SwapKind.GivenIn, }), wethIsEth, usePermit2Signatures, - ), + }), ).rejects.toThrowError( buildCallWithPermit2ETHError, ); @@ -802,15 +811,15 @@ describe('SwapV3', () => { paths: [pathMultiSwap, pathWithExit], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactIn({ + contractToCall: BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - false, + wethIsEth: false, usePermit2Signatures, - ); + }); }); test('GivenOut', async () => { const swap = new Swap({ @@ -818,15 +827,15 @@ describe('SwapV3', () => { paths: [pathMultiSwap, pathWithExit], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - false, + wethIsEth: false, usePermit2Signatures, - ); + }); }); }); describe('wethIsEth: true', () => { @@ -837,15 +846,16 @@ describe('SwapV3', () => { swapKind: SwapKind.GivenIn, }); await expect(() => - assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + assertSwapExactIn({ + contractToCall: + BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - true, + wethIsEth: true, usePermit2Signatures, - ), + }), ).rejects.toThrowError( buildCallWithPermit2ETHError, ); @@ -912,13 +922,13 @@ describe('SwapV3', () => { ); expect(expected.swapKind).to.eq(SwapKind.GivenIn); expect(expected.pathAmounts).to.deep.eq([ - 98985649125345246328n, + 98985649641876909761n, ]); expect(expected.expectedAmountOut.token).to.deep.eq( daiToken, ); expect(expected.expectedAmountOut.amount).to.eq( - 98985649125345246328n, + 98985649641876909761n, ); }); test('GivenOut', async () => { @@ -952,6 +962,14 @@ describe('SwapV3', () => { }); }); describe('swap should be executed correctly', () => { + beforeEach(async () => { + await approveTokens( + client, + testAddress, + tokens, + protocolVersion, + ); + }); test('GivenIn', async () => { const swap = new Swap({ chainId, @@ -964,14 +982,19 @@ describe('SwapV3', () => { ], swapKind: SwapKind.GivenIn, }); - await assertSwapExactIn( - BALANCER_BATCH_ROUTER[chainId], + // Buffers can have a small difference due to rates so we don't check for 100% match between result and query + await assertSwapExactIn({ + contractToCall: BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - false, - ); + wethIsEth: false, + outputTest: { + isExactOut: false, + percentage: 0.001, + }, + }); }); test('GivenOut', async () => { const swap = new Swap({ @@ -985,14 +1008,18 @@ describe('SwapV3', () => { ], swapKind: SwapKind.GivenOut, }); - await assertSwapExactOut( - BALANCER_BATCH_ROUTER[chainId], + await assertSwapExactOut({ + contractToCall: BALANCER_BATCH_ROUTER[chainId], client, rpcUrl, chainId, swap, - false, - ); + wethIsEth: false, + inputTest: { + isExactIn: false, + percentage: 0.001, + }, + }); }); }); }); diff --git a/test/lib/utils/swapHelpers.ts b/test/lib/utils/swapHelpers.ts index 80ea538f..ca8c2f38 100644 --- a/test/lib/utils/swapHelpers.ts +++ b/test/lib/utils/swapHelpers.ts @@ -19,15 +19,46 @@ import { } from '../../../src'; import { sendTransactionGetBalances } from '../../lib/utils/helper'; -export async function assertSwapExactIn( - contractToCall: Address, - client: Client & PublicActions & TestActions & WalletActions, - rpcUrl: string, - chainId: ChainId, - swap: Swap, - wethIsEth: boolean, +// Helper function to check if two BigInts are within a given percentage +function areBigIntsWithinPercent( + value1: bigint, + value2: bigint, + percent: number, +): boolean { + if (percent < 0) { + throw new Error('Percent must be non-negative'); + } + const difference = value1 > value2 ? value1 - value2 : value2 - value1; + const percentFactor = BigInt(Math.floor(percent * 1e8)); + const tolerance = (value2 * percentFactor) / BigInt(1e10); + return difference <= tolerance; +} + +export async function assertSwapExactIn({ + contractToCall, + client, + rpcUrl, + chainId, + swap, + wethIsEth, usePermit2Signatures = false, -) { + outputTest = { + isExactOut: true, + percentage: 0, + }, +}: { + contractToCall: Address; + client: Client & PublicActions & TestActions & WalletActions; + rpcUrl: string; + chainId: ChainId; + swap: Swap; + wethIsEth: boolean; + usePermit2Signatures?: boolean; + outputTest?: { + isExactOut: boolean; + percentage: number; + }; +}) { const testAddress = (await client.getAddresses())[0]; const slippage = Slippage.fromPercentage('0.1'); const deadline = 999999999999999999n; @@ -108,22 +139,52 @@ export async function assertSwapExactIn( expectedTokenOutDelta = 0n; } - expect(balanceDeltas).to.deep.eq([ - expectedEthDelta, - expectedTokenInDelta, - expectedTokenOutDelta, - ]); + if (outputTest.isExactOut) + expect(balanceDeltas).to.deep.eq([ + expectedEthDelta, + expectedTokenInDelta, + expectedTokenOutDelta, + ]); + else { + // 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 + expect(balanceDeltas[0]).to.eq(expectedEthDelta); + expect(balanceDeltas[1]).to.eq(expectedTokenInDelta); + expect( + areBigIntsWithinPercent( + balanceDeltas[2], + expectedTokenOutDelta, + outputTest.percentage, + ), + ).toBe(true); + } } -export async function assertSwapExactOut( - contractToCall: Address, - client: Client & PublicActions & TestActions & WalletActions, - rpcUrl: string, - chainId: ChainId, - swap: Swap, - wethIsEth: boolean, +export async function assertSwapExactOut({ + contractToCall, + client, + rpcUrl, + chainId, + swap, + wethIsEth, usePermit2Signatures = false, -) { + inputTest = { + isExactIn: true, + percentage: 0, + }, +}: { + contractToCall: Address; + client: Client & PublicActions & TestActions & WalletActions; + rpcUrl: string; + chainId: ChainId; + swap: Swap; + wethIsEth: boolean; + usePermit2Signatures?: boolean; + inputTest?: { + isExactIn: boolean; + percentage: number; + }; +}) { const testAddress = (await client.getAddresses())[0]; const slippage = Slippage.fromPercentage('0.1'); const deadline = 999999999999999999n; @@ -207,9 +268,23 @@ export async function assertSwapExactOut( expectedTokenOutDelta = 0n; } - expect(balanceDeltas).to.deep.eq([ - expectedEthDelta, - expectedTokenInDelta, - expectedTokenOutDelta, - ]); + if (inputTest.isExactIn) + expect(balanceDeltas).to.deep.eq([ + expectedEthDelta, + expectedTokenInDelta, + expectedTokenOutDelta, + ]); + else { + // 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 + expect(balanceDeltas[0]).to.eq(expectedEthDelta); + expect(balanceDeltas[2]).to.eq(expectedTokenOutDelta); + expect( + areBigIntsWithinPercent( + balanceDeltas[1], + expectedTokenInDelta, + inputTest.percentage, + ), + ).toBe(true); + } } From c5b4287d905ce693e5a8eb295dca2c4f97621e10 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 6 Aug 2024 11:21:00 +0100 Subject: [PATCH 5/6] chore: Changeset. --- .changeset/quick-teachers-tease.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/quick-teachers-tease.md diff --git a/.changeset/quick-teachers-tease.md b/.changeset/quick-teachers-tease.md new file mode 100644 index 00000000..cd6eed3f --- /dev/null +++ b/.changeset/quick-teachers-tease.md @@ -0,0 +1,5 @@ +--- +"@balancer/sdk": minor +--- + +Adds buffer/boosted pool support to swaps. From e2a8be93776ed6d4e70d0b355855e8508aaaeaa2 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Tue, 6 Aug 2024 16:09:42 +0100 Subject: [PATCH 6/6] chore: Refactor, use better naming for test flags. --- src/abi/index.ts | 1 - test/entities/swaps/v3/swapV3.integration.test.ts | 4 ++-- test/lib/utils/swapHelpers.ts | 12 ++++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/abi/index.ts b/src/abi/index.ts index adfc9e05..6aeece7b 100644 --- a/src/abi/index.ts +++ b/src/abi/index.ts @@ -1,7 +1,6 @@ export * from './authorizer'; export * from './balancerBatchRouter'; export * from './balancerQueries'; -export * from './balancerBatchRouter'; export * from './balancerRelayer'; export * from './balancerRouter'; export * from './batchRelayerLibrary'; diff --git a/test/entities/swaps/v3/swapV3.integration.test.ts b/test/entities/swaps/v3/swapV3.integration.test.ts index c8579412..a160ba3e 100644 --- a/test/entities/swaps/v3/swapV3.integration.test.ts +++ b/test/entities/swaps/v3/swapV3.integration.test.ts @@ -991,7 +991,7 @@ describe('SwapV3', () => { swap, wethIsEth: false, outputTest: { - isExactOut: false, + testExactOutAmount: false, percentage: 0.001, }, }); @@ -1016,7 +1016,7 @@ describe('SwapV3', () => { swap, wethIsEth: false, inputTest: { - isExactIn: false, + testExactInAmount: false, percentage: 0.001, }, }); diff --git a/test/lib/utils/swapHelpers.ts b/test/lib/utils/swapHelpers.ts index ca8c2f38..25db95d1 100644 --- a/test/lib/utils/swapHelpers.ts +++ b/test/lib/utils/swapHelpers.ts @@ -43,7 +43,7 @@ export async function assertSwapExactIn({ wethIsEth, usePermit2Signatures = false, outputTest = { - isExactOut: true, + testExactOutAmount: true, percentage: 0, }, }: { @@ -55,7 +55,7 @@ export async function assertSwapExactIn({ wethIsEth: boolean; usePermit2Signatures?: boolean; outputTest?: { - isExactOut: boolean; + testExactOutAmount: boolean; percentage: number; }; }) { @@ -139,7 +139,7 @@ export async function assertSwapExactIn({ expectedTokenOutDelta = 0n; } - if (outputTest.isExactOut) + if (outputTest.testExactOutAmount) expect(balanceDeltas).to.deep.eq([ expectedEthDelta, expectedTokenInDelta, @@ -169,7 +169,7 @@ export async function assertSwapExactOut({ wethIsEth, usePermit2Signatures = false, inputTest = { - isExactIn: true, + testExactInAmount: true, percentage: 0, }, }: { @@ -181,7 +181,7 @@ export async function assertSwapExactOut({ wethIsEth: boolean; usePermit2Signatures?: boolean; inputTest?: { - isExactIn: boolean; + testExactInAmount: boolean; percentage: number; }; }) { @@ -268,7 +268,7 @@ export async function assertSwapExactOut({ expectedTokenOutDelta = 0n; } - if (inputTest.isExactIn) + if (inputTest.testExactInAmount) expect(balanceDeltas).to.deep.eq([ expectedEthDelta, expectedTokenInDelta,