From cb5a5ff42c68d573fa5354a487893e18e1d393e3 Mon Sep 17 00:00:00 2001 From: Bruno Eidam Guerios Date: Tue, 21 Nov 2023 11:20:05 -0300 Subject: [PATCH] Add generalized approach for unbalanced join --- balancer-js/src/lib/utils/math.ts | 6 + .../pricing/priceImpact.compare.spec.ts | 167 ++++++++++++------ balancer-js/wstETH-rETH-sfrxETH.csv | 120 +++++++++++++ 3 files changed, 242 insertions(+), 51 deletions(-) create mode 100644 balancer-js/wstETH-rETH-sfrxETH.csv diff --git a/balancer-js/src/lib/utils/math.ts b/balancer-js/src/lib/utils/math.ts index e4ea33b33..00b80ec21 100644 --- a/balancer-js/src/lib/utils/math.ts +++ b/balancer-js/src/lib/utils/math.ts @@ -50,3 +50,9 @@ export function formatFromBigInt18(value: bigint): string { * Like parseEther but for numbers. Converts floating point to BigNumber using 18 decimals */ export const bn = (value: number): BigNumber => _parseFixed(`${value}`, 18); + +export const min = (values: BigNumber[]): BigNumber => + values.reduce((a, b) => (a.lt(b) ? a : b)); + +export const max = (values: BigNumber[]): BigNumber => + values.reduce((a, b) => (a.gt(b) ? a : b)); diff --git a/balancer-js/src/modules/pricing/priceImpact.compare.spec.ts b/balancer-js/src/modules/pricing/priceImpact.compare.spec.ts index 7936dbda9..510ccf2b4 100644 --- a/balancer-js/src/modules/pricing/priceImpact.compare.spec.ts +++ b/balancer-js/src/modules/pricing/priceImpact.compare.spec.ts @@ -7,14 +7,18 @@ import * as fs from 'fs'; import { Address, BalancerSDK, + BatchSwapStep, Network, PoolToken, PoolWithMethods, SubgraphPoolBase, SwapType, + max, + min, } from '@/.'; import { forkSetup, TestPoolHelper } from '@/test/lib/utils'; import { queryBatchSwap } from '../swaps/queryBatchSwap'; +import { Zero } from '@ethersproject/constants'; dotenv.config(); @@ -27,7 +31,7 @@ const { contracts, pricing } = sdk; const provider = new JsonRpcProvider(rpcUrl, 1); const signer = provider.getSigner(); const { balancerHelpers, vault } = contracts; -const csvFilePath = '50COIL50USDC_USDC.csv'; +const csvFilePath = 'wstETH-rETH-sfrxETH.csv'; // Write the header to the CSV file const csvLine = 'action,test,spot price,ABA,error abs,error rel,\n'; fs.writeFileSync(csvFilePath, csvLine, { flag: 'w' }); @@ -37,14 +41,14 @@ const writeNewTable = (header: string) => { const blockNumber = 18559730; const testPoolId = - '0x42fbd9f666aacc0026ca1b88c94259519e03dd67000200000000000000000507'; // 80BAL/20WETH + '0x42ed016f826165c2e5976fe5bc3df540c5ad0af700000000000000000000058b'; // 80BAL/20WETH /** * When testing pools with phantom BPT (e.g. ComposableStable), indexes should consider pool tokens with BPT */ // swap config -const swapAmountFloat = '500'; +const swapAmountFloat = '10'; const swapAmountFloats = [ swapAmountFloat, String(Number(swapAmountFloat) * 2), @@ -55,10 +59,10 @@ const swapAmountFloats = [ String(Number(swapAmountFloat) * 100), ]; const assetInIndex = 1; -const assetOutIndex = 0; +const assetOutIndex = 2; // single token join config -const joinAmountFloat = '500'; +const joinAmountFloat = '10'; const tokenIndex = 1; const singleTokenJoinTests = [ { amountFloat: joinAmountFloat, tokenIndex }, @@ -73,11 +77,9 @@ const singleTokenJoinTests = [ // unbalanced join config // const amountsInFloat = ['0', '200', '100', '10']; // should add value for BPT if present const unbalancedJoinTests = [ - { amountsInFloat: ['10', '10'] }, - { amountsInFloat: ['10', '100'] }, - { amountsInFloat: ['10', '1000'] }, - { amountsInFloat: ['10', '10000'] }, - { amountsInFloat: ['10', '100000'] }, + { amountsInFloat: ['0', '10', '100', '1000'] }, + { amountsInFloat: ['0', '100', '1000', '10'] }, + { amountsInFloat: ['0', '1000', '100', '10'] }, // Add more test scenarios as needed ]; @@ -339,7 +341,7 @@ describe('Price impact comparison tests', async () => { }); unbalancedJoinTests.forEach((test, index) => { - context('unbalanced join - 2 tokens', async () => { + context('unbalanced join - generalized (multi-token)', async () => { let tokensIn: string[]; let amountsIn: BigNumber[]; @@ -398,7 +400,7 @@ describe('Price impact comparison tests', async () => { console.log(`priceImpactSpotPrice: ${priceImpactSpot}`); }); - it('should calculate price impact - ABA method', async () => { + it('should calculate price impact - ABA method - generalized (multi-token)', async () => { const maxAmountsInByToken = new Map( amountsIn.map((a, i) => [tokensIn[i], a]) ); @@ -419,50 +421,113 @@ describe('Price impact comparison tests', async () => { // diff between unbalanced and proportional amounts for token 1 const diffs = amountsOut.map((a, i) => a.sub(amountsIn[i])); - const excessIndex = diffs.findIndex((a) => a.gt(0)); // token index that has excess amount on proportional compared to unbalanced - const otherIndex = diffs.findIndex((a) => a.lt(0)); - const diffExcess = amountsOut[excessIndex].sub(amountsIn[excessIndex]); - - // swap that diff to token other (non-excess) - const returnAmounts = await queryBatchSwap( - vault, - SwapType.SwapExactIn, - [ - { - poolId: pool.id, - assetInIndex: excessIndex, - assetOutIndex: otherIndex, - amount: diffExcess.toString(), - userData: '0x', - }, - ], - pool.tokensList - ); - - // calculate final other token amount (using sub because returnAmounts[0] is negative) - const otherTokenFinal = amountsOut[otherIndex].sub( - BigNumber.from(returnAmounts[otherIndex]) - ); - // diff between unbalanced and proportional amounts for token 0 - const diffOther = amountsIn[otherIndex].sub(otherTokenFinal); - - // query join with diffOther in order to get BPT difference between unbalanced and proportional - const diffAmounts = new Map([ - [pool.tokensList[otherIndex], diffOther], - ]); + const diffBPTs: BigNumber[] = []; + for (let i = 0; i < diffs.length; i++) { + if (diffs[i].eq(Zero)) { + diffBPTs.push(Zero); + } else { + const diffQuery = await balancerHelpers.callStatic.queryJoin( + ...pool.buildQueryJoinExactIn({ + maxAmountsInByToken: new Map([ + [tokensIn[i], diffs[i].abs()], + ]), + }) + ); + const diffBPT = diffQuery.bptOut.mul(diffs[i].gte(0) ? 1 : -1); + diffBPTs.push(diffBPT); + } + } + let minPositiveDiffIndex = 0; + let minNegativeDiffIndex = 1; - const { bptOut: bptOutDiff } = - await balancerHelpers.callStatic.queryJoin( - ...pool.buildQueryJoinExactIn({ - maxAmountsInByToken: diffAmounts, - }) + const nonZeroDiffs = diffs.filter((a) => !a.eq(Zero)); + for (let i = 0; i < nonZeroDiffs.length - 1; i++) { + minPositiveDiffIndex = diffBPTs.findIndex((diffBPT) => + diffBPT.eq(min(diffBPTs.filter((a) => a.gt(0)))) ); + minNegativeDiffIndex = diffBPTs.findIndex((diffBPT) => + diffBPT.eq(max(diffBPTs.filter((a) => a.lt(0)))) + ); + + let returnAmounts: string[]; + if ( + diffBPTs[minPositiveDiffIndex] < + diffBPTs[minNegativeDiffIndex].abs() + ) { + // swap that diff to token other (non-excess) + returnAmounts = await queryBatchSwap( + vault, + SwapType.SwapExactIn, + [ + { + poolId: pool.id, + assetInIndex: minPositiveDiffIndex, + assetOutIndex: minNegativeDiffIndex, + amount: diffs[minPositiveDiffIndex].toString(), + userData: '0x', + }, + ], + pool.tokensList + ); + diffs[minPositiveDiffIndex] = Zero; + diffBPTs[minPositiveDiffIndex] = Zero; + diffs[minNegativeDiffIndex] = diffs[minNegativeDiffIndex].sub( + BigNumber.from(returnAmounts[minNegativeDiffIndex]) + ); + const diffQuery = await balancerHelpers.callStatic.queryJoin( + ...pool.buildQueryJoinExactIn({ + maxAmountsInByToken: new Map([ + [ + tokensIn[minNegativeDiffIndex], + diffs[minNegativeDiffIndex].abs(), + ], + ]), + }) + ); + diffBPTs[minNegativeDiffIndex] = diffQuery.bptOut.mul(-1); + } else { + returnAmounts = await queryBatchSwap( + vault, + SwapType.SwapExactOut, + [ + { + poolId: pool.id, + assetInIndex: minPositiveDiffIndex, + assetOutIndex: minNegativeDiffIndex, + amount: diffs[minNegativeDiffIndex].abs().toString(), + userData: '0x', + }, + ], + pool.tokensList + ); + diffs[minNegativeDiffIndex] = Zero; + diffBPTs[minNegativeDiffIndex] = Zero; + diffs[minPositiveDiffIndex] = diffs[minPositiveDiffIndex].add( + BigNumber.from(returnAmounts[minPositiveDiffIndex]) + ); + const diffQuery = await balancerHelpers.callStatic.queryJoin( + ...pool.buildQueryJoinExactIn({ + maxAmountsInByToken: new Map([ + [ + tokensIn[minPositiveDiffIndex], + diffs[minPositiveDiffIndex].abs(), + ], + ]), + }) + ); + diffBPTs[minPositiveDiffIndex] = diffQuery.bptOut; + } + } - const initialBPT = parseFloat(formatFixed(bptOut, 18)); - const finalBPT = parseFloat(formatFixed(bptOut.sub(bptOutDiff), 18)); + const amountInitial = parseFloat( + amountsIn[minNegativeDiffIndex].toString() + ); + const amountDiff = parseFloat( + diffs[minNegativeDiffIndex].abs().toString() + ); - priceImpactABA = (initialBPT - finalBPT) / initialBPT / 2; + priceImpactABA = amountDiff / amountInitial / 2; console.log(`priceImpactABA : ${priceImpactABA}`); }); }); diff --git a/balancer-js/wstETH-rETH-sfrxETH.csv b/balancer-js/wstETH-rETH-sfrxETH.csv new file mode 100644 index 000000000..e182154e6 --- /dev/null +++ b/balancer-js/wstETH-rETH-sfrxETH.csv @@ -0,0 +1,120 @@ +action,test,spot price,ABA,error abs,error rel, +swap,1,0.00040377556888164767,0.00040370224878127914,-7.332010036853161e-8,-0.0001815862722244663 +Swap Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,10,3049.113080910972561465,0.0032796422220629272 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +swap,2,0.00040752761462463616,0.00040748466224682645,-4.2952377809709914e-8,-0.00010539746576258964 +Swap Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,20,3049.113080910972561465,0.0065592844441258544 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +swap,3,0.00041865039101878187,0.00041883520209616163,1.848110773797619e-7,0.00044144489374541327 +Swap Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,50,3049.113080910972561465,0.016398211110314635 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +swap,4,0.0004367569669131873,0.0004377776577118198,0.0000010206907986324491,0.002336976570393961 +Swap Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,100,3049.113080910972561465,0.03279642222062927 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +swap,5,0.0004880753174956617,0.0004950607027807906,0.000006985385285128876,0.014312105191000499 +Swap Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,250,3049.113080910972561465,0.08199105555157317 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +swap,6,0.0005649246215843502,0.0005938876298481546,0.000028963008263804368,0.0512688014598773 +Swap Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,500,3049.113080910972561465,0.16398211110314634 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +swap,7,0.0006937172565830037,0.0008207671867847921,0.00012704993020178838,0.18314367848882648 +Swap Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,1000,3049.113080910972561465,0.3279642222062927 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +single token join,1,0.000299821876819659,0.0002997818706826472,-4.000613701183068e-8,-0.00013343301508279907 +Single-Sided Join Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,10,3049.113080910972561465,0.0032796422220629272 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +single token join,2,0.000301646893391609,0.00030162116023273454,-2.573315887444529e-8,-0.00008530888080799017 +Single-Sided Join Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,20,3049.113080910972561465,0.0065592844441258544 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +single token join,3,0.000307064127222337,0.00030714052265715,7.639543481299067e-8,0.00024879309577466454 +Single-Sided Join Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,50,3049.113080910972561465,0.016398211110314635 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +single token join,4,0.000315905711879544,0.0003163503571391857,4.446452596417231e-7,0.001407525229588973 +Single-Sided Join Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,100,3049.113080910972561465,0.03279642222062927 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +single token join,5,0.000341126126129627,0.00034417624561956475,0.0000030501194899377397,0.008941324795447366 +Single-Sided Join Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,250,3049.113080910972561465,0.08199105555157317 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +single token join,6,0.000379371869200906,0.0003919909344199368,0.000012619065219030814,0.033263049381102285 +Single-Sided Join Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,500,3049.113080910972561465,0.16398211110314634 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +single token join,7,0.00044486981991085,0.00050014069422582,0.00005527087431496998,0.12424055721749348 +Single-Sided Join Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,1000,3049.113080910972561465,0.3279642222062927 +0xac3e018457b222d93114458476f3e3416abbe38f,0,8353.098667955846370862,0 +0xae78736cd615f374d3085123a210448e74fc6393,0,1210.62732888482209938,0 + +unbalanced join,1,0.001395038034686279,0.002192811273091142,0.000797773238404863,0.5718648657377066 +Unbalanced Join Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,10,3049.113080910972561465,0.0032796422220629272 +0xac3e018457b222d93114458476f3e3416abbe38f,100,8353.098667955846370862,0.011971605265914068 +0xae78736cd615f374d3085123a210448e74fc6393,1000,1210.62732888482209938,0.8260180289512851 + +unbalanced join,2,0.000143047857261436,0.00015267199507975335,0.000009624137818317347,0.0672791470111164 +Unbalanced Join Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,100,3049.113080910972561465,0.03279642222062927 +0xac3e018457b222d93114458476f3e3416abbe38f,1000,8353.098667955846370862,0.11971605265914068 +0xae78736cd615f374d3085123a210448e74fc6393,10,1210.62732888482209938,0.00826018028951285 + +unbalanced join,3,0.00038505679898757,0.00045787494478876696,0.00007281814580119696,0.18911014165353718 +Unbalanced Join Summary +0x42ed016f826165c2e5976fe5bc3df540c5ad0af7,0,2596148429267391.797360501734824291,0 +0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0,1000,3049.113080910972561465,0.3279642222062927 +0xac3e018457b222d93114458476f3e3416abbe38f,100,8353.098667955846370862,0.011971605265914068 +0xae78736cd615f374d3085123a210448e74fc6393,10,1210.62732888482209938,0.00826018028951285 +