Skip to content

Commit

Permalink
Merge pull request #481 from balancer/375-priceimpact-error-messaging…
Browse files Browse the repository at this point in the history
…-improvement

375 priceimpact error messaging improvement
  • Loading branch information
johngrantuk authored Nov 7, 2024
2 parents 0454cae + dcd1f0d commit d7f1755
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 77 deletions.
7 changes: 7 additions & 0 deletions .changeset/silent-ligers-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@balancer/sdk": patch
---

PriceImpact error handling and messaging improvements.
* Catch any errors with initial add/remove steps - these are thrown because the user input is valid, e.g. would cause INVARIANT_GROWTH errors
* Catch any errors in query during unbalanced calc steps and throw message that will help with debug - e.g. caused by delta amounts
82 changes: 52 additions & 30 deletions src/entities/priceImpact/addLiquidityUnbalancedBoosted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,17 @@ export async function addLiquidityUnbalancedBoosted(

// simulate adding liquidity to get amounts in
const addLiquidity = new AddLiquidityBoostedV3();
const { amountsIn, bptOut } = await addLiquidity.query(input, poolState);
let amountsIn: TokenAmount[];
let bptOut: TokenAmount;
try {
const queryResult = await addLiquidity.query(input, poolState);
amountsIn = queryResult.amountsIn;
bptOut = queryResult.bptOut;
} catch (err) {
throw new Error(
`addLiquidity operation will fail at SC level with user defined input.\n${err}`,
);
}
const poolTokens = amountsIn.map((a) => a.token);

// simulate removing liquidity to get amounts out
Expand Down Expand Up @@ -143,17 +153,23 @@ async function queryAddLiquidityForTokenDelta(
);

const amountsIn = [absDelta.toInputAmount()];
// Work-around Vault _MINIMUM_WRAP_AMOUNT limit
if (absDelta.amount <= 1000n) amountsIn[0].rawAmount = 1001n;

const { bptOut: deltaBPT } = await addLiquidity.query(
{
...input,
amountsIn,
},
poolState,
);
return delta < 0n ? -deltaBPT.amount : deltaBPT.amount;
if (absDelta.amount <= 1000n)
// Work-around Vault _MINIMUM_WRAP_AMOUNT limit
amountsIn[0].rawAmount = 1001n;
try {
const { bptOut: deltaBPT } = await addLiquidity.query(
{
...input,
amountsIn,
},
poolState,
);
return delta < 0n ? -deltaBPT.amount : deltaBPT.amount;
} catch (err) {
throw new Error(
`Unexpected error while calculating addLiquidityUnbalancedBoosted PI at Delta add step:\n${err}`,
);
}
}

async function zeroOutDeltas(
Expand Down Expand Up @@ -213,25 +229,31 @@ async function zeroOutDeltas(
inputAmountRaw,
outputAmountRaw,
);
const swap = new Swap(swapInput);
const result = await swap.query(input.rpcUrl);
const resultAmount =
result.swapKind === SwapKind.GivenIn
? result.expectedAmountOut
: result.expectedAmountIn;
try {
const swap = new Swap(swapInput);
const result = await swap.query(input.rpcUrl);
const resultAmount =
result.swapKind === SwapKind.GivenIn
? result.expectedAmountOut
: result.expectedAmountIn;

deltas[givenTokenIndex] = 0n;
deltaBPTs[givenTokenIndex] = 0n;
deltas[resultTokenIndex] =
deltas[resultTokenIndex] + resultAmount.amount;
deltaBPTs[resultTokenIndex] = await queryAddLiquidityForTokenDelta(
addLiquidity,
input,
poolState,
poolTokens,
resultTokenIndex,
deltas[resultTokenIndex],
);
deltas[givenTokenIndex] = 0n;
deltaBPTs[givenTokenIndex] = 0n;
deltas[resultTokenIndex] =
deltas[resultTokenIndex] + resultAmount.amount;
deltaBPTs[resultTokenIndex] = await queryAddLiquidityForTokenDelta(
addLiquidity,
input,
poolState,
poolTokens,
resultTokenIndex,
deltas[resultTokenIndex],
);
} catch (err) {
throw new Error(
`Unexpected error while calculating addLiquidityUnbalancedBoosted PI at Swap step:\n${err}`,
);
}
}
return minNegativeDeltaIndex;
}
Expand Down
140 changes: 93 additions & 47 deletions src/entities/priceImpact/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { AddLiquidityNested } from '../addLiquidityNested';
import { AddLiquidityBoostedUnbalancedInput } from '../addLiquidityBoosted/types';
import { addLiquidityUnbalancedBoosted } from './addLiquidityUnbalancedBoosted';
import { addLiquidityNested } from './addLiquidityNested';
import { Token } from '../token';

