From 761dd787c472877fa3fb34a64166b484643521e2 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem De Liz Date: Thu, 20 Jul 2023 18:37:08 -0300 Subject: [PATCH 1/4] Adding chunkSize param to multicall fetchPools function; --- balancer-js/examples/swaps/swap.ts | 62 +-- .../src/modules/sor/pool-data/onChainData.ts | 492 ++++++++++-------- .../sor/pool-data/subgraphPoolDataService.ts | 9 +- .../src/modules/sor/pool-data/types.ts | 17 + balancer-js/src/modules/swaps/swaps.module.ts | 8 +- 5 files changed, 316 insertions(+), 272 deletions(-) create mode 100644 balancer-js/src/modules/sor/pool-data/types.ts diff --git a/balancer-js/examples/swaps/swap.ts b/balancer-js/examples/swaps/swap.ts index 98924d382..3bbd37054 100644 --- a/balancer-js/examples/swaps/swap.ts +++ b/balancer-js/examples/swaps/swap.ts @@ -1,72 +1,60 @@ /** * How to build a swap and send it using ethers.js - * + * * How to run: * yarn example examples/swaps/swap.ts */ -import { BalancerSDK, Network } from '@balancer-labs/sdk' -import { formatFixed } from '@ethersproject/bignumber' -import { AddressZero } from '@ethersproject/constants' +import { BalancerSDK, Network } from '@balancer-labs/sdk'; +import { formatFixed } from '@ethersproject/bignumber'; +import { AddressZero } from '@ethersproject/constants'; -const tokenIn = AddressZero // eth -const tokenOut = '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599' // wBTC -const amount = String(BigInt(100e18)) // 100 eth +const tokenIn = AddressZero; // eth +const tokenOut = '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'; // wBTC +const amount = String(BigInt(100e18)); // 100 eth const sdk = new BalancerSDK({ network: Network.MAINNET, rpcUrl: `http://127.0.0.1:8545`, // Uses a local fork for simulating transaction sending. -}) +}); -const { swaps } = sdk +const { swaps } = sdk; -const erc20Out = sdk.contracts.ERC20(tokenOut, sdk.provider) +const erc20Out = sdk.contracts.ERC20(tokenOut, sdk.provider); async function swap() { - const signer = sdk.provider.getSigner() - const account = await signer.getAddress() + const signer = sdk.provider.getSigner(); + const account = await signer.getAddress(); // Finding a trading route rely on on-chain data. // fetchPools will fetch the current data from the subgraph. // Let's fetch just 5 pools with highest liquidity of tokenOut. - await swaps.fetchPools({ - first: 5, - where: { - swapEnabled: { - eq: true, - }, - tokensList: { - contains: [tokenOut], - }, - }, - orderBy: 'totalLiquidity', - orderDirection: 'desc', - }) + await swaps.fetchPools(undefined, 30); // Set exectution deadline to 60 seconds from now - const deadline = String(Math.ceil(Date.now() / 1000) + 60) + const deadline = String(Math.ceil(Date.now() / 1000) + 60); // Avoid getting rekt by setting low slippage from expected amounts out, 10 bsp = 0.1% - const maxSlippage = 10 + const maxSlippage = 10; // Building the route payload const payload = await swaps.buildRouteExactIn( account, account, - tokenIn, // eth + tokenIn, // eth tokenOut, // wBTC amount, { maxSlippage, - deadline + deadline, } - ) + ); // Extract parameters required for sendTransaction - const { to, data, value } = payload + const { to, data, value } = payload; // Execution with ethers.js try { - const balanceBefore = await erc20Out.balanceOf(account) + const balanceBefore = await erc20Out.balanceOf(account); await ( await signer.sendTransaction({ @@ -74,20 +62,20 @@ async function swap() { data, value, }) - ).wait() + ).wait(); // check delta - const balanceAfter = await erc20Out.balanceOf(account) + const balanceAfter = await erc20Out.balanceOf(account); console.log( `Amount of BTC received: ${formatFixed( balanceAfter.sub(balanceBefore), 8 )}` - ) + ); } catch (err) { - console.log(err) + console.log(err); } } -swap() +swap(); diff --git a/balancer-js/src/modules/sor/pool-data/onChainData.ts b/balancer-js/src/modules/sor/pool-data/onChainData.ts index 97d5d423c..7da042fc1 100644 --- a/balancer-js/src/modules/sor/pool-data/onChainData.ts +++ b/balancer-js/src/modules/sor/pool-data/onChainData.ts @@ -20,6 +20,8 @@ import { import { JsonFragment } from '@ethersproject/abi'; import { Multicaller } from '@/lib/utils/multiCaller'; import { isSameAddress } from '@/lib/utils'; +import _ from 'lodash'; +import { MulticallPool } from '@/modules/sor/pool-data/types'; export type Tokens = (SubgraphToken | PoolToken)[]; @@ -62,221 +64,27 @@ export async function getOnChainBalances< subgraphPoolsOriginal: GenericPool[], multiAddress: string, vaultAddress: string, - provider: Provider + provider: Provider, + chunkSize?: number ): Promise { if (subgraphPoolsOriginal.length === 0) return subgraphPoolsOriginal; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const abis: any = Object.values( - // Remove duplicate entries using their names - Object.fromEntries( - [ - ...(Vault__factory.abi as readonly JsonFragment[]), - ...(StaticATokenRateProvider__factory.abi as readonly JsonFragment[]), - ...(WeightedPool__factory.abi as readonly JsonFragment[]), - ...(StablePool__factory.abi as readonly JsonFragment[]), - ...(ConvergentCurvePool__factory.abi as readonly JsonFragment[]), - ...(LinearPool__factory.abi as readonly JsonFragment[]), - ...(ComposableStablePool__factory.abi as readonly JsonFragment[]), - ...(GyroEV2__factory.abi as readonly JsonFragment[]), - ].map((row) => [row.name, row]) - ) + const { multipools, poolsToBeCalledByChunk } = generateMultipools( + subgraphPoolsOriginal, + vaultAddress, + multiAddress, + provider, + chunkSize ); - const multicall = Multicall__factory.connect(multiAddress, provider); - - const multiPool = new Multicaller(multicall, abis); - - const supportedPoolTypes: string[] = Object.values(PoolType); - const subgraphPools: GenericPool[] = []; - subgraphPoolsOriginal.forEach((pool) => { - if ( - !supportedPoolTypes.includes(pool.poolType) || - pool.poolType === 'Managed' - ) { - const logger = Logger.getInstance(); - logger.warn(`Unknown pool type: ${pool.poolType} ${pool.id}`); - return; - } - - subgraphPools.push(pool); - - multiPool.call(`${pool.id}.poolTokens`, vaultAddress, 'getPoolTokens', [ - pool.id, - ]); - multiPool.call(`${pool.id}.totalSupply`, pool.address, 'totalSupply'); - - switch (pool.poolType) { - case 'LiquidityBootstrapping': - case 'Investment': - case 'Weighted': - multiPool.call( - `${pool.id}.swapFee`, - pool.address, - 'getSwapFeePercentage' - ); - multiPool.call( - `${pool.id}.weights`, - pool.address, - 'getNormalizedWeights' - ); - break; - case 'StablePhantom': - multiPool.call( - `${pool.id}.virtualSupply`, - pool.address, - 'getVirtualSupply' - ); - multiPool.call( - `${pool.id}.amp`, - pool.address, - 'getAmplificationParameter' - ); - multiPool.call( - `${pool.id}.swapFee`, - pool.address, - 'getSwapFeePercentage' - ); - break; - // MetaStable is the same as Stable for multicall purposes - case 'MetaStable': - case 'Stable': - multiPool.call( - `${pool.id}.amp`, - pool.address, - 'getAmplificationParameter' - ); - multiPool.call( - `${pool.id}.swapFee`, - pool.address, - 'getSwapFeePercentage' - ); - break; - case 'ComposableStable': - /** - * Returns the effective BPT supply. - * In other pools, this would be the same as `totalSupply`, but there are two key differences here: - * - this pool pre-mints BPT and holds it in the Vault as a token, and as such we need to subtract the Vault's - * balance to get the total "circulating supply". This is called the 'virtualSupply'. - * - the Pool owes debt to the Protocol in the form of unminted BPT, which will be minted immediately before the - * next join or exit. We need to take these into account since, even if they don't yet exist, they will - * effectively be included in any Pool operation that involves BPT. - * In the vast majority of cases, this function should be used instead of `totalSupply()`. - */ - multiPool.call( - `${pool.id}.actualSupply`, - pool.address, - 'getActualSupply' - ); - // MetaStable & StablePhantom is the same as Stable for multicall purposes - multiPool.call( - `${pool.id}.amp`, - pool.address, - 'getAmplificationParameter' - ); - multiPool.call( - `${pool.id}.swapFee`, - pool.address, - 'getSwapFeePercentage' - ); - break; - case 'Element': - multiPool.call(`${pool.id}.swapFee`, pool.address, 'percentFee'); - break; - case 'Gyro2': - case 'Gyro3': - multiPool.call(`${pool.id}.poolTokens`, vaultAddress, 'getPoolTokens', [ - pool.id, - ]); - multiPool.call(`${pool.id}.totalSupply`, pool.address, 'totalSupply'); - multiPool.call( - `${pool.id}.swapFee`, - pool.address, - 'getSwapFeePercentage' - ); - break; - case 'GyroE': - multiPool.call( - `${pool.id}.swapFee`, - pool.address, - 'getSwapFeePercentage' - ); - if (pool.poolTypeVersion && pool.poolTypeVersion === 2) { - multiPool.call( - `${pool.id}.tokenRates`, - pool.address, - 'getTokenRates' - ); - } - break; - default: - //Handling all Linear pools - if (pool.poolType.toString().includes('Linear')) { - multiPool.call( - `${pool.id}.virtualSupply`, - pool.address, - 'getVirtualSupply' - ); - multiPool.call( - `${pool.id}.swapFee`, - pool.address, - 'getSwapFeePercentage' - ); - multiPool.call(`${pool.id}.targets`, pool.address, 'getTargets'); - multiPool.call( - `${pool.id}.rate`, - pool.address, - 'getWrappedTokenRate' - ); - } - break; - } - }); - - let pools = {} as Record< - string, - { - amp?: string[]; - swapFee: string; - weights?: string[]; - targets?: string[]; - poolTokens: { - tokens: string[]; - balances: string[]; - }; - totalSupply: string; - virtualSupply?: string; - rate?: string; - actualSupply?: string; - tokenRates?: string[]; - } - >; - - try { - pools = (await multiPool.execute()) as Record< - string, - { - amp?: string[]; - swapFee: string; - weights?: string[]; - poolTokens: { - tokens: string[]; - balances: string[]; - }; - totalSupply: string; - virtualSupply?: string; - rate?: string; - actualSupply?: string; - tokenRates?: string[]; - } - >; - } catch (err) { - throw new Error(`Issue with multicall execution.`); - } + const { multicallPools, calledPools } = await executeChunks( + multipools, + poolsToBeCalledByChunk + ); const onChainPools: GenericPool[] = []; - Object.entries(pools).forEach(([poolId, onchainData], index) => { + Object.entries(multicallPools).forEach(([poolId, onchainData], index) => { try { const { poolTokens, @@ -289,10 +97,10 @@ export async function getOnChainBalances< } = onchainData; if ( - subgraphPools[index].poolType === 'Stable' || - subgraphPools[index].poolType === 'MetaStable' || - subgraphPools[index].poolType === 'StablePhantom' || - subgraphPools[index].poolType === 'ComposableStable' + calledPools[index].poolType === 'Stable' || + calledPools[index].poolType === 'MetaStable' || + calledPools[index].poolType === 'StablePhantom' || + calledPools[index].poolType === 'ComposableStable' ) { if (!onchainData.amp) { console.error(`Stable Pool Missing Amp: ${poolId}`); @@ -300,26 +108,26 @@ export async function getOnChainBalances< } else { // Need to scale amp by precision to match expected Subgraph scale // amp is stored with 3 decimals of precision - subgraphPools[index].amp = formatFixed(onchainData.amp[0], 3); + calledPools[index].amp = formatFixed(onchainData.amp[0], 3); } } - if (subgraphPools[index].poolType.includes('Linear')) { + if (calledPools[index].poolType.includes('Linear')) { if (!onchainData.targets) { console.error(`Linear Pool Missing Targets: ${poolId}`); return; } else { - subgraphPools[index].lowerTarget = formatFixed( + calledPools[index].lowerTarget = formatFixed( onchainData.targets[0], 18 ); - subgraphPools[index].upperTarget = formatFixed( + calledPools[index].upperTarget = formatFixed( onchainData.targets[1], 18 ); } - const wrappedIndex = subgraphPools[index].wrappedIndex; + const wrappedIndex = calledPools[index].wrappedIndex; if (wrappedIndex === undefined || onchainData.rate === undefined) { console.error( `Linear Pool Missing WrappedIndex or PriceRate: ${poolId}` @@ -327,17 +135,17 @@ export async function getOnChainBalances< return; } // Update priceRate of wrappedToken - subgraphPools[index].tokens[wrappedIndex].priceRate = formatFixed( + calledPools[index].tokens[wrappedIndex].priceRate = formatFixed( onchainData.rate, 18 ); } - if (subgraphPools[index].poolType !== 'FX') - subgraphPools[index].swapFee = formatFixed(swapFee, 18); + if (calledPools[index].poolType !== 'FX') + calledPools[index].swapFee = formatFixed(swapFee, 18); poolTokens.tokens.forEach((token, i) => { - const tokens = subgraphPools[index].tokens; + const tokens = calledPools[index].tokens; const T = tokens.find((t) => isSameAddress(t.address, token)); if (!T) throw `Pool Missing Expected Token: ${poolId} ${token}`; T.balance = formatFixed(poolTokens.balances[i], T.decimals); @@ -349,8 +157,8 @@ export async function getOnChainBalances< // Pools with pre minted BPT if ( - subgraphPools[index].poolType.includes('Linear') || - subgraphPools[index].poolType === 'StablePhantom' + calledPools[index].poolType.includes('Linear') || + calledPools[index].poolType === 'StablePhantom' ) { if (virtualSupply === undefined) { const logger = Logger.getInstance(); @@ -359,21 +167,21 @@ export async function getOnChainBalances< ); return; } - subgraphPools[index].totalShares = formatFixed(virtualSupply, 18); - } else if (subgraphPools[index].poolType === 'ComposableStable') { + calledPools[index].totalShares = formatFixed(virtualSupply, 18); + } else if (calledPools[index].poolType === 'ComposableStable') { if (actualSupply === undefined) { const logger = Logger.getInstance(); logger.warn(`ComposableStable missing Actual Supply: ${poolId}`); return; } - subgraphPools[index].totalShares = formatFixed(actualSupply, 18); + calledPools[index].totalShares = formatFixed(actualSupply, 18); } else { - subgraphPools[index].totalShares = formatFixed(totalSupply, 18); + calledPools[index].totalShares = formatFixed(totalSupply, 18); } if ( - subgraphPools[index].poolType === 'GyroE' && - subgraphPools[index].poolTypeVersion == 2 + calledPools[index].poolType === 'GyroE' && + calledPools[index].poolTypeVersion == 2 ) { if (!Array.isArray(tokenRates) || tokenRates.length !== 2) { console.error( @@ -381,15 +189,239 @@ export async function getOnChainBalances< ); return; } - subgraphPools[index].tokenRates = tokenRates.map((rate) => + calledPools[index].tokenRates = tokenRates.map((rate) => formatFixed(rate, 18) ); } - onChainPools.push(subgraphPools[index]); + onChainPools.push(calledPools[index]); } catch (err) { throw new Error(`Issue with pool onchain data: ${err}`); } }); return onChainPools; } + +const generateMultipools = ( + pools: GenericPool[], + vaultAddress: string, + multiAddress: string, + provider: Provider, + chunkSize?: number +): { + multipools: Multicaller[]; + poolsToBeCalledByChunk: GenericPool[][]; +} => { + const abis: JsonFragment[] = _.uniqBy( + [ + ...(Vault__factory.abi as readonly JsonFragment[]), + ...(StaticATokenRateProvider__factory.abi as readonly JsonFragment[]), + ...(WeightedPool__factory.abi as readonly JsonFragment[]), + ...(StablePool__factory.abi as readonly JsonFragment[]), + ...(ConvergentCurvePool__factory.abi as readonly JsonFragment[]), + ...(LinearPool__factory.abi as readonly JsonFragment[]), + ...(ComposableStablePool__factory.abi as readonly JsonFragment[]), + ...(GyroEV2__factory.abi as readonly JsonFragment[]), + ], + 'name' + ); + const supportedPoolTypes: string[] = Object.values(PoolType); + + if (!chunkSize) { + chunkSize = pools.length; + } + + const chunks: GenericPool[][] = []; + for (let i = 0; i < pools.length / chunkSize; i += 1) { + const chunk = pools.slice(i * chunkSize, (i + 1) * chunkSize); + chunks.push(chunk); + } + const poolsToBeCalledByChunk: GenericPool[][] = Array(chunks.length).fill([]); + const multicallers: Multicaller[] = chunks.map((poolsChunk, chunkIndex) => { + const multicall = Multicall__factory.connect(multiAddress, provider); + const multiPool = new Multicaller(multicall, abis); + poolsChunk.forEach((pool) => { + if ( + !supportedPoolTypes.includes(pool.poolType) || + pool.poolType === 'Managed' + ) { + const logger = Logger.getInstance(); + logger.warn(`Unknown pool type: ${pool.poolType} ${pool.id}`); + return; + } + + poolsToBeCalledByChunk[chunkIndex].push(pool); + + multiPool.call(`${pool.id}.poolTokens`, vaultAddress, 'getPoolTokens', [ + pool.id, + ]); + multiPool.call(`${pool.id}.totalSupply`, pool.address, 'totalSupply'); + + switch (pool.poolType) { + case 'LiquidityBootstrapping': + case 'Investment': + case 'Weighted': + multiPool.call( + `${pool.id}.swapFee`, + pool.address, + 'getSwapFeePercentage' + ); + multiPool.call( + `${pool.id}.weights`, + pool.address, + 'getNormalizedWeights' + ); + break; + case 'StablePhantom': + multiPool.call( + `${pool.id}.virtualSupply`, + pool.address, + 'getVirtualSupply' + ); + multiPool.call( + `${pool.id}.amp`, + pool.address, + 'getAmplificationParameter' + ); + multiPool.call( + `${pool.id}.swapFee`, + pool.address, + 'getSwapFeePercentage' + ); + break; + // MetaStable is the same as Stable for multicall purposes + case 'MetaStable': + case 'Stable': + multiPool.call( + `${pool.id}.amp`, + pool.address, + 'getAmplificationParameter' + ); + multiPool.call( + `${pool.id}.swapFee`, + pool.address, + 'getSwapFeePercentage' + ); + break; + case 'ComposableStable': + /** + * Returns the effective BPT supply. + * In other pools, this would be the same as `totalSupply`, but there are two key differences here: + * - this pool pre-mints BPT and holds it in the Vault as a token, and as such we need to subtract the Vault's + * balance to get the total "circulating supply". This is called the 'virtualSupply'. + * - the Pool owes debt to the Protocol in the form of unminted BPT, which will be minted immediately before the + * next join or exit. We need to take these into account since, even if they don't yet exist, they will + * effectively be included in any Pool operation that involves BPT. + * In the vast majority of cases, this function should be used instead of `totalSupply()`. + */ + multiPool.call( + `${pool.id}.actualSupply`, + pool.address, + 'getActualSupply' + ); + // MetaStable & StablePhantom is the same as Stable for multicall purposes + multiPool.call( + `${pool.id}.amp`, + pool.address, + 'getAmplificationParameter' + ); + multiPool.call( + `${pool.id}.swapFee`, + pool.address, + 'getSwapFeePercentage' + ); + break; + case 'Element': + multiPool.call(`${pool.id}.swapFee`, pool.address, 'percentFee'); + break; + case 'Gyro2': + case 'Gyro3': + multiPool.call( + `${pool.id}.poolTokens`, + vaultAddress, + 'getPoolTokens', + [pool.id] + ); + multiPool.call(`${pool.id}.totalSupply`, pool.address, 'totalSupply'); + multiPool.call( + `${pool.id}.swapFee`, + pool.address, + 'getSwapFeePercentage' + ); + break; + case 'GyroE': + multiPool.call( + `${pool.id}.swapFee`, + pool.address, + 'getSwapFeePercentage' + ); + if (pool.poolTypeVersion && pool.poolTypeVersion === 2) { + multiPool.call( + `${pool.id}.tokenRates`, + pool.address, + 'getTokenRates' + ); + } + break; + default: + //Handling all Linear pools + if (pool.poolType.toString().includes('Linear')) { + multiPool.call( + `${pool.id}.virtualSupply`, + pool.address, + 'getVirtualSupply' + ); + multiPool.call( + `${pool.id}.swapFee`, + pool.address, + 'getSwapFeePercentage' + ); + multiPool.call(`${pool.id}.targets`, pool.address, 'getTargets'); + multiPool.call( + `${pool.id}.rate`, + pool.address, + 'getWrappedTokenRate' + ); + } + break; + } + }); + return multiPool; + }); + + return { multipools: multicallers, poolsToBeCalledByChunk }; +}; + +const executeChunks = async ( + multipools: Multicaller[], + poolsToBeCalledByChunk: GenericPool[][] +): Promise<{ + multicallPools: Record; + calledPools: GenericPool[]; +}> => { + let calledPools: GenericPool[] = []; + const acceptedChunks: number[] = []; + const multicallPools = ( + (await Promise.all( + multipools.map(async (m, i) => { + try { + const records = await m.execute(); + acceptedChunks.push(i); + return records; + } catch (error) { + console.log(acceptedChunks); + console.warn(`Error in chunk ${i}, discarding chunk`); + return {}; + } + }) + )) as Record[] + ).reduce((acc, poolsRecord) => { + return { ...acc, ...poolsRecord }; + }, {}); + acceptedChunks + .sort() + .forEach( + (i) => (calledPools = [...calledPools, ...poolsToBeCalledByChunk[i]]) + ); + return { multicallPools, calledPools }; +}; diff --git a/balancer-js/src/modules/sor/pool-data/subgraphPoolDataService.ts b/balancer-js/src/modules/sor/pool-data/subgraphPoolDataService.ts index 4b94e8fac..a2cc54ff5 100644 --- a/balancer-js/src/modules/sor/pool-data/subgraphPoolDataService.ts +++ b/balancer-js/src/modules/sor/pool-data/subgraphPoolDataService.ts @@ -42,6 +42,7 @@ export function mapPools(pools: any[]): SubgraphPoolBase[] { export class SubgraphPoolDataService implements PoolDataService { private readonly defaultArgs: GraphQLArgs; + constructor( private readonly client: SubgraphClient, private readonly provider: Provider, @@ -70,7 +71,10 @@ export class SubgraphPoolDataService implements PoolDataService { * @param queryArgs * @returns SubgraphPoolBase[] */ - async getPools(queryArgs?: GraphQLArgs): Promise { + async getPools( + queryArgs?: GraphQLArgs, + chunkSize?: number + ): Promise { const pools = await this.getSubgraphPools(queryArgs); const filteredPools = pools.filter((p) => { @@ -93,7 +97,8 @@ export class SubgraphPoolDataService implements PoolDataService { mapped, this.network.addresses.contracts.multicall, this.network.addresses.contracts.vault, - this.provider + this.provider, + chunkSize ); console.timeEnd(`fetching on-chain balances for ${mapped.length} pools`); diff --git a/balancer-js/src/modules/sor/pool-data/types.ts b/balancer-js/src/modules/sor/pool-data/types.ts new file mode 100644 index 000000000..9e022c8bf --- /dev/null +++ b/balancer-js/src/modules/sor/pool-data/types.ts @@ -0,0 +1,17 @@ +import { BigNumberish } from "@ethersproject/bignumber"; + +export type MulticallPool = { + amp?: string[]; + swapFee: string; + weights?: string[]; + poolTokens: { + tokens: string[]; + balances: string[]; + }; + totalSupply: string; + virtualSupply?: string; + rate?: string; + actualSupply?: string; + tokenRates?: string[]; + targets: BigNumberish[]; +}; diff --git a/balancer-js/src/modules/swaps/swaps.module.ts b/balancer-js/src/modules/swaps/swaps.module.ts index 82c131816..b63135b45 100644 --- a/balancer-js/src/modules/swaps/swaps.module.ts +++ b/balancer-js/src/modules/swaps/swaps.module.ts @@ -212,7 +212,6 @@ export class Swaps { gasPrice: BigNumber.from(opts.gasPrice), maxPools: opts.maxPools, }); - const tx = this.buildSwap({ userAddress: sender, // sender account recipient, // recipient account @@ -288,8 +287,11 @@ export class Swaps { * @returns Boolean indicating whether pools data was fetched correctly (true) or not (false). */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - async fetchPools(queryArgs?: GraphQLArgs): Promise { - return this.sor.fetchPools(queryArgs); + async fetchPools( + queryArgs?: GraphQLArgs, + chunkSize?: number + ): Promise { + return this.sor.fetchPools(queryArgs, chunkSize); } public getPools(): SubgraphPoolBase[] { From 8bafbe35d1a75c44fd98f35a9a15a78147cf642b Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem De Liz Date: Fri, 21 Jul 2023 16:21:38 -0300 Subject: [PATCH 2/4] fixing Lint error; removing "discard chunk" functionality; --- balancer-js/examples/swaps/swap.ts | 2 +- .../src/modules/sor/pool-data/onChainData.ts | 95 +++++++------------ .../src/modules/sor/pool-data/types.ts | 2 +- 3 files changed, 38 insertions(+), 61 deletions(-) diff --git a/balancer-js/examples/swaps/swap.ts b/balancer-js/examples/swaps/swap.ts index 3bbd37054..44c27aa86 100644 --- a/balancer-js/examples/swaps/swap.ts +++ b/balancer-js/examples/swaps/swap.ts @@ -28,7 +28,7 @@ async function swap() { // Finding a trading route rely on on-chain data. // fetchPools will fetch the current data from the subgraph. // Let's fetch just 5 pools with highest liquidity of tokenOut. - await swaps.fetchPools(undefined, 30); + await swaps.fetchPools(undefined, 200); // Set exectution deadline to 60 seconds from now const deadline = String(Math.ceil(Date.now() / 1000) + 60); diff --git a/balancer-js/src/modules/sor/pool-data/onChainData.ts b/balancer-js/src/modules/sor/pool-data/onChainData.ts index 7da042fc1..322412afa 100644 --- a/balancer-js/src/modules/sor/pool-data/onChainData.ts +++ b/balancer-js/src/modules/sor/pool-data/onChainData.ts @@ -69,7 +69,7 @@ export async function getOnChainBalances< ): Promise { if (subgraphPoolsOriginal.length === 0) return subgraphPoolsOriginal; - const { multipools, poolsToBeCalledByChunk } = generateMultipools( + const { multipools, poolsToBeCalled } = generateMultipools( subgraphPoolsOriginal, vaultAddress, multiAddress, @@ -77,10 +77,7 @@ export async function getOnChainBalances< chunkSize ); - const { multicallPools, calledPools } = await executeChunks( - multipools, - poolsToBeCalledByChunk - ); + const multicallPools = await executeChunks(multipools); const onChainPools: GenericPool[] = []; @@ -97,10 +94,10 @@ export async function getOnChainBalances< } = onchainData; if ( - calledPools[index].poolType === 'Stable' || - calledPools[index].poolType === 'MetaStable' || - calledPools[index].poolType === 'StablePhantom' || - calledPools[index].poolType === 'ComposableStable' + poolsToBeCalled[index].poolType === 'Stable' || + poolsToBeCalled[index].poolType === 'MetaStable' || + poolsToBeCalled[index].poolType === 'StablePhantom' || + poolsToBeCalled[index].poolType === 'ComposableStable' ) { if (!onchainData.amp) { console.error(`Stable Pool Missing Amp: ${poolId}`); @@ -108,26 +105,26 @@ export async function getOnChainBalances< } else { // Need to scale amp by precision to match expected Subgraph scale // amp is stored with 3 decimals of precision - calledPools[index].amp = formatFixed(onchainData.amp[0], 3); + poolsToBeCalled[index].amp = formatFixed(onchainData.amp[0], 3); } } - if (calledPools[index].poolType.includes('Linear')) { + if (poolsToBeCalled[index].poolType.includes('Linear')) { if (!onchainData.targets) { console.error(`Linear Pool Missing Targets: ${poolId}`); return; } else { - calledPools[index].lowerTarget = formatFixed( + poolsToBeCalled[index].lowerTarget = formatFixed( onchainData.targets[0], 18 ); - calledPools[index].upperTarget = formatFixed( + poolsToBeCalled[index].upperTarget = formatFixed( onchainData.targets[1], 18 ); } - const wrappedIndex = calledPools[index].wrappedIndex; + const wrappedIndex = poolsToBeCalled[index].wrappedIndex; if (wrappedIndex === undefined || onchainData.rate === undefined) { console.error( `Linear Pool Missing WrappedIndex or PriceRate: ${poolId}` @@ -135,17 +132,17 @@ export async function getOnChainBalances< return; } // Update priceRate of wrappedToken - calledPools[index].tokens[wrappedIndex].priceRate = formatFixed( + poolsToBeCalled[index].tokens[wrappedIndex].priceRate = formatFixed( onchainData.rate, 18 ); } - if (calledPools[index].poolType !== 'FX') - calledPools[index].swapFee = formatFixed(swapFee, 18); + if (poolsToBeCalled[index].poolType !== 'FX') + poolsToBeCalled[index].swapFee = formatFixed(swapFee, 18); poolTokens.tokens.forEach((token, i) => { - const tokens = calledPools[index].tokens; + const tokens = poolsToBeCalled[index].tokens; const T = tokens.find((t) => isSameAddress(t.address, token)); if (!T) throw `Pool Missing Expected Token: ${poolId} ${token}`; T.balance = formatFixed(poolTokens.balances[i], T.decimals); @@ -157,8 +154,8 @@ export async function getOnChainBalances< // Pools with pre minted BPT if ( - calledPools[index].poolType.includes('Linear') || - calledPools[index].poolType === 'StablePhantom' + poolsToBeCalled[index].poolType.includes('Linear') || + poolsToBeCalled[index].poolType === 'StablePhantom' ) { if (virtualSupply === undefined) { const logger = Logger.getInstance(); @@ -167,21 +164,21 @@ export async function getOnChainBalances< ); return; } - calledPools[index].totalShares = formatFixed(virtualSupply, 18); - } else if (calledPools[index].poolType === 'ComposableStable') { + poolsToBeCalled[index].totalShares = formatFixed(virtualSupply, 18); + } else if (poolsToBeCalled[index].poolType === 'ComposableStable') { if (actualSupply === undefined) { const logger = Logger.getInstance(); logger.warn(`ComposableStable missing Actual Supply: ${poolId}`); return; } - calledPools[index].totalShares = formatFixed(actualSupply, 18); + poolsToBeCalled[index].totalShares = formatFixed(actualSupply, 18); } else { - calledPools[index].totalShares = formatFixed(totalSupply, 18); + poolsToBeCalled[index].totalShares = formatFixed(totalSupply, 18); } if ( - calledPools[index].poolType === 'GyroE' && - calledPools[index].poolTypeVersion == 2 + poolsToBeCalled[index].poolType === 'GyroE' && + poolsToBeCalled[index].poolTypeVersion == 2 ) { if (!Array.isArray(tokenRates) || tokenRates.length !== 2) { console.error( @@ -189,12 +186,12 @@ export async function getOnChainBalances< ); return; } - calledPools[index].tokenRates = tokenRates.map((rate) => + poolsToBeCalled[index].tokenRates = tokenRates.map((rate) => formatFixed(rate, 18) ); } - onChainPools.push(calledPools[index]); + onChainPools.push(poolsToBeCalled[index]); } catch (err) { throw new Error(`Issue with pool onchain data: ${err}`); } @@ -210,7 +207,7 @@ const generateMultipools = ( chunkSize?: number ): { multipools: Multicaller[]; - poolsToBeCalledByChunk: GenericPool[][]; + poolsToBeCalled: GenericPool[]; } => { const abis: JsonFragment[] = _.uniqBy( [ @@ -236,7 +233,7 @@ const generateMultipools = ( const chunk = pools.slice(i * chunkSize, (i + 1) * chunkSize); chunks.push(chunk); } - const poolsToBeCalledByChunk: GenericPool[][] = Array(chunks.length).fill([]); + const poolsToBeCalled: GenericPool[] = []; const multicallers: Multicaller[] = chunks.map((poolsChunk, chunkIndex) => { const multicall = Multicall__factory.connect(multiAddress, provider); const multiPool = new Multicaller(multicall, abis); @@ -250,7 +247,7 @@ const generateMultipools = ( return; } - poolsToBeCalledByChunk[chunkIndex].push(pool); + poolsToBeCalled.push(pool); multiPool.call(`${pool.id}.poolTokens`, vaultAddress, 'getPoolTokens', [ pool.id, @@ -389,39 +386,19 @@ const generateMultipools = ( return multiPool; }); - return { multipools: multicallers, poolsToBeCalledByChunk }; + return { multipools: multicallers, poolsToBeCalled }; }; const executeChunks = async ( - multipools: Multicaller[], - poolsToBeCalledByChunk: GenericPool[][] -): Promise<{ - multicallPools: Record; - calledPools: GenericPool[]; -}> => { - let calledPools: GenericPool[] = []; - const acceptedChunks: number[] = []; + multipools: Multicaller[] +): Promise> => { const multicallPools = ( - (await Promise.all( - multipools.map(async (m, i) => { - try { - const records = await m.execute(); - acceptedChunks.push(i); - return records; - } catch (error) { - console.log(acceptedChunks); - console.warn(`Error in chunk ${i}, discarding chunk`); - return {}; - } - }) - )) as Record[] + (await Promise.all(multipools.map((m, i) => m.execute()))) as Record< + string, + MulticallPool + >[] ).reduce((acc, poolsRecord) => { return { ...acc, ...poolsRecord }; }, {}); - acceptedChunks - .sort() - .forEach( - (i) => (calledPools = [...calledPools, ...poolsToBeCalledByChunk[i]]) - ); - return { multicallPools, calledPools }; + return multicallPools; }; diff --git a/balancer-js/src/modules/sor/pool-data/types.ts b/balancer-js/src/modules/sor/pool-data/types.ts index 9e022c8bf..0965c94a3 100644 --- a/balancer-js/src/modules/sor/pool-data/types.ts +++ b/balancer-js/src/modules/sor/pool-data/types.ts @@ -1,4 +1,4 @@ -import { BigNumberish } from "@ethersproject/bignumber"; +import { BigNumberish } from '@ethersproject/bignumber'; export type MulticallPool = { amp?: string[]; From 2f708c991905debf91784384b0789f233eb2c71b Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem De Liz Date: Fri, 21 Jul 2023 16:50:39 -0300 Subject: [PATCH 3/4] Fixing lint; --- balancer-js/src/modules/sor/pool-data/onChainData.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/balancer-js/src/modules/sor/pool-data/onChainData.ts b/balancer-js/src/modules/sor/pool-data/onChainData.ts index 322412afa..9d1492e13 100644 --- a/balancer-js/src/modules/sor/pool-data/onChainData.ts +++ b/balancer-js/src/modules/sor/pool-data/onChainData.ts @@ -234,7 +234,7 @@ const generateMultipools = ( chunks.push(chunk); } const poolsToBeCalled: GenericPool[] = []; - const multicallers: Multicaller[] = chunks.map((poolsChunk, chunkIndex) => { + const multicallers: Multicaller[] = chunks.map((poolsChunk) => { const multicall = Multicall__factory.connect(multiAddress, provider); const multiPool = new Multicaller(multicall, abis); poolsChunk.forEach((pool) => { @@ -389,11 +389,11 @@ const generateMultipools = ( return { multipools: multicallers, poolsToBeCalled }; }; -const executeChunks = async ( +const executeChunks = async( multipools: Multicaller[] ): Promise> => { const multicallPools = ( - (await Promise.all(multipools.map((m, i) => m.execute()))) as Record< + (await Promise.all(multipools.map((m) => m.execute()))) as Record< string, MulticallPool >[] From 513f08f720718271967e9fe9b5c87b7c34034721 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Abou Hatem De Liz Date: Mon, 24 Jul 2023 10:25:04 -0300 Subject: [PATCH 4/4] Fixing Lint --- balancer-js/src/modules/sor/pool-data/onChainData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/balancer-js/src/modules/sor/pool-data/onChainData.ts b/balancer-js/src/modules/sor/pool-data/onChainData.ts index 9d1492e13..ac8f15c94 100644 --- a/balancer-js/src/modules/sor/pool-data/onChainData.ts +++ b/balancer-js/src/modules/sor/pool-data/onChainData.ts @@ -389,7 +389,7 @@ const generateMultipools = ( return { multipools: multicallers, poolsToBeCalled }; }; -const executeChunks = async( +const executeChunks = async ( multipools: Multicaller[] ): Promise> => { const multicallPools = (