From 7a524268afad364d53e828b92d7fcb128e67f27d Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 13 Sep 2023 14:20:43 -0300 Subject: [PATCH 1/3] Rename join kind to follow convention decided by the team --- src/entities/encoders/weighted.ts | 15 ++++++---- src/entities/join/index.ts | 24 ++++++++-------- src/entities/join/weighted/weightedJoin.ts | 33 ++++++++++++---------- test/weightedJoin.integration.test.ts | 22 +++++++-------- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/src/entities/encoders/weighted.ts b/src/entities/encoders/weighted.ts index 269bd0b2..45aa9888 100644 --- a/src/entities/encoders/weighted.ts +++ b/src/entities/encoders/weighted.ts @@ -39,7 +39,10 @@ export class WeightedEncoder { * @param amountsIn - the amounts each of token to deposit in the pool as liquidity * @param minimumBPT - the minimum acceptable BPT to receive in return for deposited tokens */ - static joinExactIn = (amountsIn: bigint[], minimumBPT: bigint): Address => + static joinUnbalanced = ( + amountsIn: bigint[], + minimumBPT: bigint, + ): Address => encodeAbiParameters( [{ type: 'uint256' }, { type: 'uint256[]' }, { type: 'uint256' }], [ @@ -54,7 +57,7 @@ export class WeightedEncoder { * @param bptAmountOut - the amount of BPT to be minted * @param enterTokenIndex - the index of the token to be provided as liquidity */ - static joinExactOutSingleAsset = ( + static joinSingleAsset = ( bptAmountOut: bigint, enterTokenIndex: number, ): Address => { @@ -73,7 +76,7 @@ export class WeightedEncoder { * Encodes the userData parameter for joining a WeightedPool proportionally to receive an exact amount of BPT * @param bptAmountOut - the amount of BPT to be minted */ - static joinExactOutProportional = (bptAmountOut: bigint): Address => { + static joinProportional = (bptAmountOut: bigint): Address => { return encodeAbiParameters( [{ type: 'uint256' }, { type: 'uint256' }], [ @@ -88,7 +91,7 @@ export class WeightedEncoder { * @param bptAmountIn - the amount of BPT to be burned * @param enterTokenIndex - the index of the token to removed from the pool */ - static exitExacInSingleAsset = ( + static exitSingleAsset = ( bptAmountIn: bigint, exitTokenIndex: number, ): Address => { @@ -106,7 +109,7 @@ export class WeightedEncoder { * Encodes the userData parameter for exiting a WeightedPool by removing tokens in return for an exact amount of BPT * @param bptAmountIn - the amount of BPT to be burned */ - static exitExacInProportional = (bptAmountIn: bigint): Address => { + static exitProportional = (bptAmountIn: bigint): Address => { return encodeAbiParameters( [{ type: 'uint256' }, { type: 'uint256' }], [ @@ -121,7 +124,7 @@ export class WeightedEncoder { * @param amountsOut - the amounts of each token to be withdrawn from the pool * @param maxBPTAmountIn - the minimum acceptable BPT to burn in return for withdrawn tokens */ - static exitExactOut = ( + static exitUnbalanced = ( amountsOut: bigint[], maxBPTAmountIn: bigint, ): Address => diff --git a/src/entities/join/index.ts b/src/entities/join/index.ts index 4a67705a..cafa2217 100644 --- a/src/entities/join/index.ts +++ b/src/entities/join/index.ts @@ -4,9 +4,9 @@ import { Address } from '../../types'; export enum JoinKind { Init = 'Init', - ExactIn = 'ExactIn', - ExactOutSingleAsset = 'ExactOutSingleAsset', - ExactOutProportional = 'ExactOutProportional', + Unbalanced = 'Unbalanced', + SingleAsset = 'SingleAsset', + Proportional = 'Proportional', } // Returned from API and used as input @@ -32,27 +32,27 @@ export type InitJoinInput = BaseJoinInput & { kind: JoinKind.Init; }; -export type ExactInJoinInput = BaseJoinInput & { +export type UnbalancedJoinInput = BaseJoinInput & { amountsIn: TokenAmount[]; - kind: JoinKind.ExactIn; + kind: JoinKind.Unbalanced; }; -export type ExactOutSingleAssetJoinInput = BaseJoinInput & { +export type SingleAssetJoinInput = BaseJoinInput & { bptOut: TokenAmount; tokenIn: Address; - kind: JoinKind.ExactOutSingleAsset; + kind: JoinKind.SingleAsset; }; -export type ExactOutProportionalJoinInput = BaseJoinInput & { +export type ProportionalJoinInput = BaseJoinInput & { bptOut: TokenAmount; - kind: JoinKind.ExactOutProportional; + kind: JoinKind.Proportional; }; export type JoinInput = | InitJoinInput - | ExactInJoinInput - | ExactOutSingleAssetJoinInput - | ExactOutProportionalJoinInput; + | UnbalancedJoinInput + | SingleAssetJoinInput + | ProportionalJoinInput; // Returned from a join query export type JoinQueryResult = { diff --git a/src/entities/join/weighted/weightedJoin.ts b/src/entities/join/weighted/weightedJoin.ts index 0bec8cd3..844dca42 100644 --- a/src/entities/join/weighted/weightedJoin.ts +++ b/src/entities/join/weighted/weightedJoin.ts @@ -28,7 +28,7 @@ export class WeightedJoin implements BaseJoin { // TODO - This would need extended to work with relayer // TODO: check inputs - // joinExactOutProportional only works for Weighted v2+ -> should we handle at the SDK level or should we just let the query fail? + // joinProportional only works for Weighted v2+ -> should we handle at the SDK level or should we just let the query fail? const poolTokens = poolState.tokens.map( (t) => new Token(input.chainId, t.address, t.decimals), @@ -45,24 +45,24 @@ export class WeightedJoin implements BaseJoin { ); userData = WeightedEncoder.joinInit(maxAmountsIn); break; - case JoinKind.ExactIn: + case JoinKind.Unbalanced: maxAmountsIn = poolTokens.map( (t) => input.amountsIn.find((a) => a.token.isEqual(t)) ?.amount ?? 0n, ); - userData = WeightedEncoder.joinExactIn(maxAmountsIn, 0n); + userData = WeightedEncoder.joinUnbalanced(maxAmountsIn, 0n); break; - case JoinKind.ExactOutSingleAsset: - userData = WeightedEncoder.joinExactOutSingleAsset( + case JoinKind.SingleAsset: + userData = WeightedEncoder.joinSingleAsset( input.bptOut.amount, poolTokens.findIndex( (t) => t.address === input.tokenIn.toLowerCase(), ), ); break; - case JoinKind.ExactOutProportional: - userData = WeightedEncoder.joinExactOutProportional( + case JoinKind.Proportional: + userData = WeightedEncoder.joinProportional( input.bptOut.amount, ); break; @@ -111,7 +111,7 @@ export class WeightedJoin implements BaseJoin { ); const tokenInIndex = - input.kind === JoinKind.ExactOutSingleAsset + input.kind === JoinKind.SingleAsset ? poolTokens.findIndex( (t) => t.address === input.tokenIn.toLowerCase(), ) @@ -143,31 +143,34 @@ export class WeightedJoin implements BaseJoin { userData = WeightedEncoder.joinInit(maxAmountsIn); break; } - case JoinKind.ExactIn: { + case JoinKind.Unbalanced: { maxAmountsIn = input.amountsIn.map((a) => a.amount); minBptOut = input.slippage.removeFrom(input.bptOut.amount); - userData = WeightedEncoder.joinExactIn(maxAmountsIn, minBptOut); + userData = WeightedEncoder.joinUnbalanced( + maxAmountsIn, + minBptOut, + ); break; } - case JoinKind.ExactOutSingleAsset: + case JoinKind.SingleAsset: if (input.tokenInIndex === undefined) { throw new Error( - 'tokenInIndex must be defined for ExactOutSingleAsset joins', + 'tokenInIndex must be defined for SingleAsset joins', ); } maxAmountsIn = input.amountsIn.map((a) => input.slippage.applyTo(a.amount), ); - userData = WeightedEncoder.joinExactOutSingleAsset( + userData = WeightedEncoder.joinSingleAsset( input.bptOut.amount, input.tokenInIndex, ); break; - case JoinKind.ExactOutProportional: { + case JoinKind.Proportional: { maxAmountsIn = input.amountsIn.map((a) => input.slippage.applyTo(a.amount), ); - userData = WeightedEncoder.joinExactOutProportional( + userData = WeightedEncoder.joinProportional( input.bptOut.amount, ); break; diff --git a/test/weightedJoin.integration.test.ts b/test/weightedJoin.integration.test.ts index c4fc13e4..1267bb7c 100644 --- a/test/weightedJoin.integration.test.ts +++ b/test/weightedJoin.integration.test.ts @@ -17,9 +17,9 @@ import { import { BaseJoin, - ExactInJoinInput, - ExactOutProportionalJoinInput, - ExactOutSingleAssetJoinInput, + UnbalancedJoinInput, + ProportionalJoinInput, + SingleAssetJoinInput, JoinKind, PoolState, Slippage, @@ -98,11 +98,11 @@ describe('weighted join test', () => { const amountIn = TokenAmount.fromHumanAmount(tokenIn, '1'); // perform join query to get expected bpt out - const joinInput: ExactInJoinInput = { + const joinInput: UnbalancedJoinInput = { amountsIn: [amountIn], chainId, rpcUrl, - kind: JoinKind.ExactIn, + kind: JoinKind.Unbalanced, }; const queryResult = await weightedJoin.query( joinInput, @@ -155,11 +155,11 @@ describe('weighted join test', () => { const amountIn = TokenAmount.fromHumanAmount(tokenIn, '1'); // perform join query to get expected bpt out - const joinInput: ExactInJoinInput = { + const joinInput: UnbalancedJoinInput = { amountsIn: [amountIn], chainId, rpcUrl, - kind: JoinKind.ExactIn, + kind: JoinKind.Unbalanced, joinWithNativeAsset: true, }; const queryResult = await weightedJoin.query( @@ -223,12 +223,12 @@ describe('weighted join test', () => { const tokenIn = '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0'; // perform join query to get expected bpt out - const joinInput: ExactOutSingleAssetJoinInput = { + const joinInput: SingleAssetJoinInput = { bptOut: amountOut, tokenIn, chainId, rpcUrl, - kind: JoinKind.ExactOutSingleAsset, + kind: JoinKind.SingleAsset, }; const queryResult = await weightedJoin.query( joinInput, @@ -275,11 +275,11 @@ describe('weighted join test', () => { const amountOut = TokenAmount.fromHumanAmount(tokenOut, '1'); // perform join query to get expected bpt out - const joinInput: ExactOutProportionalJoinInput = { + const joinInput: ProportionalJoinInput = { bptOut: amountOut, chainId, rpcUrl, - kind: JoinKind.ExactOutProportional, + kind: JoinKind.Proportional, }; const queryResult = await weightedJoin.query( joinInput, From 2ece7206f53d3f36a3768faa2139214d5dac30e6 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 13 Sep 2023 14:59:17 -0300 Subject: [PATCH 2/3] Add initial structure to check inputs --- src/entities/join/weighted/helpers.ts | 38 ++++++++++++++++++++++ src/entities/join/weighted/weightedJoin.ts | 6 ++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/entities/join/weighted/helpers.ts b/src/entities/join/weighted/helpers.ts index 2e2a209a..94e5ee09 100644 --- a/src/entities/join/weighted/helpers.ts +++ b/src/entities/join/weighted/helpers.ts @@ -1,3 +1,4 @@ +import { JoinInput, JoinKind, PoolState } from '..'; import { Address } from '../../../types'; export function getJoinParameters({ @@ -24,3 +25,40 @@ export function getJoinParameters({ return [poolId, sender, recipient, joinPoolRequest] as const; } + +export function checkInputs(input: JoinInput, poolState: PoolState) { + switch (input.kind) { + case JoinKind.Init: + checkTokenMismatch( + input.initAmountsIn.map((a) => a.token.address), + poolState.tokens.map((t) => t.address), + ); + break; + case JoinKind.Unbalanced: + checkTokenMismatch( + input.amountsIn.map((a) => a.token.address), + poolState.tokens.map((t) => t.address), + ); + break; + case JoinKind.SingleAsset: + checkTokenMismatch( + [input.tokenIn], + poolState.tokens.map((t) => t.address), + ); + case JoinKind.Proportional: + checkTokenMismatch( + [input.bptOut.token.address.toLowerCase() as Address], + [poolState.address], + ); + default: + break; + } +} + +function checkTokenMismatch(tokensIn: Address[], poolTokens: Address[]) { + for (const tokenIn of tokensIn) { + if (!poolTokens.includes(tokenIn.toLowerCase() as Address)) { + throw new Error(`Token ${tokenIn} not found in pool`); + } + } +} diff --git a/src/entities/join/weighted/weightedJoin.ts b/src/entities/join/weighted/weightedJoin.ts index 844dca42..7f446f46 100644 --- a/src/entities/join/weighted/weightedJoin.ts +++ b/src/entities/join/weighted/weightedJoin.ts @@ -10,7 +10,7 @@ import { ZERO_ADDRESS, } from '../../../utils'; import { balancerHelpersAbi, vaultAbi } from '../../../abi'; -import { getJoinParameters } from './helpers'; +import { checkInputs, getJoinParameters } from './helpers'; import { BaseJoin, JoinCallInput, @@ -27,8 +27,8 @@ export class WeightedJoin implements BaseJoin { ): Promise { // TODO - This would need extended to work with relayer - // TODO: check inputs - // joinProportional only works for Weighted v2+ -> should we handle at the SDK level or should we just let the query fail? + // TODO: Extend input validation for cases we'd like to check + checkInputs(input, poolState); const poolTokens = poolState.tokens.map( (t) => new Token(input.chainId, t.address, t.decimals), From 4d49b3f0c1d98da8667d62821fb776f796eb1571 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Wed, 13 Sep 2023 16:16:27 -0300 Subject: [PATCH 3/3] Use index provided by the api to sort pool tokens --- src/entities/join/index.ts | 3 ++- src/entities/join/weighted/weightedJoin.ts | 6 +++--- test/weightedJoin.integration.test.ts | 7 ++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/entities/join/index.ts b/src/entities/join/index.ts index cafa2217..b448a52f 100644 --- a/src/entities/join/index.ts +++ b/src/entities/join/index.ts @@ -17,7 +17,8 @@ export type PoolState = { tokens: { address: Address; decimals: number; - }[]; // already properly sorted in case different versions sort them differently + index: number; + }[]; }; // This will be extended for each pools specific input requirements diff --git a/src/entities/join/weighted/weightedJoin.ts b/src/entities/join/weighted/weightedJoin.ts index 7f446f46..7b34c7f3 100644 --- a/src/entities/join/weighted/weightedJoin.ts +++ b/src/entities/join/weighted/weightedJoin.ts @@ -30,9 +30,9 @@ export class WeightedJoin implements BaseJoin { // TODO: Extend input validation for cases we'd like to check checkInputs(input, poolState); - const poolTokens = poolState.tokens.map( - (t) => new Token(input.chainId, t.address, t.decimals), - ); + const poolTokens = poolState.tokens + .sort((a, b) => a.index - b.index) + .map((t) => new Token(input.chainId, t.address, t.decimals)); let maxAmountsIn = Array(poolTokens.length).fill(MAX_UINT256); let userData: Address; diff --git a/test/weightedJoin.integration.test.ts b/test/weightedJoin.integration.test.ts index 1267bb7c..b8b07453 100644 --- a/test/weightedJoin.integration.test.ts +++ b/test/weightedJoin.integration.test.ts @@ -328,7 +328,8 @@ describe('weighted join test', () => { export class MockApi { public async getPool(id: Address): Promise { - let tokens: { address: Address; decimals: number }[] = []; + let tokens: { address: Address; decimals: number; index: number }[] = + []; if ( id === '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014' @@ -337,10 +338,12 @@ export class MockApi { { address: '0xba100000625a3754423978a60c9317c58a424e3d', // BAL decimals: 18, + index: 0, }, { address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', // wETH decimals: 18, + index: 1, }, ]; } else if ( @@ -351,10 +354,12 @@ export class MockApi { { address: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', // wstETH slot 0 decimals: 18, + index: 0, }, { address: '0xc00e94cb662c3520282e6f5717214004a7f26888', // COMP slot 1 decimals: 18, + index: 1, }, ]; }