export class PriceImpact {
/**
Expand All @@ -42,7 +43,15 @@ export class PriceImpact {

// simulate adding liquidity to get amounts in
const addLiquidity = new AddLiquidity();
const { amountsIn } = await addLiquidity.query(input, poolState);
let amountsIn: TokenAmount[];
try {
const queryResult = await addLiquidity.query(input, poolState);
amountsIn = queryResult.amountsIn;
} catch (err) {
throw new Error(
`addLiquiditySingleToken operation will fail at SC level with user defined input.\n${err}`,
);
}

// simulate removing liquidity to get amounts out
const removeLiquidity = new RemoveLiquidity();
Expand Down Expand Up @@ -101,11 +110,19 @@ export class PriceImpact {

// simulate adding liquidity to get amounts in
const addLiquidity = new AddLiquidity();
const { amountsIn, bptOut } = await addLiquidity.query(
input,
poolState,
);
const poolTokens = amountsIn.map((a) => a.token);
let amountsIn: TokenAmount[];
let bptOut: TokenAmount;
let poolTokens: Token[];
try {
const queryResult = await addLiquidity.query(input, poolState);
amountsIn = queryResult.amountsIn;
bptOut = queryResult.bptOut;
poolTokens = amountsIn.map((a) => a.token);
} catch (err) {
throw new Error(
`addLiquidityUnbalanced operation will fail at SC level with user defined input.\n${err}`,
);
}

// simulate removing liquidity to get amounts out
const removeLiquidity = new RemoveLiquidity();
Expand All @@ -129,7 +146,13 @@ export class PriceImpact {
if (deltas[i] === 0n) {
deltaBPTs.push(0n);
} else {
deltaBPTs.push(await queryAddLiquidityForTokenDelta(i));
try {
deltaBPTs.push(await queryAddLiquidityForTokenDelta(i));
} catch (err) {
throw new Error(
`Unexpected error while calculating addLiquidityUnbalanced PI at Delta add step:\n${err}`,
);
}
}
}

Expand Down Expand Up @@ -188,39 +211,45 @@ export class PriceImpact {
resultTokenIndex = minPositiveDeltaIndex;
outputAmountRaw = abs(deltas[givenTokenIndex]);
}
const swapInput: SwapInput = {
chainId: input.chainId,
paths: [
{
tokens: [
poolTokens[
minPositiveDeltaIndex
].toInputToken(),
poolTokens[
minNegativeDeltaIndex
].toInputToken(),
],
pools: [poolState.id],
inputAmountRaw,
outputAmountRaw,
protocolVersion: poolState.protocolVersion,
},
],
swapKind,
};
const swap = new Swap(swapInput);
const result = await swap.query(input.rpcUrl);
const resultAmount =
result.swapKind === SwapKind.GivenIn
? result.expectedAmountOut
: result.expectedAmountIn;
try {
const swapInput: SwapInput = {
chainId: input.chainId,
paths: [
{
tokens: [
poolTokens[
minPositiveDeltaIndex
].toInputToken(),
poolTokens[
minNegativeDeltaIndex
].toInputToken(),
],
pools: [poolState.id],
inputAmountRaw,
outputAmountRaw,
protocolVersion: poolState.protocolVersion,
},
],
swapKind,
};
const swap = new Swap(swapInput);
const result = await swap.query(input.rpcUrl);
const resultAmount =
result.swapKind === SwapKind.GivenIn
? result.expectedAmountOut
: result.expectedAmountIn;

deltas[givenTokenIndex] = 0n;
deltaBPTs[givenTokenIndex] = 0n;
deltas[resultTokenIndex] =
deltas[resultTokenIndex] + resultAmount.amount;
deltaBPTs[resultTokenIndex] =
await queryAddLiquidityForTokenDelta(resultTokenIndex);
deltas[givenTokenIndex] = 0n;
deltaBPTs[givenTokenIndex] = 0n;
deltas[resultTokenIndex] =
deltas[resultTokenIndex] + resultAmount.amount;
deltaBPTs[resultTokenIndex] =
await queryAddLiquidityForTokenDelta(resultTokenIndex);
} catch (err) {
throw new Error(
`Unexpected error while calculating addLiquidityUnbalanced PI at Swap step:\n${err}`,
);
}
}
return minNegativeDeltaIndex;
}
Expand Down Expand Up @@ -285,10 +314,17 @@ export class PriceImpact {

// simulate removing liquidity to get amounts out
const removeLiquidity = new RemoveLiquidity();
const { bptIn, amountsOut } = await removeLiquidity.query(
input,
poolState,
);
let amountsOut: TokenAmount[];
let bptIn: TokenAmount;
try {
const queryResult = await removeLiquidity.query(input, poolState);
amountsOut = queryResult.amountsOut;
bptIn = queryResult.bptIn;
} catch (err) {
throw new Error(
`removeLiquidity operation will fail at SC level with user defined input.\n${err}`,
);
}

// simulate adding liquidity to get amounts in
const addLiquidity = new AddLiquidity();
Expand Down Expand Up @@ -325,10 +361,20 @@ export class PriceImpact {

// simulate removing liquidity to get amounts out
const removeLiquidityNested = new RemoveLiquidityNested();
const { bptAmountIn, amountsOut } = await removeLiquidityNested.query(
input,
nestedPoolState,
);
let amountsOut: TokenAmount[];
let bptAmountIn: TokenAmount;
try {
const queryResult = await removeLiquidityNested.query(
input,
nestedPoolState,
);
amountsOut = queryResult.amountsOut;
bptAmountIn = queryResult.bptAmountIn;
} catch (err) {
throw new Error(
`removeLiquidity operation will fail at SC level with user defined input.\n${err}`,
);
}

// simulate adding liquidity to get amounts in
const addLiquidityNested = new AddLiquidityNested();
Expand Down
28 changes: 28 additions & 0 deletions test/mockData/weightedWethBalPool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { PoolState } from '@/entities';
import { PoolType } from '@/types';
import { ChainId } from '@/utils';
import { POOLS, TOKENS } from 'test/lib/utils';

const chainId = ChainId.SEPOLIA;
export const MOCK_WETH_BAL_POOL = POOLS[chainId].MOCK_WETH_BAL_POOL;
export const WETH = TOKENS[chainId].WETH;
const BAL = TOKENS[chainId].BAL;

export const weightedWethBal: PoolState = {
id: MOCK_WETH_BAL_POOL.id,
address: MOCK_WETH_BAL_POOL.address,
type: PoolType.Weighted,
protocolVersion: 3,
tokens: [
{
address: WETH.address,
decimals: WETH.decimals,
index: 0,
},
{
address: BAL.address,
decimals: BAL.decimals,
index: 1,
},
],
};
Loading

0 comments on commit d7f1755

Please sign in to comment.