diff --git a/balancer-js/examples/data/onchain-multicall3.ts b/balancer-js/examples/data/onchain-multicall3.ts new file mode 100644 index 000000000..59aff46cd --- /dev/null +++ b/balancer-js/examples/data/onchain-multicall3.ts @@ -0,0 +1,102 @@ +import { + PoolsSubgraphRepository, + SubgraphPoolBase, + Network, + BALANCER_NETWORK_CONFIG, + getOnChainBalances as getOnChainBalances3, +} from '@balancer-labs/sdk'; +import { JsonRpcProvider } from '@ethersproject/providers'; +// import _ from 'lodash'; + +// Importing legacy multicall fetcher for comparison +// import { getOnChainBalances } from '@/modules/sor/pool-data/onChainData'; + +const network = Network.POLYGON; + +const pools = new PoolsSubgraphRepository({ + url: BALANCER_NETWORK_CONFIG[network].urls.subgraph, + chainId: network, + query: { + args: { + first: 10, + skip: 0, + orderBy: 'totalLiquidity', + orderDirection: 'desc', + where: { + poolType: { + eq: 'GyroE', + }, + }, + }, + attrs: {}, + }, +}); + +const providers: Record = { + [Network.MAINNET]: new JsonRpcProvider('https://rpc.ankr.com/eth'), + [Network.POLYGON]: new JsonRpcProvider('https://rpc.ankr.com/polygon'), + [Network.ARBITRUM]: new JsonRpcProvider('https://rpc.ankr.com/arbitrum'), + [Network.OPTIMISM]: new JsonRpcProvider('https://rpc.ankr.com/optimism'), + [Network.BASE]: new JsonRpcProvider('https://rpc.ankr.com/base'), + [Network.FANTOM]: new JsonRpcProvider('https://rpc.ankr.com/fantom'), + [Network.ZKEVM]: new JsonRpcProvider('https://rpc.ankr.com/polygon_zkevm'), +}; + +const provider = providers[network]; + +/** + * Recursively finds differences between two objects. Use to compare onchain data from 2 different fetchers. + */ +// function diffObjects( +// object1: any, +// object2: any, +// path = '' +// ): any { +// const allKeys = _.union(Object.keys(object1), Object.keys(object2)); + +// const differences = []; + +// for (const key of allKeys) { +// const newPath = path ? `${path}.${key}` : key; + +// if (_.isObject(object1[key]) && _.isObject(object2[key])) { +// differences.push(...diffObjects(object1[key], object2[key], newPath)); +// } else if (!_.isEqual(object1[key], object2[key])) { +// differences.push({ +// path: newPath, +// value1: object1[key], +// value2: object2[key], +// }); +// } +// } + +// return differences; +// } + +async function main() { + const subgraph = (await pools.fetch()) as SubgraphPoolBase[]; + const onchain3 = await getOnChainBalances3( + subgraph, + '', + '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + provider + ); + console.log(onchain3.length); + console.log(JSON.stringify(onchain3, null, 2)); + // const onchain = await getOnChainBalances(subgraph, '0xeefba1e63905ef1d7acba5a8513c70307c1ce441', '0xBA12222222228d8Ba445958a75a0704d566BF2C8', provider); + // console.log(onchain.length) + // for(const i in subgraph) { + // const one = onchain3.find((x) => x.id === subgraph[i].id) + // const two = onchain.find((x) => x.id === subgraph[i].id) + // console.log('Pool', subgraph[i].id) + // if (!two) { + // console.log('two missing') + // continue + // } + // console.log(JSON.stringify(findNestedValueDifferences(one, two), null, 2)); + // } +} + +main(); + +// yarn example ./debug/onchain-multicall3.ts diff --git a/balancer-js/examples/swaps/advanced.ts b/balancer-js/examples/swaps/advanced.ts index 7c37101cc..f0b80fb0a 100644 --- a/balancer-js/examples/swaps/advanced.ts +++ b/balancer-js/examples/swaps/advanced.ts @@ -6,6 +6,7 @@ * Run command: * yarn example ./examples/swaps/advanced.ts */ +import { FORK_NODES } from '@/test/lib/utils'; import { BalancerSDK, Network, @@ -119,12 +120,12 @@ async function getAndProcessSwaps( } async function swapExample() { - const network = Network.POLYGON; - const rpcUrl = 'https://rpc.ankr.com/polygon'; - const tokenIn = '0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4'; // stMatic - const tokenOut = AddressZero; // Matic + const network = Network.GNOSIS; + const rpcUrl = FORK_NODES[network]; + const tokenIn = '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d'; + const tokenOut = '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1'; const swapType = SwapTypes.SwapExactIn; - const amount = parseFixed('20', 18); + const amount = parseFixed('20000', 18); // Currently Relayer only suitable for ExactIn and non-eth swaps const canUseJoinExitPaths = canUseJoinExit(swapType, tokenIn, tokenOut); diff --git a/balancer-js/package.json b/balancer-js/package.json index 64fe16b3f..73b4673c5 100644 --- a/balancer-js/package.json +++ b/balancer-js/package.json @@ -1,6 +1,6 @@ { "name": "@balancer-labs/sdk", - "version": "1.1.4", + "version": "1.1.5", "description": "JavaScript SDK for interacting with the Balancer Protocol V2", "license": "GPL-3.0-only", "homepage": "https://github.com/balancer-labs/balancer-sdk#readme", diff --git a/balancer-js/src/lib/abi/Multicall3.json b/balancer-js/src/lib/abi/Multicall3.json new file mode 100644 index 000000000..d9c5855e7 --- /dev/null +++ b/balancer-js/src/lib/abi/Multicall3.json @@ -0,0 +1,440 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "returnData", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call3[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowFailure", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call3Value[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate3Value", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "blockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getBasefee", + "outputs": [ + { + "internalType": "uint256", + "name": "basefee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getBlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "chainid", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockCoinbase", + "outputs": [ + { + "internalType": "address", + "name": "coinbase", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockDifficulty", + "outputs": [ + { + "internalType": "uint256", + "name": "difficulty", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockGasLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "gaslimit", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "getEthBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastBlockHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "requireSuccess", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryAggregate", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "requireSuccess", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryBlockAndAggregate", + "outputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "blockHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bool", + "name": "success", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "internalType": "struct Multicall3.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "payable", + "type": "function" + } +] \ No newline at end of file diff --git a/balancer-js/src/lib/constants/config.ts b/balancer-js/src/lib/constants/config.ts index b0d77fca5..4357dba75 100644 --- a/balancer-js/src/lib/constants/config.ts +++ b/balancer-js/src/lib/constants/config.ts @@ -64,7 +64,6 @@ export const BALANCER_NETWORK_CONFIG: Record = { '0x0afbd58beca09545e4fb67772faf3858e610bcd0', '0xf22ff21e17157340575158ad7394e068048dd98b', '0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa', - '0xe0e8ac08de6708603cfd3d23b613d2f80e3b7afb', ], sorConnectingTokens: [ { @@ -302,6 +301,9 @@ export const BALANCER_NETWORK_CONFIG: Record = { address: '0x6C76971f98945AE98dD7d4DFcA8711ebea946eA6', }, ], + sorTriPathMidPoolIds: [ + '0xeb30c85cc528537f5350cf5684ce6a4538e13394000200000000000000000059', // 3POOL_BPT/wstETH + ], }, [Network.FANTOM]: { chainId: Network.FANTOM, //250 diff --git a/balancer-js/src/lib/utils/multiCaller3.ts b/balancer-js/src/lib/utils/multiCaller3.ts new file mode 100644 index 000000000..db4c87f68 --- /dev/null +++ b/balancer-js/src/lib/utils/multiCaller3.ts @@ -0,0 +1,104 @@ +import { set } from 'lodash'; +import { Fragment, JsonFragment, Interface, Result } from '@ethersproject/abi'; +import { CallOverrides } from '@ethersproject/contracts'; +import { Multicall3, Multicall3__factory } from '@/contracts'; +import { Provider } from '@ethersproject/providers'; + +export class Multicaller3 { + private interface: Interface; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private calls: [string, string, any][] = []; + private paths: string[] = []; + address = '0xcA11bde05977b3631167028862bE2a173976CA11'; + multicall: Multicall3; + + constructor( + abi: string | Array, + provider: Provider, + private options: CallOverrides = {} + ) { + this.interface = new Interface(abi); + this.multicall = Multicall3__factory.connect(this.address, provider); + } + + call( + path: string, + address: string, + functionName: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params?: any[] + ): Multicaller3 { + this.calls.push([address, functionName, params]); + this.paths.push(path); + return this; + } + + async execute( + from: Record = {}, + batchSize = 1024 // Define the number of function calls in each batch + ): Promise> { + const obj = from; + const results = await this.executeMulticall(batchSize); + results.forEach((result, i) => + set(obj, this.paths[i], result.length > 1 ? result : result[0]) + ); + this.calls = []; + this.paths = []; + return obj; + } + + private async executeMulticall(batchSize: number): Promise { + const numBatches = Math.ceil(this.calls.length / batchSize); + const results: Result[] = []; + + const batchPromises = []; + + for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) { + const batchCalls = this.calls.slice( + batchIndex * batchSize, + (batchIndex + 1) * batchSize + ); + + const batchRequests = batchCalls.map( + ([address, functionName, params]) => ({ + target: address, + allowFailure: true, + callData: this.interface.encodeFunctionData(functionName, params), + }) + ); + + batchPromises.push( + this.multicall.callStatic.aggregate3(batchRequests, this.options) + ); + } + + const batchResults = await Promise.all(batchPromises); + + batchResults.forEach((res, batchIndex) => { + const offset = batchIndex * batchSize; + + for (let i = 0; i < res.length; i++) { + const callIndex = offset + i; + const { success, returnData } = res[i]; + + if (success) { + try { + const result = this.interface.decodeFunctionResult( + this.calls[callIndex][1], + returnData + ); + results[callIndex] = result; + } catch (e) { + console.error('Multicall error', this.paths[callIndex]); + results[callIndex] = []; + } + } else { + console.error('Failed request in multicall', this.paths[callIndex]); + results[callIndex] = []; + } + } + }); + + return results; + } +} diff --git a/balancer-js/src/modules/data/index.ts b/balancer-js/src/modules/data/index.ts index c7b83dae0..246873ab4 100644 --- a/balancer-js/src/modules/data/index.ts +++ b/balancer-js/src/modules/data/index.ts @@ -53,7 +53,7 @@ import { Contracts } from '../contracts/contracts.module'; export class Data implements BalancerDataRepositories { pools; - poolsForSor; + poolsForSimulations; poolsOnChain; yesterdaysPools; poolShares; @@ -83,7 +83,8 @@ export class Data implements BalancerDataRepositories { query: subgraphQuery, }); - this.poolsForSor = new SubgraphPoolDataService( + // Used for VaultModel and Simulations + this.poolsForSimulations = new SubgraphPoolDataService( createSubgraphClient(networkConfig.urls.subgraph), provider, networkConfig, diff --git a/balancer-js/src/modules/data/liquidity-gauges/provider.ts b/balancer-js/src/modules/data/liquidity-gauges/provider.ts index 126b01559..3ef53e75b 100644 --- a/balancer-js/src/modules/data/liquidity-gauges/provider.ts +++ b/balancer-js/src/modules/data/liquidity-gauges/provider.ts @@ -69,15 +69,15 @@ export class LiquidityGaugeSubgraphRPCProvider } else { // Filter out gauges that are not from the factory supporting inflation rate // Safe to remove this once all gauges are migrated to the new factory - const newFactories = [ - '0x22625eedd92c81a219a83e1dc48f88d54786b017', // Polygon - '0x6817149cb753bf529565b4d023d7507ed2ff4bc0', // Arbitrum - '0x83e443ef4f9963c77bd860f94500075556668cb8', // Gnosis - '0x2498a2b0d6462d2260eac50ae1c3e03f4829ba95', // zkEVM + const oldFactories = [ + '0x3b8ca519122cdd8efb272b0d3085453404b25bd0', // Polygon + '0xb08e16cfc07c684daa2f93c70323badb2a6cbfd2', // Arbitrum + '0x2e96068b3d5b5bae3d7515da4a1d2e52d08a2647', // Optimism + '0x809b79b53f18e9bc08a961ed4678b901ac93213a', // Gnosis ]; const childGaugeAddresses = gauges - .filter((g) => newFactories.includes(g.factory.id.toLowerCase())) + .filter((g) => !oldFactories.includes(g.factory.id.toLowerCase())) .map((g) => g.id); if (childGaugeAddresses.length > 0) { diff --git a/balancer-js/src/modules/data/pool/index.ts b/balancer-js/src/modules/data/pool/index.ts index 89d24baf0..9fa9b327b 100644 --- a/balancer-js/src/modules/data/pool/index.ts +++ b/balancer-js/src/modules/data/pool/index.ts @@ -4,3 +4,4 @@ export * from './fallback'; export * from './static'; export * from './subgraph'; export * from './subgraphOnChain'; +export * from './onchain-data'; diff --git a/balancer-js/src/modules/data/pool/onchain-data.ts b/balancer-js/src/modules/data/pool/onchain-data.ts new file mode 100644 index 000000000..cd168be7c --- /dev/null +++ b/balancer-js/src/modules/data/pool/onchain-data.ts @@ -0,0 +1,232 @@ +import { Multicaller3 } from '@/lib/utils/multiCaller3'; +import { SubgraphPoolBase } from '@/.'; +import { Provider } from '@ethersproject/providers'; +import { formatFixed } from '@ethersproject/bignumber'; +import { SubgraphToken } from '@balancer-labs/sor'; + +const abi = [ + 'function getSwapFeePercentage() view returns (uint256)', + 'function percentFee() view returns (uint256)', + 'function protocolPercentFee() view returns (uint256)', + 'function getNormalizedWeights() view returns (uint256[])', + 'function totalSupply() view returns (uint256)', + 'function getVirtualSupply() view returns (uint256)', + 'function getActualSupply() view returns (uint256)', + 'function getTargets() view returns (uint256 lowerTarget, uint256 upperTarget)', + 'function getTokenRates() view returns (uint256, uint256)', + 'function getWrappedTokenRate() view returns (uint256)', + 'function getAmplificationParameter() view returns (uint256 value, bool isUpdating, uint256 precision)', + 'function getPausedState() view returns (bool)', + 'function inRecoveryMode() view returns (bool)', + 'function getRate() view returns (uint256)', + 'function getScalingFactors() view returns (uint256[] memory)', // do we need this here? + 'function getPoolTokens(bytes32) view returns (address[], uint256[])', +]; + +const getTotalSupplyFn = (poolType: string) => { + if (poolType.includes('Linear') || ['StablePhantom'].includes(poolType)) { + return 'getVirtualSupply'; + } else if (poolType === 'ComposableStable') { + return 'getActualSupply'; + } else { + return 'totalSupply'; + } +}; + +const getSwapFeeFn = (poolType: string) => { + if (poolType === 'Element') { + return 'percentFee'; + } else if (poolType === 'FX') { + return 'protocolPercentFee'; + } else { + return 'getSwapFeePercentage'; + } +}; + +interface OnchainData { + poolTokens: [string[], string[]]; + totalShares: string; + swapFee: string; + isPaused?: boolean; + inRecoveryMode?: boolean; + rate?: string; + scalingFactors?: string[]; + weights?: string[]; + targets?: [string, string]; + wrappedTokenRate?: string; + amp?: [string, boolean, string]; + tokenRates?: [string, string]; +} + +const defaultCalls = ( + id: string, + address: string, + vaultAddress: string, + poolType: string, + multicaller: Multicaller3 +) => { + multicaller.call(`${id}.poolTokens`, vaultAddress, 'getPoolTokens', [id]); + multicaller.call(`${id}.totalShares`, address, getTotalSupplyFn(poolType)); + multicaller.call(`${id}.swapFee`, address, getSwapFeeFn(poolType)); + // Following where added to the pools query contract, however legacy onchain data didn't have them. + // multicaller.call(`${id}.isPaused`, address, 'getPausedState'); + // multicaller.call(`${id}.inRecoveryMode`, address, 'inRecoveryMode'); + // multicaller.call(`${id}.rate`, address, 'getRate'); + // multicaller.call(`${id}.scalingFactors`, address, 'getScalingFactors'); +}; + +const weightedCalls = ( + id: string, + address: string, + multicaller: Multicaller3 +) => { + multicaller.call(`${id}.weights`, address, 'getNormalizedWeights'); +}; + +const linearCalls = ( + id: string, + address: string, + multicaller: Multicaller3 +) => { + multicaller.call(`${id}.targets`, address, 'getTargets'); + multicaller.call(`${id}.wrappedTokenRate`, address, 'getWrappedTokenRate'); +}; + +const stableCalls = ( + id: string, + address: string, + multicaller: Multicaller3 +) => { + multicaller.call(`${id}.amp`, address, 'getAmplificationParameter'); +}; + +const gyroECalls = (id: string, address: string, multicaller: Multicaller3) => { + multicaller.call(`${id}.tokenRates`, address, 'getTokenRates'); +}; + +const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { + const do_nothing = () => ({}); + switch (poolType) { + case 'Weighted': + case 'LiquidityBootstrapping': + case 'Investment': + return weightedCalls; + case 'Stable': + case 'StablePhantom': + case 'MetaStable': + case 'ComposableStable': + return stableCalls; + case 'GyroE': + if (poolTypeVersion === 2) { + return gyroECalls; + } else { + return do_nothing; + } + case 'AaveLinear': + if (poolTypeVersion === 1) { + return linearCalls; + } else { + return do_nothing; + } + default: + return do_nothing; + } +}; + +const merge = (pool: SubgraphPoolBase, result: OnchainData) => ({ + ...pool, + tokens: pool.tokens.map((token) => { + const idx = result.poolTokens[0] + .map((t) => t.toLowerCase()) + .indexOf(token.address); + const wrappedToken = + pool.wrappedIndex && pool.tokensList[pool.wrappedIndex]; + return { + ...token, + balance: formatFixed(result.poolTokens[1][idx], token.decimals || 18), + weight: + (result.weights && formatFixed(result.weights[idx], 18)) || + token.weight, + priceRate: + (result.wrappedTokenRate && + wrappedToken && + wrappedToken.toLowerCase() === token.address.toLowerCase() && + formatFixed(result.wrappedTokenRate, 18)) || + token.priceRate, + } as SubgraphToken; + }), + totalShares: result.totalShares + ? formatFixed(result.totalShares, 18) + : pool.totalShares, + swapFee: formatFixed(result.swapFee, 18), + amp: + (result.amp && + result.amp[0] && + formatFixed(result.amp[0], String(result.amp[2]).length - 1)) || + pool.amp, + lowerTarget: + (result.targets && formatFixed(result.targets[0], 18)) || pool.lowerTarget, + upperTarget: + (result.targets && formatFixed(result.targets[1], 18)) || pool.upperTarget, + tokenRates: + (result.tokenRates && + result.tokenRates.map((rate) => formatFixed(rate, 18))) || + pool.tokenRates, + // rate: result.rate, + // isPaused: result.isPaused, + // inRecoveryMode: result.inRecoveryMode, + // scalingFactors: result.scalingFactors, +}); + +export const fetchOnChainPoolData = async ( + pools: { + id: string; + address: string; + poolType: string; + poolTypeVersion?: number; + }[], + vaultAddress: string, + provider: Provider +): Promise<{ [id: string]: OnchainData }> => { + if (pools.length === 0) { + return {}; + } + + const multicaller = new Multicaller3(abi, provider); + + pools.forEach(({ id, address, poolType, poolTypeVersion }) => { + defaultCalls(id, address, vaultAddress, poolType, multicaller); + poolTypeCalls(poolType, poolTypeVersion)(id, address, multicaller); + }); + + // ZkEVM needs a smaller batch size + const results = (await multicaller.execute({}, 128)) as { + [id: string]: OnchainData; + }; + + return results; +}; + +export async function getOnChainBalances( + subgraphPoolsOriginal: SubgraphPoolBase[], + _multiAddress: string, + vaultAddress: string, + provider: Provider +): Promise { + if (subgraphPoolsOriginal.length === 0) return subgraphPoolsOriginal; + + const poolsWithOnchainData: SubgraphPoolBase[] = []; + + const onchainData = (await fetchOnChainPoolData( + subgraphPoolsOriginal, + vaultAddress, + provider + )) as { [id: string]: OnchainData }; + + subgraphPoolsOriginal.forEach((pool) => { + const data = onchainData[pool.id]; + poolsWithOnchainData.push(merge(pool, data)); + }); + + return poolsWithOnchainData; +} diff --git a/balancer-js/src/modules/data/pool/subgraph.ts b/balancer-js/src/modules/data/pool/subgraph.ts index cfc963b7a..f0492ea8a 100644 --- a/balancer-js/src/modules/data/pool/subgraph.ts +++ b/balancer-js/src/modules/data/pool/subgraph.ts @@ -107,7 +107,7 @@ export class PoolsSubgraphRepository this.query.args.block = { number: await this.blockHeight() }; } - this.query.args.first = options?.first || 1000; + this.query.args.first = options?.first || this.query.args.first || 1000; const formattedQuery = new GraphQLArgsBuilder(this.query.args).format( new SubgraphArgsFormatter() diff --git a/balancer-js/src/modules/pools/index.ts b/balancer-js/src/modules/pools/index.ts index 7f91fb36e..d81a6b598 100644 --- a/balancer-js/src/modules/pools/index.ts +++ b/balancer-js/src/modules/pools/index.ts @@ -77,7 +77,7 @@ export class Pools implements Findable { ); this.simulationService = new Simulation( networkConfig, - this.repositories.poolsForSor + this.repositories.poolsForSimulations ); this.graphService = new PoolGraph(this.repositories.poolsOnChain); this.joinService = new Join( @@ -371,11 +371,15 @@ export class Pools implements Findable { bptAmount, userAddress, slippage, + shouldUnwrapNativeAsset, + singleTokenOut, }: { pool: Pool; bptAmount: string; userAddress: string; slippage: string; + shouldUnwrapNativeAsset?: boolean; + singleTokenOut?: string; }): ExitExactBPTInAttributes { const concerns = PoolTypeConcerns.from(pool.poolType); if (!concerns || !concerns.exit.buildExitExactBPTIn) @@ -388,7 +392,8 @@ export class Pools implements Findable { slippage, wrappedNativeAsset: this.networkConfig.addresses.tokens.wrappedNativeAsset.toLowerCase(), - shouldUnwrapNativeAsset: false, + shouldUnwrapNativeAsset: shouldUnwrapNativeAsset ?? false, + singleTokenOut: singleTokenOut ?? undefined, toInternalBalance: false, }); } diff --git a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/recovery.integration.spec.ts b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/recovery.integration.spec.ts index da92e61c4..6f41625a7 100644 --- a/balancer-js/src/modules/pools/pool-types/concerns/composableStable/recovery.integration.spec.ts +++ b/balancer-js/src/modules/pools/pool-types/concerns/composableStable/recovery.integration.spec.ts @@ -19,7 +19,7 @@ const { ALCHEMY_URL_POLYGON: jsonRpcUrl } = process.env; const rpcUrl = 'http://127.0.0.1:8137'; const provider = new ethers.providers.JsonRpcProvider(rpcUrl, network); const signer = provider.getSigner(); -const blockNumber = 46572274; +const blockNumber = 47427007; let balancer: BalancerSDK; describe('ComposableStable - recovery', () => { @@ -229,7 +229,7 @@ describe('ComposableStable - recovery', () => { context('buildRecoveryExit', async () => { context('PoolWithMethods', async () => { it('should recovery exit', async () => { - const bptAmount = parseFixed('1.34', 18).toString(); + const bptAmount = parseFixed('0.001', 18).toString(); const slippage = '10'; // 10 bps = 0.1% const pool = await balancer.pools.find(poolId); if (!pool) throw Error('Pool not found'); @@ -252,7 +252,7 @@ describe('ComposableStable - recovery', () => { }); context('Pool & refresh', async () => { it('should recovery exit', async () => { - const bptAmount = parseFixed('1.34', 18).toString(); + const bptAmount = parseFixed('0.00001', 18).toString(); const slippage = '10'; // 10 bps = 0.1% let pool = await balancer.data.pools.find(poolId); if (!pool) throw Error('Pool not found'); diff --git a/balancer-js/src/modules/pools/queries/queries.integration.spec.ts b/balancer-js/src/modules/pools/queries/queries.integration.spec.ts index c926f6252..e4773efde 100644 --- a/balancer-js/src/modules/pools/queries/queries.integration.spec.ts +++ b/balancer-js/src/modules/pools/queries/queries.integration.spec.ts @@ -30,31 +30,30 @@ const balPool = { }; const composableStablePool = { - id: '0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d', + id: '0x4edcb2b46377530bc18bb4d2c7fe46a992c73e100000000000000000000003ec', poolType: PoolType.ComposableStable, tokensList: [ - '0x2f4eb100552ef93840d5adc30560e5513dfffacb', - '0x82698aecc9e28e9bb27608bd52cf57f704bd1b83', - '0xa13a9247ea42d743238089903570127dda72fe44', - '0xae37d54ae477268b9997d4161b96b8200755935c', + '0x4edcb2b46377530bc18bb4d2c7fe46a992c73e10', + '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + '0xbe9895146f7af43049ca1c1ae358b0541ea49704', ], }; -const composableStablePoolWithTokenAtZero = { - id: '0x02d928e68d8f10c0358566152677db51e1e2dc8c00000000000000000000051e', - poolType: PoolType.ComposableStable, - tokensList: [ - '0x02d928e68d8f10c0358566152677db51e1e2dc8c', - '0x60d604890feaa0b5460b28a424407c24fe89374a', - '0xf951e335afb289353dc249e82926178eac7ded78', - ], -}; +// const composableStablePoolWithTokenAtZero = { +// id: '0x02d928e68d8f10c0358566152677db51e1e2dc8c00000000000000000000051e', +// poolType: PoolType.ComposableStable, +// tokensList: [ +// '0x02d928e68d8f10c0358566152677db51e1e2dc8c', +// '0x60d604890feaa0b5460b28a424407c24fe89374a', +// '0xf951e335afb289353dc249e82926178eac7ded78', +// ], +// }; const pools = [ stETHPool, balPool, composableStablePool, - composableStablePoolWithTokenAtZero, + // composableStablePoolWithTokenAtZero, ]; let queryParams: ParamsBuilder; diff --git a/balancer-js/src/modules/sdk.module.ts b/balancer-js/src/modules/sdk.module.ts index c92951832..a99c6b9f9 100644 --- a/balancer-js/src/modules/sdk.module.ts +++ b/balancer-js/src/modules/sdk.module.ts @@ -89,7 +89,7 @@ export class BalancerSDK implements BalancerSDKRoot { }); } this.vaultModel = new VaultModel( - this.data.poolsForSor, + this.data.poolsForSimulations, this.networkConfig.addresses.tokens.wrappedNativeAsset ); } diff --git a/balancer-js/src/modules/sor/pool-data/onChainData3.ts b/balancer-js/src/modules/sor/pool-data/onChainData3.ts new file mode 100644 index 000000000..15acacd2f --- /dev/null +++ b/balancer-js/src/modules/sor/pool-data/onChainData3.ts @@ -0,0 +1 @@ +export * from '@/modules/data/pool/onchain-data'; diff --git a/balancer-js/src/modules/sor/pool-data/subgraphPoolDataService.ts b/balancer-js/src/modules/sor/pool-data/subgraphPoolDataService.ts index 86dbcef33..94c0b1af7 100644 --- a/balancer-js/src/modules/sor/pool-data/subgraphPoolDataService.ts +++ b/balancer-js/src/modules/sor/pool-data/subgraphPoolDataService.ts @@ -6,7 +6,7 @@ import { SubgraphClient, } from '@/modules/subgraph/subgraph'; import { parseInt } from 'lodash'; -import { getOnChainBalances } from './onChainData'; +import { getOnChainBalances } from './onChainData3'; import { Provider } from '@ethersproject/providers'; import { BalancerNetworkConfig, diff --git a/balancer-js/src/modules/sor/sor.module.ts b/balancer-js/src/modules/sor/sor.module.ts index cdb91063d..a99ad5605 100644 --- a/balancer-js/src/modules/sor/sor.module.ts +++ b/balancer-js/src/modules/sor/sor.module.ts @@ -13,6 +13,8 @@ import { } from '@/types'; import { SubgraphTokenPriceService } from './token-price/subgraphTokenPriceService'; import { getNetworkConfig } from '@/modules/sdk.helpers'; +import { poolsToIgnore } from '@/lib/constants/poolsToIgnore'; +import { getPoolAddress } from '@/pool-utils'; export class Sor extends SOR { constructor(sdkConfig: BalancerSdkConfig) { @@ -70,12 +72,17 @@ export class Sor extends SOR { provider: Provider, subgraphClient: SubgraphClient ) { + const poolsToIgnoreAddr = poolsToIgnore.map((id) => getPoolAddress(id)); + // For SOR we want to ignore all configured pools (for Vault/Simulation we don't) + const allPoolsToIgnore = network.poolsToIgnore + ? [...network.poolsToIgnore, ...poolsToIgnoreAddr] + : poolsToIgnoreAddr; return typeof sorConfig.poolDataService === 'object' ? sorConfig.poolDataService : new SubgraphPoolDataService( subgraphClient, provider, - network, + { ...network, poolsToIgnore: allPoolsToIgnore }, sorConfig ); } diff --git a/balancer-js/src/test/factories/data.ts b/balancer-js/src/test/factories/data.ts index b64c8e282..7edc946bb 100644 --- a/balancer-js/src/test/factories/data.ts +++ b/balancer-js/src/test/factories/data.ts @@ -96,7 +96,7 @@ export const repositores = ({ ), }): BalancerDataRepositories => ({ pools, - poolsForSor: {} as SubgraphPoolDataService, + poolsForSimulations: {} as SubgraphPoolDataService, poolsOnChain, yesterdaysPools, tokenPrices, diff --git a/balancer-js/src/test/lib/utils.ts b/balancer-js/src/test/lib/utils.ts index 673080934..327e1ec6b 100644 --- a/balancer-js/src/test/lib/utils.ts +++ b/balancer-js/src/test/lib/utils.ts @@ -73,6 +73,7 @@ export const FORK_NODES: Record = { [Network.POLYGON]: `${process.env.ALCHEMY_URL_POLYGON}`, [Network.ARBITRUM]: `${process.env.ALCHEMY_URL_ARBITRUM}`, [Network.ZKEVM]: `${process.env.ALCHEMY_URL_ZKEVM}`, + [Network.GNOSIS]: `${process.env.RPC_URL_GNOSIS}`, }; /** diff --git a/balancer-js/src/types.ts b/balancer-js/src/types.ts index bf6582165..ca0633bd2 100644 --- a/balancer-js/src/types.ts +++ b/balancer-js/src/types.ts @@ -139,7 +139,7 @@ export interface BalancerDataRepositories { */ pools: Findable & Searchable; // Does it need to be different from the pools repository? - poolsForSor: SubgraphPoolDataService; + poolsForSimulations: SubgraphPoolDataService; poolGauges?: PoolGaugesRepository; poolJoinExits: PoolJoinExitRepository; // Perhaps better to use a function to get upto date balances when needed.