From 8ae0ce6b3c0b1cb6221988c3f47bec4eb84ca599 Mon Sep 17 00:00:00 2001 From: bronco Date: Wed, 23 Aug 2023 14:41:21 +0200 Subject: [PATCH 01/23] drop in replacement for pool data via multicall --- debug/multicall.ts | 44 ++++ src/data/enrichers/onChainPoolDataEnricher.ts | 2 +- .../onChainPoolDataViaMulticallEnricher.ts | 122 ++++++++++ src/data/onChainPoolDataViaMulticall.ts | 222 ++++++++++++++++++ 4 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 debug/multicall.ts create mode 100644 src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts create mode 100644 src/data/onChainPoolDataViaMulticall.ts diff --git a/debug/multicall.ts b/debug/multicall.ts new file mode 100644 index 00000000..7af29b38 --- /dev/null +++ b/debug/multicall.ts @@ -0,0 +1,44 @@ +import { fetchAdditionalPoolData } from '../src/data/onChainPoolDataViaMulticall'; +import { OnChainPoolDataViaMulticallEnricher } from '../src/data/enrichers/onChainPoolDataViaMulticallEnricher'; +import { createPublicClient, http } from 'viem'; +import { mainnet, polygonZkEvm } from 'viem/chains'; + +const client = createPublicClient({ + chain: mainnet, + transport: http(), +}); + +const poolsQuery = `{ + pools(first: 10, orderBy: id, orderDirection: desc, where: { totalShares_gt: 0, poolType_contains: "Weighted" }) { + id + poolType + wrappedIndex + tokens { + address + decimals + index + } + } +}`; + +// 'https://api.studio.thegraph.com/query/24660/balancer-polygon-zk-v2/version/latest', +const pools = await fetch( + 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ query: poolsQuery }), + }, +) + .then((res) => res.json()) + .then((res) => res.data); + +// const data = await fetchAdditionalPoolData(pools.pools, client); + +const onChainEnricher = new OnChainPoolDataViaMulticallEnricher(1, ''); +const data = await onChainEnricher.fetchAdditionalPoolData(pools); +const enrichered = onChainEnricher.enrichPoolsWithData(pools.pools, data); + +console.log(enrichered, data.length); diff --git a/src/data/enrichers/onChainPoolDataEnricher.ts b/src/data/enrichers/onChainPoolDataEnricher.ts index 3903e565..5c1c3df9 100644 --- a/src/data/enrichers/onChainPoolDataEnricher.ts +++ b/src/data/enrichers/onChainPoolDataEnricher.ts @@ -18,7 +18,7 @@ import { } from '../../utils'; import { HumanAmount, SwapOptions } from '../../types'; -interface OnChainPoolData { +export interface OnChainPoolData { id: string; balances: readonly bigint[]; totalSupply: bigint; diff --git a/src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts b/src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts new file mode 100644 index 00000000..999c47eb --- /dev/null +++ b/src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts @@ -0,0 +1,122 @@ +import { createPublicClient, formatUnits, http, PublicClient } from 'viem'; +import { + GetPoolsResponse, + PoolDataEnricher, + RawPool, + RawPoolTokenWithRate, + RawWeightedPoolToken, +} from '../types'; + +import { CHAINS } from '../../utils'; +import { HumanAmount } from '../../types'; +import { fetchAdditionalPoolData } from '../onChainPoolDataViaMulticall'; +import { OnChainPoolData } from './onChainPoolDataEnricher'; + +export class OnChainPoolDataViaMulticallEnricher implements PoolDataEnricher { + private readonly client: PublicClient; + + constructor( + private readonly chainId: number, + private readonly rpcUrl: string, + ) { + this.client = createPublicClient({ + transport: http(this.rpcUrl), + chain: CHAINS[this.chainId], + }); + } + + public async fetchAdditionalPoolData( + data: GetPoolsResponse, + ): Promise { + return fetchAdditionalPoolData(data.pools, this.client); + } + + public enrichPoolsWithData( + pools: RawPool[], + additionalPoolData: OnChainPoolData[], + ): RawPool[] { + return pools.map((pool) => { + const data = additionalPoolData.find((item) => item.id === pool.id); + + return { + ...pool, + tokens: pool.tokens + .sort((a, b) => a.index - b.index) + .map((token) => { + return { + ...token, + balance: + data?.balances && data.balances.length > 0 + ? (formatUnits( + data.balances[token.index], + token.decimals, + ) as HumanAmount) + : token.balance, + priceRate: this.getPoolTokenRate({ + pool, + token: token as RawPoolTokenWithRate, + data, + index: token.index, + }), + weight: data?.weights + ? formatUnits(data.weights[token.index], 18) + : (token as RawWeightedPoolToken).weight, + }; + }), + totalShares: data?.totalSupply + ? (formatUnits(data.totalSupply, 18) as HumanAmount) + : pool.totalShares, + amp: data?.amp + ? formatUnits(data.amp, 3) + : 'amp' in pool + ? pool.amp + : undefined, + swapFee: data?.swapFee + ? (formatUnits(data.swapFee, 18) as HumanAmount) + : pool.swapFee, + tokenRates: data?.tokenRates + ? data.tokenRates.map( + (tokenRate) => + formatUnits(tokenRate, 18) as HumanAmount, + ) + : undefined, + balances: data?.balances, + totalSupply: data?.totalSupply, + weights: data?.weights, + wrappedTokenRate: data?.wrappedTokenRate, + linearTargets: data?.linearTargets, + scalingFactors: data?.scalingFactors, + rate: data?.poolRate, + inRecoveryMode: data?.inRecoveryMode || false, + isPaused: data?.isPaused || false, + queryFailed: data?.queryFailed, + }; + }); + } + + private getPoolTokenRate({ + pool, + token, + data, + index, + }: { + pool: RawPool; + token: RawPoolTokenWithRate; + data?: OnChainPoolData; + index: number; + }): string { + if ( + data?.wrappedTokenRate && + 'wrappedIndex' in pool && + pool.wrappedIndex === index + ) { + return formatUnits(data.wrappedTokenRate, 18); + } + + if (data?.scalingFactors) { + return formatUnits(data.scalingFactors[index], 18); + } + + return token.priceRate; + } +} diff --git a/src/data/onChainPoolDataViaMulticall.ts b/src/data/onChainPoolDataViaMulticall.ts new file mode 100644 index 00000000..3db39f72 --- /dev/null +++ b/src/data/onChainPoolDataViaMulticall.ts @@ -0,0 +1,222 @@ +import { PublicClient, formatUnits, parseAbi } from 'viem'; +import { getPoolAddress } from '../utils'; +import { vaultAbi } from '../abi'; + +const abi = parseAbi([ + '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? +]); + +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'; + } +}; + +const defaultCalls = { + count: 7, + build: (id: string, poolType: string) => [ + { + address: + '0xBA12222222228d8Ba445958a75a0704d566BF2C8' as `0x${string}`, + abi: vaultAbi, + functionName: 'getPoolTokens', + args: [id], + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: getTotalSupplyFn(poolType), + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: getSwapFeeFn(poolType), + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getPausedState', + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'inRecoveryMode', + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getRate', + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getScalingFactors', + }, + ], + parse: (results: any, shift: number) => ({ + balances: results[shift].result[0], + totalSupply: results[shift + 1].result, + swapFee: results[shift + 2].result, + isPaused: results[shift + 3].result, + inRecoveryMode: results[shift + 4].result, + poolRate: results[shift + 5].result, + scalingFactors: results[shift + 6].result, + queryFailed: false, + }), +}; + +const weightedCalls = { + count: 1, + build: (id: string) => [ + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getNormalizedWeights', + }, + ], + parse: (results: any, shift: number) => ({ + weights: results[shift].result, + }), +}; + +const linearCalls = { + count: 2, + build: (id: string) => [ + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getTargets', + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getWrappedTokenRate', + }, + ], + parse: (results: any, shift: number) => ({ + linearTargets: results[shift].result, + wrappedTokenRate: results[shift + 1].result, + }), +}; + +const stableCalls = { + count: 1, + build: (id: string) => [ + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getAmplificationParameter', + }, + ], + parse: (results: any, shift: number) => ({ + amplificationParameter: formatUnits( + results[shift].result[0], + String(results[shift].result[2]).length - 1, + ), + }), +}; + +const gyroECalls = { + count: 1, + build: (id: string) => [ + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getTokenRates', + }, + ], + parse: (results: any, shift: number) => ({ + tokenRates: results[shift].result, + }), +}; + +const poolTypeCalls = (poolType: string) => { + switch (poolType) { + case 'Weighted': + case 'LiquidityBootstrapping': + case 'Investment': + return weightedCalls; + case 'Stable': + case 'StablePhantom': + case 'MetaStable': + case 'ComposableStable': + return stableCalls; + case 'GyroE': + return gyroECalls; + default: + if (poolType.includes('Linear')) { + return linearCalls; + } else { + return { + count: 0, + build: () => [], + parse: () => ({}), + }; + } + } +}; + +export const fetchAdditionalPoolData = async ( + pools: { + id: string; + poolType: string; + }[], + client: PublicClient, +) => { + if (pools.length === 0) { + return []; + } + + const contracts = pools.flatMap(({ id, poolType }) => [ + ...defaultCalls.build(id, poolType), + ...poolTypeCalls(poolType).build(id), + ]); + + const results = await client.multicall({ + contracts, + batchSize: 2_048, + }); + + let shift = 0; + + return pools.map(({ id, poolType }) => { + const result = { + id, + ...defaultCalls.parse(results, shift), + ...poolTypeCalls(poolType).parse( + results, + shift + defaultCalls.count, + ), + }; + shift += defaultCalls.count + poolTypeCalls(poolType).count; + return result; + }); +}; From 0f72357314043af0b0ba519a534cd5610948f1dd Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 4 Sep 2023 15:46:16 +0100 Subject: [PATCH 02/23] fix: Add missing gyro3 to SupportedRawPoolTypes. --- src/data/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/types.ts b/src/data/types.ts index 34476dac..5cf7c36d 100644 --- a/src/data/types.ts +++ b/src/data/types.ts @@ -15,6 +15,7 @@ export type SupportedRawPoolTypes = | 'Element' | 'FX' | 'Gyro2' + | 'Gyro3' | 'GyroE'; type LinearPoolType = `${string}Linear`; From f420ea3b9b7656c6f895df40936c94f5733d274c Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Wed, 13 Sep 2023 21:21:38 +0200 Subject: [PATCH 03/23] fixes onchain data in multicall enricher --- debug/multicall.ts | 19 +++++----- .../onChainPoolDataViaMulticallEnricher.ts | 17 +++++---- src/data/onChainPoolDataViaMulticall.ts | 35 ++++++++++++------- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/debug/multicall.ts b/debug/multicall.ts index 7af29b38..2a3ba6d9 100644 --- a/debug/multicall.ts +++ b/debug/multicall.ts @@ -1,16 +1,21 @@ -import { fetchAdditionalPoolData } from '../src/data/onChainPoolDataViaMulticall'; import { OnChainPoolDataViaMulticallEnricher } from '../src/data/enrichers/onChainPoolDataViaMulticallEnricher'; import { createPublicClient, http } from 'viem'; +// rome-ignore lint/correctness/noUnusedVariables: import { mainnet, polygonZkEvm } from 'viem/chains'; +const chain = polygonZkEvm; +const rpc = 'https://rpc.ankr.com/polygon_zkevm'; + +// rome-ignore lint/correctness/noUnusedVariables: const client = createPublicClient({ - chain: mainnet, + chain, transport: http(), }); const poolsQuery = `{ - pools(first: 10, orderBy: id, orderDirection: desc, where: { totalShares_gt: 0, poolType_contains: "Weighted" }) { + pools(first: 10, orderBy: id, orderDirection: desc, where: { totalShares_gt: 0, poolType_contains: "ComposableStable" }) { id + address poolType wrappedIndex tokens { @@ -21,9 +26,9 @@ const poolsQuery = `{ } }`; -// 'https://api.studio.thegraph.com/query/24660/balancer-polygon-zk-v2/version/latest', const pools = await fetch( - 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', + 'https://api.studio.thegraph.com/query/24660/balancer-polygon-zk-v2/version/latest', + // 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', { method: 'POST', headers: { @@ -35,9 +40,7 @@ const pools = await fetch( .then((res) => res.json()) .then((res) => res.data); -// const data = await fetchAdditionalPoolData(pools.pools, client); - -const onChainEnricher = new OnChainPoolDataViaMulticallEnricher(1, ''); +const onChainEnricher = new OnChainPoolDataViaMulticallEnricher(chain.id, rpc); const data = await onChainEnricher.fetchAdditionalPoolData(pools); const enrichered = onChainEnricher.enrichPoolsWithData(pools.pools, data); diff --git a/src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts b/src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts index 999c47eb..276bc5bb 100644 --- a/src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts +++ b/src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts @@ -80,13 +80,16 @@ export class OnChainPoolDataViaMulticallEnricher implements PoolDataEnricher { formatUnits(tokenRate, 18) as HumanAmount, ) : undefined, - balances: data?.balances, - totalSupply: data?.totalSupply, - weights: data?.weights, - wrappedTokenRate: data?.wrappedTokenRate, - linearTargets: data?.linearTargets, - scalingFactors: data?.scalingFactors, - rate: data?.poolRate, + lowerTarget: data?.linearTargets + ? data.linearTargets[0] + : 'lowerTarget' in pool + ? pool.lowerTarget + : undefined, + upperTarget: data?.linearTargets + ? data.linearTargets[0] + : 'upperTarget' in pool + ? pool.upperTarget + : undefined, inRecoveryMode: data?.inRecoveryMode || false, isPaused: data?.isPaused || false, queryFailed: data?.queryFailed, diff --git a/src/data/onChainPoolDataViaMulticall.ts b/src/data/onChainPoolDataViaMulticall.ts index 3db39f72..96c15ee1 100644 --- a/src/data/onChainPoolDataViaMulticall.ts +++ b/src/data/onChainPoolDataViaMulticall.ts @@ -82,7 +82,7 @@ const defaultCalls = { }, ], parse: (results: any, shift: number) => ({ - balances: results[shift].result[0], + balances: results[shift].result[1], totalSupply: results[shift + 1].result, swapFee: results[shift + 2].result, isPaused: results[shift + 3].result, @@ -158,7 +158,13 @@ const gyroECalls = { }), }; -const poolTypeCalls = (poolType: string) => { +const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { + const do_nothing = { + count: 0, + build: () => [], + parse: () => ({}), + }; + switch (poolType) { case 'Weighted': case 'LiquidityBootstrapping': @@ -170,17 +176,19 @@ const poolTypeCalls = (poolType: string) => { case 'ComposableStable': return stableCalls; case 'GyroE': - return gyroECalls; - default: - if (poolType.includes('Linear')) { + if (poolTypeVersion === 2) { + return gyroECalls; + } else { + return do_nothing; + } + case 'AaveLinear': + if (poolTypeVersion === 1) { return linearCalls; } else { - return { - count: 0, - build: () => [], - parse: () => ({}), - }; + return do_nothing; } + default: + return do_nothing; } }; @@ -188,6 +196,7 @@ export const fetchAdditionalPoolData = async ( pools: { id: string; poolType: string; + poolTypeVersion: number; }[], client: PublicClient, ) => { @@ -195,14 +204,14 @@ export const fetchAdditionalPoolData = async ( return []; } - const contracts = pools.flatMap(({ id, poolType }) => [ + const contracts = pools.flatMap(({ id, poolType, poolTypeVersion }) => [ ...defaultCalls.build(id, poolType), - ...poolTypeCalls(poolType).build(id), + ...poolTypeCalls(poolType, poolTypeVersion).build(id), ]); const results = await client.multicall({ contracts, - batchSize: 2_048, + batchSize: 128, // 128 is the max batch size for zkEVM RPCs }); let shift = 0; From 70045618a6d5e0d423c24d1b262770ed871f8ac3 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Mon, 25 Sep 2023 11:22:13 +0100 Subject: [PATCH 04/23] Add changeset. --- .changeset/perfect-ties-play.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/perfect-ties-play.md diff --git a/.changeset/perfect-ties-play.md b/.changeset/perfect-ties-play.md new file mode 100644 index 00000000..0415a01a --- /dev/null +++ b/.changeset/perfect-ties-play.md @@ -0,0 +1,5 @@ +--- +"@balancer/sdk": patch +--- + +Add missing gyro3 to SupportedRawPoolTypes. From 63f53163d251acc7f8bc81d115a47682f56f190d Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 10:07:34 +0100 Subject: [PATCH 05/23] fix: Subgraph filter out bricked pools from vulnerability. --- src/data/providers/subgraphPoolProvider.ts | 4 +- src/utils/constants.ts | 337 +++++++++++++++++++++ 2 files changed, 340 insertions(+), 1 deletion(-) diff --git a/src/data/providers/subgraphPoolProvider.ts b/src/data/providers/subgraphPoolProvider.ts index 2d8c1106..142c1c55 100644 --- a/src/data/providers/subgraphPoolProvider.ts +++ b/src/data/providers/subgraphPoolProvider.ts @@ -5,8 +5,9 @@ import { RawPool, } from '../types'; import { fetchWithRetry } from '../../utils/fetch'; -import { SUBGRAPH_URLS } from '../../utils'; +import { SUBGRAPH_URLS, brickedPools } from '../../utils'; +// rome-ignore lint/complexity/useLiteralKeys: BigInt.prototype['toJSON'] = function () { return this.toString(); }; @@ -72,6 +73,7 @@ export class SubgraphPoolProvider implements PoolDataProvider { ? ['Investment', 'LiquidityBootstrapping'] : undefined, ...config, + poolIdNotIn: [...(config?.poolIdNotIn ?? []), ...brickedPools], }; } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 3ecee182..131939a5 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -175,3 +175,340 @@ export const DEFAULT_FUND_MANAGMENT = { }; export const DEFAULT_USERDATA = '0x'; + +// Pools from `23 Vulnerability that are bricked/broken +export const brickedPools = [ + '0x00c2a4be503869fa751c2dbcb7156cc970b5a8da000000000000000000000477', + '0x02d928e68d8f10c0358566152677db51e1e2dc8c00000000000000000000051e', + '0x04248aabca09e9a1a3d5129a7ba05b7f17de768400000000000000000000050e', + '0x05513ca725b6ce035ca2641075474eb469f05f4c00020000000000000000041f', + '0x0a0fb4ff697de5ac5b6770cd8ee1b72af80b57cf000000000000000000000496', + '0x0afbd58beca09545e4fb67772faf3858e610bcd00000000000000000000004b9', + '0x0d05aac44ac7dd3c7ba5d50be93eb884a057d23400000000000000000000051c', + '0x11839d635e2f0270da37e8ef4324d4d5d54329570002000000000000000004d8', + '0x126e7643235ec0ab9c103c507642dc3f4ca23c66000000000000000000000468', + '0x133d241f225750d2c92948e464a5a80111920331000000000000000000000476', + '0x159cb00338fb63f263fd6f621df619cef71da9540000000000000000000004d5', + '0x173063a30e095313eee39411f07e95a8a806014e0002000000000000000003ab', + '0x1bd2f176a812e312077bca87e37c08432bb09f3e0000000000000000000005a1', + '0x20b156776114e8a801e9767d90c6ccccc8adf398000000000000000000000499', + '0x246ffb4d928e394a02e45761fecdba6c2e79b8eb000000000000000000000541', + '0x25accb7943fd73dda5e23ba6329085a3c24bfb6a000200000000000000000387', + '0x26c2b83fc8535deead276f5cc3ad9c1a2192e02700020000000000000000056b', + '0x2b218683178d029bab6c9789b1073aa6c96e517600000000000000000000058c', + '0x2ba7aa2213fa2c909cd9e46fed5a0059542b36b00000000000000000000003a3', + '0x2bbf681cc4eb09218bee85ea2a5d3d13fa40fc0c0000000000000000000000fd', + '0x2e52c64fd319e380cdbcfc4577ea1fda558a32e40002000000000000000005ba', + '0x2f4eb100552ef93840d5adc30560e5513dfffacb000000000000000000000334', + '0x2ff1a9dbdacd55297452cfd8a4d94724bc22a5f7000000000000000000000484', + '0x3035917be42af437cbdd774be26b9ec90a2bd677000200000000000000000543', + '0x331d50e0b00fc1c32742f151e56b9b616227e23e00000000000000000000047c', + '0x334c96d792e4b26b841d28f53235281cec1be1f200020000000000000000038a', + '0x335d1709d4da9aca59d16328db5cd4ea66bfe06b0000000000000000000004d6', + '0x395d8a1d9ad82b5abe558f8abbfe183b27138af40000000000000000000004e5', + '0x3bb22fc9033b802f2ac47c18885f63476f158afc000000000000000000000483', + '0x3c640f0d3036ad85afa2d5a9e32be651657b874f00000000000000000000046b', + '0x3cdae4f12a67ba563499e102f309c73213cb241c000000000000000000000335', + '0x3dbb8d974b82e82ce79c20c0f5995f4f1f533ede000000000000000000000470', + '0x3f7a7fd7f214be45ec26820fd01ac3be4fc75aa70002000000000000000004c5', + '0x3fcb7085b8f2f473f80bf6d879cae99ea4de934400000000000000000000056d', + '0x41503c9d499ddbd1dcdf818a1b05e9774203bf46000000000000000000000594', + '0x4228290ee9cab692938ff0b4ba303fbcdb68e9f200020000000000000000057d', + '0x454ed96955d04d2f5cdd05e0fd1c77975bfe5307000000000000000000000410', + '0x481c5fc05d63a58aa2f0f2aa417c021b5d419cb200000000000000000000056a', + '0x483006684f422a9448023b2382615c57c5ecf18f000000000000000000000488', + '0x4a82b580365cff9b146281ab72500957a849abdc000000000000000000000494', + '0x4c81255cc9ed7062180ea99962fe05ac0d57350b0000000000000000000005a3', + '0x4c8d2e60863e8d7e1033eda2b3d84e92a641802000000000000000000000040f', + '0x4cbde5c4b4b53ebe4af4adb85404725985406163000000000000000000000595', + '0x4ce0bd7debf13434d3ae127430e9bd4291bfb61f00020000000000000000038b', + '0x4ce277df0feb5b4d07a0ca2adcf5326e4005239d000000000000000000000518', + '0x4fd4687ec38220f805b6363c3c1e52d0df3b5023000200000000000000000473', + '0x4fd63966879300cafafbb35d157dc5229278ed230000000000000000000000e9', + '0x50cf90b954958480b8df7958a9e965752f62712400000000000000000000046f', + '0x53bc3cba3832ebecbfa002c12023f8ab1aa3a3a0000000000000000000000411', + '0x5a6a8cffb4347ff7fc484bf5f0f8a2e234d34255000200000000000000000275', + '0x5b3240b6be3e7487d61cd1afdfc7fe4fa1d81e6400000000000000000000037b', + '0x60683b05e9a39e3509d8fdb9c959f23170f8a0fa000000000000000000000489', + '0x60d604890feaa0b5460b28a424407c24fe89374a0000000000000000000004fc', + '0x639883476960a23b38579acfd7d71561a0f408cf000200000000000000000505', + '0x652d486b80c461c397b0d95612a404da936f3db30000000000000000000000e7', + '0x6667c6fa9f2b3fc1cc8d85320b62703d938e43850000000000000000000004fb', + '0x6a1eb2e9b45e772f55bd9a34659a04b6f75da68700000000000000000000040d', + '0x6c56e72c551b5ac4bf54a620a76077ca768c8fe40002000000000000000004da', + '0x70b7d3b3209a59fb0400e17f67f3ee8c37363f4900020000000000000000018f', + '0x7337224d59cb16c2dc6938cd45a7b2c60c865d6a0000000000000000000004d4', + '0x74cbfaf94a3577c539a9dcee9870a6349a33b34f000000000000000000000534', + '0x779d01f939d78a918a3de18cc236ee89221dfd4e0000000000000000000004c7', + '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe', + '0x804cdb9116a10bb78768d3252355a1b18067bf8f0000000000000000000000fb', + '0x813e3fe1761f714c502d1d2d3a7cceb33f37f59d00000000000000000000040c', + '0x82698aecc9e28e9bb27608bd52cf57f704bd1b83000000000000000000000336', + '0x8a6b25e33b12d1bb6929a8793961076bd1f9d3eb0002000000000000000003e8', + '0x8e6ec57a822c2f527f2df7c7d7d361df3e7530a1000000000000000000000498', + '0x8f4063446f5011bc1c9f79a819efe87776f23704000000000000000000000197', + '0x9001cbbd96f54a658ff4e6e65ab564ded76a543100000000000000000000050a', + '0x9210f1204b5a24742eba12f710636d76240df3d00000000000000000000000fc', + '0x9516a2d25958edb8da246a320f2c7d94a0dbe25d000000000000000000000519', + '0x959216bb492b2efa72b15b7aacea5b5c984c3cca000200000000000000000472', + '0x968024662b9566b42d78af23a0f441bc8723fa83000200000000000000000418', + '0x99c88ad7dc566616548adde8ed3effa730eb6c3400000000000000000000049a', + '0x9b1c8407a360443a9e5eca004713e4088fab8ac0000000000000000000000497', + '0x9b692f571b256140a39a34676bffa30634c586e100000000000000000000059d', + '0x9d7f992c900fbea0ec314bdd71b7cc1becf76a33000200000000000000000573', + '0x9fb771d530b0ceba5160f7bfe2dd1e8b8aa1340300000000000000000000040e', + '0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d', + '0xa1697f9af0875b63ddc472d6eebada8c1fab85680000000000000000000004f9', + '0xa3823e50f20982656557a4a6a9c06ba5467ae9080000000000000000000000e6', + '0xa718042e5622099e5f0ace4e7122058ab39e1bbe000200000000000000000475', + '0xa8b103a10a94f4f2d7ed2fdcd5545e807557330700000000000000000000048e', + '0xac5b4ef7ede2f2843a704e96dcaa637f4ba3dc3f00000000000000000000051d', + '0xac976bb42cb0c85635644e8c7c74d0e0286aa61c0000000000000000000003cb', + '0xae37d54ae477268b9997d4161b96b8200755935c000000000000000000000337', + '0xae8535c23afedda9304b03c68a3563b75fc8f92b0000000000000000000005a0', + '0xb0f75e97a114a4eb4a425edc48990e6760726709000000000000000000000198', + '0xb5e3de837f869b0248825e0175da73d4e8c3db6b000200000000000000000474', + '0xb841b062ea8ccf5c4cb78032e91de4ae875560420002000000000000000005b7', + '0xb9bd68a77ccf8314c0dfe51bc291c77590c4e9e6000200000000000000000385', + '0xbb6881874825e60e1160416d6c426eae65f2459e000000000000000000000592', + '0xbc0f2372008005471874e426e86ccfae7b4de79d000000000000000000000485', + '0xbf2ef8bdc2fc0f3203b3a01778e3ec5009aeef3300000000000000000000058d', + '0xbfa413a2ff0f20456d57b643746133f54bfe0cd20000000000000000000004c3', + '0xc2b021133d1b0cf07dba696fd5dd89338428225b000000000000000000000598', + '0xc443c15033fcb6cf72cc24f1bda0db070ddd9786000000000000000000000593', + '0xc50d4347209f285247bda8a09fc1c12ce42031c3000000000000000000000590', + '0xc5dc1316ab670a2eed5716d7f19ced321191f38200000000000000000000056e', + '0xc8c79fcd0e859e7ec81118e91ce8e4379a481ee6000000000000000000000196', + '0xcaa052584b462198a5a9356c28bce0634d65f65c0000000000000000000004db', + '0xcbfa4532d8b2ade2c261d3dd5ef2a2284f7926920000000000000000000004fa', + '0xcfae6e251369467f465f13836ac8135bd42f8a56000000000000000000000591', + '0xd4e7c1f3da1144c9e2cfd1b015eda7652b4a439900000000000000000000046a', + '0xd6e355036f41dc261b3f1ed3bbc6003e87aadb4f000000000000000000000495', + '0xd7edb56f63b2a0191742aea32df1f98ca81ed9c600000000000000000000058e', + '0xd997f35c9b1281b82c8928039d14cddab5e13c2000000000000000000000019c', + '0xdba274b4d04097b90a72b62467d828cefd708037000000000000000000000486', + '0xdc063deafce952160ec112fa382ac206305657e60000000000000000000004c4', + '0xdec02e6642e2c999af429f5ce944653cad15e093000000000000000000000469', + '0xe03af00fabe8401560c1ff7d242d622a5b601573000000000000000000000493', + '0xe0fcbf4d98f0ad982db260f86cf28b49845403c5000000000000000000000504', + '0xe2d16b0a39f3fbb4389a0e8f1efcbecfb3d1e6e10000000000000000000005a7', + '0xe4dc3c1998ac693d68f4c77476d7c815694c3e94000200000000000000000416', + '0xe6bcc79f328eec93d4ec8f7ed35534d9ab549faa0000000000000000000000e8', + '0xe8c56405bc405840154d9b572927f4197d110de10000000000000000000005a4', + '0xeb486af868aeb3b6e53066abc9623b1041b42bc000000000000000000000046c', + '0xeb567dde03f3da7fe185bdacd5ab495ab220769d000000000000000000000548', + '0xec3626fee40ef95e7c0cbb1d495c8b67b34d398300000000000000000000053d', + '0xf22ff21e17157340575158ad7394e068048dd98b0000000000000000000004b8', + '0xf57c794f42da72b38c8f610ff3b5e8502e48cbde00000000000000000000055c', + '0xf71d0774b214c4cf51e33eb3d30ef98132e4dbaa00000000000000000000046e', + '0xfa24a90a3f2bbe5feea92b95cd0d14ce709649f900000000000000000000058f', + '0xfd11ccdbdb7ab91cb9427a6d6bf570c95876d1950000000000000000000004c2', + '0xfebb0bbf162e64fb9d0dfe186e517d84c395f016000000000000000000000502', + '0xfef969638c52899f91781f1be594af6f40b99bad00000000000000000000047b', + '0x02e139d53ebf4033bf78ab66c6a1e7f1f204487f0002000000000000000009f9', + '0x03090a9811181a2afe830a3a0b467698ccf3a8b1000000000000000000000bf5', + '0x0320c1c5b6df19a194d48882aaec1c72940081d9000000000000000000000a7d', + '0x04b54ea92d73de2d62d651db7d9778f0c49157d8000200000000000000000ba2', + '0x0503dd6b2d3dd463c9bef67fb5156870af63393e00000000000000000000042e', + '0x0889b240a5876aae745ac19f1771853671dc5d36000000000000000000000b3f', + '0x0bc54e914f53f98d16035f4f0d948f3e09c2fac0000200000000000000000bac', + '0x0c06e87c7b88d998f645b91c1f53b51294b12bca000100000000000000000bb9', + '0x10b040038f87219d9b42e025e3bd9b8095c87dd9000000000000000000000b11', + '0x117a3d474976274b37b7b94af5dcade5c90c6e85000000000000000000000aca', + '0x11884da90fb4221b3aa288a7741c51ec4fc43b2f000000000000000000000a5f', + '0x1379b816b9be611431d693290289c204720ca56d000100000000000000000b6f', + '0x150e7b885bdfce974f2abe88a72fdbd692175c6f0002000000000000000009fd', + '0x178e029173417b1f9c8bc16dcec6f697bc323746000000000000000000000758', + '0x1aafc31091d93c3ff003cff5d2d8f7ba2e7284250000000000000000000003b3', + '0x216690738aac4aa0c4770253ca26a28f0115c595000000000000000000000b2c', + '0x216d6db0c28204014618482c369d7fbf0a8f3232000100000000000000000b60', + '0x230ecdb2a7cee56d6889965a023aa0473d6da507000000000000000000000bf3', + '0x252ff6a3a6fd7b5e8e999de8e3f5c3b306ed1401000200000000000000000bec', + '0x25e57f4612912614e6c99616bd2abb9b5ae71e99000000000000000000000bf0', + '0x2645b13fd2c5295296e94a76280b968bdcbbdfed000000000000000000000c11', + '0x284eb68520c8fa83361c1a3a5910aec7f873c18b000000000000000000000ac9', + '0x2c8dbe8eb86135d9f2f26d196748c088d47f73e7000200000000000000000a29', + '0x31bccf9e28b94e5dacebaa67fe8bc1603cecd904000000000000000000000a01', + '0x341068a547c3cde3c09e338714010dd01b32f93f000200000000000000000a34', + '0x3db543faf7a92052de7860c5c9debabee59ed5bd000000000000000000000a62', + '0x3dd0843a028c86e0b760b1a76929d1c5ef93a2dd00000000000000000000070d', + '0x3efb91c4f9b103ee45885695c67794591916f34e000200000000000000000b43', + '0x402cfdb7781fa85d52f425352661128250b79e12000000000000000000000be3', + '0x43894de14462b421372bcfe445fa51b1b4a0ff3d000000000000000000000b36', + '0x4739e50b59b552d490d3fdc60d200977a38510c0000000000000000000000b10', + '0x48e6b98ef6329f8f0a30ebb8c7c960330d64808500000000000000000000075b', + '0x4a0b73f0d13ff6d43e304a174697e3d5cfd310a400020000000000000000091c', + '0x4a77ef015ddcd972fd9ba2c7d5d658689d090f1a000000000000000000000b38', + '0x4ae3661afa119892f0cc8c43edaf6a94989ac171000000000000000000000c06', + '0x4ccb966d8246240afb7a1a24628efb930870b1c40002000000000000000009fc', + '0x52cc8389c6b93d740325729cc7c958066cee4262000000000000000000000b0f', + '0x5b77107fcdf2b41903bab2bc555d4fc14cf7667d000000000000000000000b32', + '0x5bae72b75caab1f260d21bc028c630140607d6e8000000000000000000000ac6', + '0x600bd01b6526611079e12e1ff93aba7a3e34226f0000000000000000000009e4', + '0x63ce19ccd39930725b8a3d2733627804718ab83d000000000000000000000bf2', + '0x64efad69f099813021b41f4cac6e749fd55e188f000000000000000000000b39', + '0x6933ec1ca55c06a894107860c92acdfd2dd8512f000000000000000000000428', + '0x6abe4e7a497b8293c258389c1b00d177e4f257ed00010000000000000000080d', + '0x6c8c7fc50247a47038015eb1fd5dc105d05dafba000200000000000000000ba0', + '0x7079a25dec33be61bbd81b2fb69b468e80d3e72c0000000000000000000009ff', + '0x71bd10c2a590b5858f5576550c163976a48af906000000000000000000000b27', + '0x7c82a23b4c48d796dee36a9ca215b641c6a8709d000000000000000000000acd', + '0x7f4f4942f2a14b6ab7b08b10ada1aacede4ee8d4000200000000000000000b44', + '0x86aef31951e0a3a54333bd9e72f9a95587d058c5000200000000000000000912', + '0x882c7a84231484b3e9f3fd45ac04b1eb5d35b076000200000000000000000a91', + '0x894c82800526e0391e709c0983a5aea3718b7f6d000000000000000000000ac5', + '0x89b28a9494589b09dbccb69911c189f74fdadc5a000000000000000000000b33', + '0x89bb15076c9f2d86aa98ec6cffc1a71e31c38953000000000000000000000bf1', + '0x89f1146fee52b5d9166e9c83cc388b6d8f69f1380001000000000000000009e7', + '0x8a819a4cabd6efcb4e5504fe8679a1abd831dd8f00000000000000000000042d', + '0x8b58a1e7fff52001c22386c2918d45938a6a9be30001000000000000000008d9', + '0x8b8225bfedebaf1708c55743acb4ad43fd4d0f21000200000000000000000918', + '0x8fbd0f8e490735cfc3abf4f29cbddd5c3289b9a7000000000000000000000b5b', + '0x8fd39252d683fdb60bddd4df4b53c9380b496d59000200000000000000000b45', + '0x9321e2250767d79bab5aa06daa8606a2b3b7b4c5000000000000000000000bf4', + '0x949a12b95ec5b80c375b98963a5d6b33b0d0efff0002000000000000000009fe', + '0x9a020bdc2faff5bd24c6acc2020d01ff9f2c627a000000000000000000000ae2', + '0x9cf9358300e34bf9373d30129a1e718d8d058b54000200000000000000000913', + '0x9e34631547adcf2f8cefa0f5f223955c7b137571000000000000000000000ad5', + '0xa5a935833f6a5312715f182733eab088452335d7000100000000000000000bee', + '0xa5fe91dde37d8bf2dacacc0168b115d28ed03f84000000000000000000000b35', + '0xa8bf1c584519be0184311c48adbdc4c15cb2e8c1000000000000000000000bf6', + '0xab269164a10fab22bc87c39946da06c870b172d6000000000000000000000bfc', + '0xac2cae8d2f78a4a8f92f20dbe74042cd0a8d5af3000000000000000000000be2', + '0xae646817e458c0be890b81e8d880206710e3c44e000000000000000000000acb', + '0xaef2c171dbe64b0c18977e16e70bfd29d4ee0256000000000000000000000ac8', + '0xb0c830dceb4ef55a60192472c20c8bf19df03488000000000000000000000be1', + '0xb266ac3b7c98d7bcb28731dac0ef42dba1b276be000000000000000000000be4', + '0xb371aa09f5a110ab69b39a84b5469d29f9b22b76000000000000000000000b37', + '0xb3d658d5b95bf04e2932370dd1ff976fe18dd66a000000000000000000000ace', + '0xb54b2125b711cd183edd3dd09433439d5396165200000000000000000000075e', + '0xb59be8f3c85a9dd6e2899103b6fbf6ea405b99a4000000000000000000000b34', + '0xb878ecce26838fbba4f78cb5b791a0e09152c067000000000000000000000427', + '0xb973ca96a3f0d61045f53255e319aedb6ed4924000000000000000000000042f', + '0xbd4e35784c832d0f9049b54cb3609e5907c5b495000100000000000000000b14', + '0xc55ec796a4debe625d95436a3531f4950b11bdcf000000000000000000000b3e', + '0xc7e6389e364f4275eb442ef215ed21877028e2af000000000000000000000ac7', + '0xc83b55bbd005f1f84906545fcdb145dee53523e0000200000000000000000b30', + '0xcb21a9e647c95127ed784626485b3104cb28d0e7000000000000000000000425', + '0xd00f9ca46ce0e4a63067c4657986f0167b0de1e5000000000000000000000b42', + '0xd2f3b9e67c69762dd1c88f1d3dadd1649a190761000200000000000000000bf7', + '0xd4accb350f9cf59fe3cf7a5ee6ed9ace6a568ea9000200000000000000000b75', + '0xda1cd1711743e57dd57102e9e61b75f3587703da000000000000000000000acc', + '0xdae301690004946424e41051ace1791083be42a1000000000000000000000b40', + '0xde0a77ab6689b980c30306b10f9131a007e1af81000200000000000000000ba1', + '0xe051605a83deae38d26a7346b100ef1ac2ef8a0b0000000000000000000003ce', + '0xe1fb90d0d3b47e551d494d7ebe8f209753526b01000000000000000000000ac4', + '0xe2272cddb2cc408e79e02a73d1db9acc24a843d5000200000000000000000ba7', + '0xe2dc0e0f2c358d6e31836dee69a558ab8d1390e70000000000000000000009fa', + '0xe4885ed2818cc9e840a25f94f9b2a28169d1aea7000000000000000000000b29', + '0xe6909c2f18a29d97217a6146f045e1780606991f000100000000000000000bfe', + '0xe78b25c06db117fdf8f98583cdaaa6c92b79e917000000000000000000000b2b', + '0xea11645ac7d8f2def94c9d8d86bd766296c9b6b6000000000000000000000b3a', + '0xeb480dbbdd921cd6c359e4cc4c65ddea6395e2a1000200000000000000000946', + '0xed35f28f837e96f81240ebb82e0e3f518c7e8a2f000100000000000000000bb5', + '0xf0211cceebe6fcc45052b4e57ee95d233f5669d2000100000000000000000c01', + '0xf22a66046b5307842f21b311ecb4c462c24c0635000000000000000000000b15', + '0xf28f17be00f8ca3c9b7f66a4aad5513757fb3341000200000000000000000b5a', + '0xf42ed61450458ee4620f5ef4f29adb25a6ef0fb6000000000000000000000bf8', + '0xf48f01dcb2cbb3ee1f6aab0e742c2d3941039d56000000000000000000000445', + '0xf93579002dbe8046c43fefe86ec78b1112247bb8000000000000000000000759', + '0xf984eb2b8a7ef780245a797a2fccd82f346409ca000000000000000000000a59', + '0xfa2c0bd8327c99db5bde4c9e9e5cbf30946351bb000000000000000000000948', + '0xff4ce5aaab5a627bf82f4a571ab1ce94aa365ea600000000000000000000075a', + '0x1ac55c31dac78ca943cb8ebfca5945ce09e036e2000000000000000000000024', + '0x225e0047671939a8d78e08ebd692788abe63f15c000000000000000000000009', + '0x41211bba6d37f5a74b22e667533f080c7c7f3f1300000000000000000000000b', + '0x4de21b365d6543661d0e105e579a34b963862497000200000000000000000045', + '0x581ec1f5e7ced12b186deae32256adb53bdd5b08000000000000000000000001', + '0x66f33ae36dd80327744207a48122f874634b3ada000100000000000000000013', + '0xa3ed6f78edc29f69df8a0d16b1d1ccf9871f918c000000000000000000000032', + '0xa611a551b95b205ccd9490657acf7899daee5db700000000000000000000002e', + '0xb95829adbacd8af89e291dee78bc09e24de51d6b000000000000000000000043', + '0xb973ca96a3f0d61045f53255e319aedb6ed49240000200000000000000000011', + '0xba1a5b19d09a79dada039b1f974015c5a989d5fd000100000000000000000046', + '0xbb9cd48d33033f5effbedec9dd700c7d7e1dcf5000000000000000000000000e', + '0xd16f72b02da5f51231fde542a8b9e2777a478c8800000000000000000000000f', + '0xd4015683b8153666190e0b2bec352580ebc4caca00000000000000000000000d', + '0xe15cac1df3621e001f76210ab12a7f1a1691481f000000000000000000000044', + '0xe7f88d7d4ef2eb18fcf9dd7216ba7da1c46f3dd600000000000000000000000a', + '0xf48f01dcb2cbb3ee1f6aab0e742c2d3941039d56000200000000000000000012', + '0xfedb19ec000d38d92af4b21436870f115db22725000000000000000000000010', + '0xffff76a3280e95dc855696111c2562da09db2ac000000000000000000000000c', + '0x00fcd3d55085e998e291a0005cedecf58ac14c4000020000000000000000047f', + '0x077794c30afeccdf5ad2abc0588e8cee7197b71a000000000000000000000352', + '0x117a3d474976274b37b7b94af5dcade5c90c6e85000000000000000000000381', + '0x11884da90fb4221b3aa288a7741c51ec4fc43b2f000000000000000000000353', + '0x19b1c92631405a0a9495ccba0becf4f2e8e908bd000000000000000000000410', + '0x1e550b7764da9638fdd32c8a701364de31f45ee800000000000000000000047c', + '0x1fa7f727934226aedab636d62a084931b97d366b000000000000000000000411', + '0x23ca0306b21ea71552b148cf3c4db4fc85ae19290000000000000000000000c9', + '0x284eb68520c8fa83361c1a3a5910aec7f873c18b000000000000000000000380', + '0x2a96254ca32020b20ed3506f8f75318da24709f9000200000000000000000456', + '0x36942963e3b6f37ecc45a4e72349558514233f0000000000000000000000048a', + '0x3f53a862919ccfa023cb6ace91378a79fb0f6bf500000000000000000000040f', + '0x40af308e3d07ec769d85eb80afb116525ff4ac99000000000000000000000485', + '0x418de00ae109e6f874d872658767866d680eaa1900000000000000000000047d', + '0x45c4d1376943ab28802b995acffc04903eb5223f000000000000000000000470', + '0x4689122d360c4725d244c5cfea22861333d862e6000100000000000000000468', + '0x4739e50b59b552d490d3fdc60d200977a38510c0000000000000000000000409', + '0x49a0e3334496442a9706e481617724e7e37eaa080000000000000000000003ff', + '0x519cce718fcd11ac09194cff4517f12d263be067000000000000000000000382', + '0x52cc8389c6b93d740325729cc7c958066cee4262000000000000000000000408', + '0x567ecfcb22205d279bb8eed3e066989902bf03d5000000000000000000000452', + '0x585d95df0231fa08aeee35ff0c16b92fd0ecdc3300020000000000000000045f', + '0x5a7f39435fd9c381e4932fa2047c9a5136a5e3e7000000000000000000000400', + '0x5bae72b75caab1f260d21bc028c630140607d6e8000000000000000000000350', + '0x6cb787a419c3e6ee2e9ff365856c29cd10659113000000000000000000000474', + '0x7c82a23b4c48d796dee36a9ca215b641c6a8709d000000000000000000000406', + '0x81fc12c60ee5b753cf5fd0adc342dfb5f3817e3200000000000000000000035d', + '0x894c82800526e0391e709c0983a5aea3718b7f6d00000000000000000000034f', + '0x970712708a08e8fb152be4d81b2dc586923f5369000200000000000000000479', + '0x9bf7c3b63c77b4b4f2717776f15a4bec1b532a280000000000000000000000c8', + '0x9cebf13bb702f253abf1579294694a1edad00eaa000000000000000000000486', + '0x9e34631547adcf2f8cefa0f5f223955c7b137571000000000000000000000407', + '0x9fb7d6dcac7b6aa20108bad226c35b85a9e31b63000200000000000000000412', + '0xa1ea76c42b2938cfa9abea12357881006c52851300000000000000000000048f', + '0xa50f89e9f439fde2a6fe05883721a00475da3c4500000000000000000000048b', + '0xa612b6aed2e7ca1a3a4f23fbca9128461bbb7718000000000000000000000274', + '0xa8af146d79ac0bb981e4e0d8b788ec5711b1d5d000000000000000000000047b', + '0xad28940024117b442a9efb6d0f25c8b59e1c950b00000000000000000000046f', + '0xae646817e458c0be890b81e8d880206710e3c44e00000000000000000000039d', + '0xaef2c171dbe64b0c18977e16e70bfd29d4ee0256000000000000000000000351', + '0xbbf9d705b75f408cfcaee91da32966124d2c6f7d00000000000000000000047e', + '0xbd724eb087d4cc0f61a5fed1fffaf937937e14de000000000000000000000473', + '0xbe0f30217be1e981add883848d0773a86d2d2cd4000000000000000000000471', + '0xc46be4b8bb6b5a3d3120660efae9c5416318ed40000000000000000000000472', + '0xc69771058481551261709d8db44977e9afde645000010000000000000000042a', + '0xc6eee8cb7643ec2f05f46d569e9ec8ef8b41b389000000000000000000000475', + '0xcba9ff45cfb9ce238afde32b0148eb82cbe635620000000000000000000003fd', + '0xcf8b555b7754556cf2ac2165e77ee23ed8517d7900020000000000000000045e', + '0xd0dc20e6342db2de82692b8dc842301ff9121805000200000000000000000454', + '0xd3d5d45f4edf82ba0dfaf061d230766032a10e07000200000000000000000413', + '0xd6d20527c7b0669989ee082b9d3a1c63af742290000000000000000000000483', + '0xda1cd1711743e57dd57102e9e61b75f3587703da0000000000000000000003fc', + '0xe1fb90d0d3b47e551d494d7ebe8f209753526b0100000000000000000000034e', + '0xee02583596aee94cccb7e8ccd3921d955f17982a00000000000000000000040a', + '0xf984eb2b8a7ef780245a797a2fccd82f346409ca00000000000000000000034d', + '0xff8f84e8c87532af96aef5582ee451572233678b000200000000000000000478', + '0x054e7b0c73e1ee5aed6864fa511658fc2b54bcaa000000000000000000000015', + '0x3f1a2c4a3a751f6626bd90ef16e104f0772d4d6b00020000000000000000001b', + '0x7275c131b1f67e8b53b4691f92b0e35a4c1c6e22000000000000000000000010', + '0xa154009870e9b6431305f19b09f9cfd7284d4e7a000000000000000000000013', + '0xa1d14d922a575232066520eda11e27760946c991000000000000000000000012', + '0xa826a114b0c7db4d1ff4a4be845a78998c64564c000000000000000000000008', + '0xea67626e1f0b59e0d172a04f5702ef90bcdf440c00000000000000000000000f', + '0xeb496161099d45b3ea4892408ef745c6182eb56e00000000000000000000000e', + '0xece571847897fd61e764d455dc15cf1cd9de8d6f000000000000000000000014', + '0xed3e2f496cbcd8e212192fb8d1499842f04a0d19000000000000000000000009', + '0x02c9dcb975262719a61f9b40bdf0987ead9add3a000000000000000000000006', + '0x16c9a4d841e88e52b51936106010f27085a529ec00000000000000000000000c', + '0x32be2d0ddeaf3333501b24a28668ce373ba8e763000200000000000000000014', + '0x32f03464fdf909fdf3798f87ff3712b10c59bd86000000000000000000000005', + '0x4b718e0e2fea1da68b763cd50c446fba03ceb2ea00000000000000000000000b', + '0x68a69c596b3839023c0e08d09682314f582314e5000200000000000000000011', + '0x6f34a44fce1506352a171232163e7716dd073ade000200000000000000000015', + '0x9e2d87f904862671eb49cb358e74284762cc9f42000200000000000000000013', + '0xac4b72c01072a52b73ca71105504f1372efcce0d000000000000000000000003', + '0xbfd65c6160cfd638a85c645e6e6d8acac5dac935000000000000000000000004', + '0xe274c9deb6ed34cfe4130f8d0a8a948dea5bb28600000000000000000000000d', +]; From b355ef5d9f7324a9c852741688d9c5c7c1fb41e5 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 10:08:41 +0100 Subject: [PATCH 06/23] chore: remove Element from supported types. --- src/data/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/types.ts b/src/data/types.ts index 34476dac..1aebbc64 100644 --- a/src/data/types.ts +++ b/src/data/types.ts @@ -12,7 +12,6 @@ export type SupportedRawPoolTypes = | 'MetaStable' | 'ComposableStable' | 'StablePhantom' - | 'Element' | 'FX' | 'Gyro2' | 'GyroE'; From c9111eece722100843e25829bb3d88cfdd2c5353 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 10:14:33 +0100 Subject: [PATCH 07/23] fix: Replace dataQueries with multicallV3. Remove tokenRates and dataQueries ABI. Update onChainPoolDataEnricher. Removed queryFailed as not used. --- src/abi/balancerPoolDataQueries.ts | 413 ---------------- src/abi/index.ts | 3 +- src/abi/multicallV3.ts | 440 ++++++++++++++++++ src/abi/tokenRates.ts | 20 - src/data/enrichers/onChainPoolDataEnricher.ts | 401 ++-------------- .../onChainPoolDataViaMulticallEnricher.ts | 125 ----- src/data/onChainPoolDataViaMulticall.ts | 231 --------- src/data/onChainPoolDataViaReadContract.ts | 432 +++++++++++++++++ src/sor.ts | 13 +- src/utils/constants.ts | 16 +- test/fxPool.integration.test.ts | 9 +- test/gyroEV2Pool.integration.test.ts | 12 +- test/sor.test.ts | 33 +- 13 files changed, 932 insertions(+), 1216 deletions(-) delete mode 100644 src/abi/balancerPoolDataQueries.ts create mode 100644 src/abi/multicallV3.ts delete mode 100644 src/abi/tokenRates.ts delete mode 100644 src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts delete mode 100644 src/data/onChainPoolDataViaMulticall.ts create mode 100644 src/data/onChainPoolDataViaReadContract.ts diff --git a/src/abi/balancerPoolDataQueries.ts b/src/abi/balancerPoolDataQueries.ts deleted file mode 100644 index 2ff1cf4c..00000000 --- a/src/abi/balancerPoolDataQueries.ts +++ /dev/null @@ -1,413 +0,0 @@ -export const balancerPoolDataQueriesAbi = [ - { - inputs: [ - { - internalType: 'contract IVault', - name: '_vault', - type: 'address', - }, - ], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'poolAddresses', - type: 'address[]', - }, - ], - name: 'getAmpForPools', - outputs: [ - { - internalType: 'uint256[]', - name: '', - type: 'uint256[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'poolAddresses', - type: 'address[]', - }, - ], - name: 'getInRecoveryModeForPools', - outputs: [ - { - internalType: 'bool[]', - name: '', - type: 'bool[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'poolAddresses', - type: 'address[]', - }, - ], - name: 'getIsPausedForPools', - outputs: [ - { - internalType: 'bool[]', - name: '', - type: 'bool[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'poolAddresses', - type: 'address[]', - }, - ], - name: 'getLinearTargetsForPools', - outputs: [ - { - internalType: 'uint256[][]', - name: '', - type: 'uint256[][]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'poolAddresses', - type: 'address[]', - }, - ], - name: 'getNormalizedWeightsForPools', - outputs: [ - { internalType: 'uint256[][]', name: '', type: 'uint256[][]' }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { internalType: 'bytes32[]', name: 'poolIds', type: 'bytes32[]' }, - { - components: [ - { - internalType: 'bool', - name: 'loadTokenBalanceUpdatesAfterBlock', - type: 'bool', - }, - { - internalType: 'bool', - name: 'loadTotalSupply', - type: 'bool', - }, - { - internalType: 'bool', - name: 'loadSwapFees', - type: 'bool', - }, - { - internalType: 'bool', - name: 'loadLinearWrappedTokenRates', - type: 'bool', - }, - { - internalType: 'bool', - name: 'loadLinearTargets', - type: 'bool', - }, - { - internalType: 'bool', - name: 'loadNormalizedWeights', - type: 'bool', - }, - { - internalType: 'bool', - name: 'loadScalingFactors', - type: 'bool', - }, - { - internalType: 'bool', - name: 'loadAmps', - type: 'bool', - }, - { - internalType: 'bool', - name: 'loadRates', - type: 'bool', - }, - { - internalType: 'uint256', - name: 'blockNumber', - type: 'uint256', - }, - { - internalType: 'enum TotalSupplyType[]', - name: 'totalSupplyTypes', - type: 'uint8[]', - }, - { - internalType: 'enum SwapFeeType[]', - name: 'swapFeeTypes', - type: 'uint8[]', - }, - { - internalType: 'uint256[]', - name: 'linearPoolIdxs', - type: 'uint256[]', - }, - { - internalType: 'uint256[]', - name: 'weightedPoolIdxs', - type: 'uint256[]', - }, - { - internalType: 'uint256[]', - name: 'scalingFactorPoolIdxs', - type: 'uint256[]', - }, - { - internalType: 'uint256[]', - name: 'ampPoolIdxs', - type: 'uint256[]', - }, - { - internalType: 'uint256[]', - name: 'ratePoolIdxs', - type: 'uint256[]', - }, - ], - internalType: 'struct PoolDataQueryConfig', - name: 'config', - type: 'tuple', - }, - ], - name: 'getPoolData', - outputs: [ - { - internalType: 'uint256[][]', - name: 'balances', - type: 'uint256[][]', - }, - { - internalType: 'uint256[]', - name: 'totalSupplies', - type: 'uint256[]', - }, - { - internalType: 'uint256[]', - name: 'swapFees', - type: 'uint256[]', - }, - { - internalType: 'uint256[]', - name: 'linearWrappedTokenRates', - type: 'uint256[]', - }, - { - internalType: 'uint256[][]', - name: 'linearTargets', - type: 'uint256[][]', - }, - { - internalType: 'uint256[][]', - name: 'weights', - type: 'uint256[][]', - }, - { - internalType: 'uint256[][]', - name: 'scalingFactors', - type: 'uint256[][]', - }, - { - internalType: 'uint256[]', - name: 'amps', - type: 'uint256[]', - }, - { - internalType: 'uint256[]', - name: 'rates', - type: 'uint256[]', - }, - { - internalType: 'uint256[]', - name: 'ignoreIdxs', - type: 'uint256[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes32[]', - name: 'poolIds', - type: 'bytes32[]', - }, - { - components: [ - { - internalType: 'bool', - name: 'loadInRecoveryMode', - type: 'bool', - }, - { - internalType: 'bool', - name: 'loadIsPaused', - type: 'bool', - }, - ], - internalType: 'struct PoolStatusQueryConfig', - name: 'config', - type: 'tuple', - }, - ], - name: 'getPoolStatus', - outputs: [ - { - internalType: 'bool[]', - name: 'isPaused', - type: 'bool[]', - }, - { - internalType: 'bool[]', - name: 'inRecoveryMode', - type: 'bool[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes32[]', - name: 'poolIds', - type: 'bytes32[]', - }, - { - internalType: 'uint256', - name: 'blockNumber', - type: 'uint256', - }, - ], - name: 'getPoolTokenBalancesWithUpdatesAfterBlock', - outputs: [ - { - internalType: 'uint256[][]', - name: '', - type: 'uint256[][]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'poolAddresses', - type: 'address[]', - }, - ], - name: 'getRateForPools', - outputs: [ - { - internalType: 'uint256[]', - name: '', - type: 'uint256[]', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'poolAddresses', - type: 'address[]', - }, - ], - name: 'getScalingFactorsForPools', - outputs: [ - { internalType: 'uint256[][]', name: '', type: 'uint256[][]' }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'poolAddresses', - type: 'address[]', - }, - { - internalType: 'enum SwapFeeType[]', - name: 'swapFeeTypes', - type: 'uint8[]', - }, - ], - name: 'getSwapFeePercentageForPools', - outputs: [{ internalType: 'uint256[]', name: '', type: 'uint256[]' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'poolAddresses', - type: 'address[]', - }, - { - internalType: 'enum TotalSupplyType[]', - name: 'totalSupplyTypes', - type: 'uint8[]', - }, - ], - name: 'getTotalSupplyForPools', - outputs: [{ internalType: 'uint256[]', name: '', type: 'uint256[]' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address[]', - name: 'poolAddresses', - type: 'address[]', - }, - ], - name: 'getWrappedTokenRateForLinearPools', - outputs: [{ internalType: 'uint256[]', name: '', type: 'uint256[]' }], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'vault', - outputs: [ - { internalType: 'contract IVault', name: '', type: 'address' }, - ], - stateMutability: 'view', - type: 'function', - }, -] as const; diff --git a/src/abi/index.ts b/src/abi/index.ts index db6ac5cb..21dcc51f 100644 --- a/src/abi/index.ts +++ b/src/abi/index.ts @@ -1,5 +1,4 @@ export * from './vault'; export * from './balancerQueries'; -export * from './balancerPoolDataQueries'; export * from './erc20'; -export * from './tokenRates'; +export * from './multicallV3'; diff --git a/src/abi/multicallV3.ts b/src/abi/multicallV3.ts new file mode 100644 index 00000000..0950f8f0 --- /dev/null +++ b/src/abi/multicallV3.ts @@ -0,0 +1,440 @@ +export const multicall = [ + { + 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', + }, +]; diff --git a/src/abi/tokenRates.ts b/src/abi/tokenRates.ts deleted file mode 100644 index 8270fe1c..00000000 --- a/src/abi/tokenRates.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const tokenRatesFragmentAbi = [ - { - inputs: [], - name: 'getTokenRates', - outputs: [ - { - internalType: 'uint256', - name: 'rate0', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'rate1', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, -] as const; diff --git a/src/data/enrichers/onChainPoolDataEnricher.ts b/src/data/enrichers/onChainPoolDataEnricher.ts index 5c1c3df9..4c46890d 100644 --- a/src/data/enrichers/onChainPoolDataEnricher.ts +++ b/src/data/enrichers/onChainPoolDataEnricher.ts @@ -1,5 +1,10 @@ -import { Address, createPublicClient, formatUnits, Hex, http } from 'viem'; -import { balancerPoolDataQueriesAbi, tokenRatesFragmentAbi } from '../../abi/'; +import { + Address, + createPublicClient, + formatUnits, + http, + PublicClient, +} from 'viem'; import { GetPoolsResponse, PoolDataEnricher, @@ -8,15 +13,9 @@ import { RawWeightedPoolToken, } from '../types'; -import { - CHAINS, - getPoolAddress, - poolHasActualSupply, - poolHasPercentFee, - poolHasVirtualSupply, - poolIsLinearPool, -} from '../../utils'; +import { CHAINS } from '../../utils'; import { HumanAmount, SwapOptions } from '../../types'; +import { fetchAdditionalPoolData } from '../onChainPoolDataViaReadContract'; export interface OnChainPoolData { id: string; @@ -34,212 +33,32 @@ export interface OnChainPoolData { isPaused: boolean; inRecoveryMode: boolean; - - queryFailed: boolean; -} - -enum TotalSupplyType { - TOTAL_SUPPLY = 0, - VIRTUAL_SUPPLY = 1, - ACTUAL_SUPPLY = 2, -} - -enum SwapFeeType { - SWAP_FEE_PERCENTAGE = 0, - PERCENT_FEE = 1, -} - -interface OnChainPoolDataQueryConfig { - loadTokenBalances: 'all' | 'updates-after-block' | 'none'; - blockNumber: bigint; - loadTotalSupply: boolean; - loadSwapFees: boolean; - loadLinearWrappedTokenRates: boolean; - loadLinearTargets: boolean; - loadWeightsForPools: { - poolIds?: string[]; - poolTypes?: string[]; - }; - loadAmpForPools: { - poolIds?: string[]; - poolTypes?: string[]; - }; - loadScalingFactorForPools: { - poolIds?: string[]; - poolTypes?: string[]; - }; - loadTokenRatesForPools: { - poolIds?: string[]; - poolTypes?: string[]; - poolTypeVersions?: number[]; - }; - loadRatesForPools: { - poolIds?: string[]; - poolTypes?: string[]; - }; - - loadInRecoveryMode: boolean; - loadIsPaused: boolean; } export class OnChainPoolDataEnricher implements PoolDataEnricher { - private readonly config: OnChainPoolDataQueryConfig; + private readonly client: PublicClient; constructor( private readonly chainId: number, private readonly rpcUrl: string, - private readonly balancerPoolQueriesAddress: Address, - config?: Partial, + private readonly multicallAddress: Address, ) { - this.config = { - loadTokenBalances: 'updates-after-block', - blockNumber: 0n, - loadTotalSupply: true, - loadSwapFees: true, - loadLinearWrappedTokenRates: true, - loadLinearTargets: true, - loadWeightsForPools: {}, - loadAmpForPools: {}, - loadScalingFactorForPools: {}, - loadRatesForPools: {}, - loadTokenRatesForPools: {}, - loadInRecoveryMode: true, - loadIsPaused: true, - ...config, - }; + this.client = createPublicClient({ + transport: http(this.rpcUrl), + chain: CHAINS[this.chainId], + }); } public async fetchAdditionalPoolData( data: GetPoolsResponse, options: SwapOptions, ): Promise { - const rawPools = data.pools; - - if (rawPools.length === 0) { - return []; - } - - const { - poolIds, - weightedPoolIdxs, - ampPoolIdxs, - linearPoolIdxs, - totalSupplyTypes, - scalingFactorPoolIdxs, - ratePoolIdxs, - swapFeeTypes, - tokenRatesPoolIdxs, - } = this.getPoolDataQueryParams(data); - - const client = createPublicClient({ - transport: http(this.rpcUrl), - chain: CHAINS[this.chainId], - }); - - const [ - balances, - totalSupplies, - swapFees, - linearWrappedTokenRates, - linearTargets, - weights, - scalingFactors, - amps, - rates, - ignoreIdxs, - ] = await client.readContract({ - address: this.balancerPoolQueriesAddress, - abi: balancerPoolDataQueriesAbi, - functionName: 'getPoolData', - args: [ - poolIds, - { - loadTokenBalanceUpdatesAfterBlock: - this.config.loadTokenBalances !== 'none', - loadTotalSupply: this.config.loadTotalSupply, - loadSwapFees: this.config.loadSwapFees, - loadLinearWrappedTokenRates: linearPoolIdxs.length > 0, - loadLinearTargets: linearPoolIdxs.length > 0, - loadNormalizedWeights: weightedPoolIdxs.length > 0, - loadScalingFactors: scalingFactorPoolIdxs.length > 0, - loadAmps: ampPoolIdxs.length > 0, - loadRates: ratePoolIdxs.length > 0, - blockNumber: - data.syncedToBlockNumber && - this.config.loadTokenBalances === 'updates-after-block' - ? data.syncedToBlockNumber - : 0n, - totalSupplyTypes, - swapFeeTypes, - linearPoolIdxs, - weightedPoolIdxs, - scalingFactorPoolIdxs, - ampPoolIdxs, - ratePoolIdxs, - }, - ], - blockNumber: options.block, - }); - - const [isPaused, inRecoveryMode] = await client.readContract({ - address: this.balancerPoolQueriesAddress, - abi: balancerPoolDataQueriesAbi, - functionName: 'getPoolStatus', - args: [ - poolIds, - { - loadInRecoveryMode: this.config.loadInRecoveryMode, - loadIsPaused: this.config.loadIsPaused, - }, - ], - blockNumber: options.block, - }); - - let tokenRates: (readonly [bigint, bigint] | undefined)[] = []; - if (tokenRatesPoolIdxs.length > 0) { - const call = { - abi: tokenRatesFragmentAbi, - functionName: 'getTokenRates', - } as const; - const poolAddressesWithTokenRates: readonly Address[] = poolIds - .filter((_, i) => tokenRatesPoolIdxs.includes(BigInt(i))) - .map((id) => getPoolAddress(id) as Address); - const results = await client.multicall({ - contracts: poolAddressesWithTokenRates.map((a) => ({ - address: a, - ...call, - })), - blockNumber: options.block, - }); - tokenRates = results.map((r) => r.result); - } - - return poolIds.map((_poolId, i) => ({ - id: poolIds[i], - balances: balances[i], - totalSupply: totalSupplies[i], - weights: weightedPoolIdxs.includes(BigInt(i)) - ? weights[weightedPoolIdxs.indexOf(BigInt(i))] - : undefined, - amp: ampPoolIdxs.includes(BigInt(i)) - ? amps[ampPoolIdxs.indexOf(BigInt(i))] - : undefined, - wrappedTokenRate: linearPoolIdxs.includes(BigInt(i)) - ? linearWrappedTokenRates[linearPoolIdxs.indexOf(BigInt(i))] - : undefined, - linearTargets: linearPoolIdxs.includes(BigInt(i)) - ? linearTargets[linearPoolIdxs.indexOf(BigInt(i))] - : undefined, - scalingFactors: scalingFactors[i], - rate: rates[i], - swapFee: swapFees[i], - tokenRates: tokenRatesPoolIdxs.includes(BigInt(i)) - ? tokenRates[tokenRatesPoolIdxs.indexOf(BigInt(i))] - : undefined, - inRecoveryMode: inRecoveryMode[i], - isPaused: isPaused[i], - queryFailed: ignoreIdxs.includes(BigInt(i)), - })); + return fetchAdditionalPoolData( + this.multicallAddress, + data.pools, + this.client, + options, + ); } public enrichPoolsWithData( @@ -291,173 +110,22 @@ export class OnChainPoolDataEnricher implements PoolDataEnricher { formatUnits(tokenRate, 18) as HumanAmount, ) : undefined, + lowerTarget: data?.linearTargets + ? (formatUnits(data.linearTargets[0], 18) as HumanAmount) + : 'lowerTarget' in pool + ? pool.lowerTarget + : undefined, + upperTarget: data?.linearTargets + ? (formatUnits(data.linearTargets[1], 18) as HumanAmount) + : 'upperTarget' in pool + ? pool.upperTarget + : undefined, + inRecoveryMode: data?.inRecoveryMode || false, + isPaused: data?.isPaused || false, }; }); } - private getPoolDataQueryParams(data: GetPoolsResponse) { - const poolIds: Hex[] = []; - const totalSupplyTypes: TotalSupplyType[] = []; - const linearPoolIdxs: bigint[] = []; - const weightedPoolIdxs: bigint[] = []; - const ampPoolIdxs: bigint[] = []; - const scalingFactorPoolIdxs: bigint[] = []; - const ratePoolIdxs: bigint[] = []; - const swapFeeTypes: SwapFeeType[] = []; - const tokenRatesPoolIdxs: bigint[] = []; - - const { - loadScalingFactorForPoolTypes, - loadScalingFactorForPoolIds, - loadWeightsForPoolTypes, - loadAmpForPoolTypes, - loadAmpForPoolIds, - loadWeightsForPoolIds, - loadTokenRatesForPoolIds, - loadTokenRatesForPoolTypes, - loadTokenRatesForPoolTypeVersions, - loadRatesForPoolIds, - loadRatesForPoolTypes, - } = this.getMergedFilterConfig(data); - - for (let i = 0; i < data.pools.length; i++) { - const pool = data.pools[i]; - - poolIds.push(pool.id); - - totalSupplyTypes.push( - poolHasVirtualSupply(pool.poolType) - ? TotalSupplyType.VIRTUAL_SUPPLY - : poolHasActualSupply(pool.poolType) - ? TotalSupplyType.ACTUAL_SUPPLY - : TotalSupplyType.TOTAL_SUPPLY, - ); - - if (poolIsLinearPool(pool.poolType)) { - linearPoolIdxs.push(BigInt(i)); - } - - if ( - loadWeightsForPoolTypes.has(pool.poolType) || - loadWeightsForPoolIds.has(pool.id) - ) { - weightedPoolIdxs.push(BigInt(i)); - } - - if ( - loadAmpForPoolTypes.has(pool.poolType) || - loadAmpForPoolIds.has(pool.id) - ) { - ampPoolIdxs.push(BigInt(i)); - } - - if ( - loadScalingFactorForPoolIds.has(pool.id) || - loadScalingFactorForPoolTypes.has(pool.poolType) - ) { - scalingFactorPoolIdxs.push(BigInt(i)); - } - - if ( - loadTokenRatesForPoolIds.has(pool.id) || - (loadTokenRatesForPoolTypes.has(pool.poolType) && - loadTokenRatesForPoolTypeVersions.has(pool.poolTypeVersion)) - ) { - tokenRatesPoolIdxs.push(BigInt(i)); - } - - if ( - loadRatesForPoolIds.has(pool.id) || - loadRatesForPoolTypes.has(pool.poolType) - ) { - ratePoolIdxs.push(BigInt(i)); - } - - if (this.config.loadSwapFees) { - swapFeeTypes.push( - poolHasPercentFee(pool.poolType) - ? SwapFeeType.PERCENT_FEE - : SwapFeeType.SWAP_FEE_PERCENTAGE, - ); - } - } - - return { - poolIds, - totalSupplyTypes, - linearPoolIdxs, - weightedPoolIdxs, - ampPoolIdxs, - scalingFactorPoolIdxs, - ratePoolIdxs, - swapFeeTypes, - tokenRatesPoolIdxs, - }; - } - - private getMergedFilterConfig({ - poolsWithActiveWeightUpdates = [], - poolsWithActiveAmpUpdates = [], - }: { - poolsWithActiveWeightUpdates?: string[]; - poolsWithActiveAmpUpdates?: string[]; - }) { - const { - loadWeightsForPools, - loadScalingFactorForPools, - loadAmpForPools, - loadTokenRatesForPools, - loadRatesForPools, - } = this.config; - - const loadWeightsForPoolIds = new Set([ - ...poolsWithActiveWeightUpdates, - ...(loadWeightsForPools.poolIds || []), - ]); - const loadAmpForPoolIds = new Set([ - ...poolsWithActiveAmpUpdates, - ...(loadAmpForPools.poolIds || []), - ]); - const loadScalingFactorForPoolIds = new Set( - loadScalingFactorForPools.poolIds || [], - ); - const loadRatesForPoolIds = new Set(loadRatesForPools.poolIds || []); - - const loadWeightsForPoolTypes = new Set( - loadWeightsForPools.poolTypes || [], - ); - const loadAmpForPoolTypes = new Set(loadAmpForPools.poolTypes || []); - const loadScalingFactorForPoolTypes = new Set( - loadScalingFactorForPools.poolTypes || [], - ); - const loadTokenRatesForPoolIds = new Set( - loadTokenRatesForPools.poolIds || [], - ); - const loadTokenRatesForPoolTypes = new Set( - loadTokenRatesForPools.poolTypes || [], - ); - const loadTokenRatesForPoolTypeVersions = new Set( - loadTokenRatesForPools.poolTypeVersions || [], - ); - const loadRatesForPoolTypes = new Set( - loadRatesForPools.poolTypes || [], - ); - - return { - loadWeightsForPoolIds, - loadAmpForPoolIds, - loadScalingFactorForPoolIds, - loadRatesForPoolIds, - loadWeightsForPoolTypes, - loadAmpForPoolTypes, - loadScalingFactorForPoolTypes, - loadTokenRatesForPoolIds, - loadTokenRatesForPoolTypes, - loadTokenRatesForPoolTypeVersions, - loadRatesForPoolTypes, - }; - } - private getPoolTokenRate({ pool, token, @@ -478,7 +146,8 @@ export class OnChainPoolDataEnricher implements PoolDataEnricher { } if (data?.scalingFactors) { - return formatUnits(data.scalingFactors[index], 18); + const decimalsDiff = 18 - token.decimals; + return formatUnits(data.scalingFactors[index], 18 + decimalsDiff); } return token.priceRate; diff --git a/src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts b/src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts deleted file mode 100644 index 276bc5bb..00000000 --- a/src/data/enrichers/onChainPoolDataViaMulticallEnricher.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { createPublicClient, formatUnits, http, PublicClient } from 'viem'; -import { - GetPoolsResponse, - PoolDataEnricher, - RawPool, - RawPoolTokenWithRate, - RawWeightedPoolToken, -} from '../types'; - -import { CHAINS } from '../../utils'; -import { HumanAmount } from '../../types'; -import { fetchAdditionalPoolData } from '../onChainPoolDataViaMulticall'; -import { OnChainPoolData } from './onChainPoolDataEnricher'; - -export class OnChainPoolDataViaMulticallEnricher implements PoolDataEnricher { - private readonly client: PublicClient; - - constructor( - private readonly chainId: number, - private readonly rpcUrl: string, - ) { - this.client = createPublicClient({ - transport: http(this.rpcUrl), - chain: CHAINS[this.chainId], - }); - } - - public async fetchAdditionalPoolData( - data: GetPoolsResponse, - ): Promise { - return fetchAdditionalPoolData(data.pools, this.client); - } - - public enrichPoolsWithData( - pools: RawPool[], - additionalPoolData: OnChainPoolData[], - ): RawPool[] { - return pools.map((pool) => { - const data = additionalPoolData.find((item) => item.id === pool.id); - - return { - ...pool, - tokens: pool.tokens - .sort((a, b) => a.index - b.index) - .map((token) => { - return { - ...token, - balance: - data?.balances && data.balances.length > 0 - ? (formatUnits( - data.balances[token.index], - token.decimals, - ) as HumanAmount) - : token.balance, - priceRate: this.getPoolTokenRate({ - pool, - token: token as RawPoolTokenWithRate, - data, - index: token.index, - }), - weight: data?.weights - ? formatUnits(data.weights[token.index], 18) - : (token as RawWeightedPoolToken).weight, - }; - }), - totalShares: data?.totalSupply - ? (formatUnits(data.totalSupply, 18) as HumanAmount) - : pool.totalShares, - amp: data?.amp - ? formatUnits(data.amp, 3) - : 'amp' in pool - ? pool.amp - : undefined, - swapFee: data?.swapFee - ? (formatUnits(data.swapFee, 18) as HumanAmount) - : pool.swapFee, - tokenRates: data?.tokenRates - ? data.tokenRates.map( - (tokenRate) => - formatUnits(tokenRate, 18) as HumanAmount, - ) - : undefined, - lowerTarget: data?.linearTargets - ? data.linearTargets[0] - : 'lowerTarget' in pool - ? pool.lowerTarget - : undefined, - upperTarget: data?.linearTargets - ? data.linearTargets[0] - : 'upperTarget' in pool - ? pool.upperTarget - : undefined, - inRecoveryMode: data?.inRecoveryMode || false, - isPaused: data?.isPaused || false, - queryFailed: data?.queryFailed, - }; - }); - } - - private getPoolTokenRate({ - pool, - token, - data, - index, - }: { - pool: RawPool; - token: RawPoolTokenWithRate; - data?: OnChainPoolData; - index: number; - }): string { - if ( - data?.wrappedTokenRate && - 'wrappedIndex' in pool && - pool.wrappedIndex === index - ) { - return formatUnits(data.wrappedTokenRate, 18); - } - - if (data?.scalingFactors) { - return formatUnits(data.scalingFactors[index], 18); - } - - return token.priceRate; - } -} diff --git a/src/data/onChainPoolDataViaMulticall.ts b/src/data/onChainPoolDataViaMulticall.ts deleted file mode 100644 index 96c15ee1..00000000 --- a/src/data/onChainPoolDataViaMulticall.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { PublicClient, formatUnits, parseAbi } from 'viem'; -import { getPoolAddress } from '../utils'; -import { vaultAbi } from '../abi'; - -const abi = parseAbi([ - '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? -]); - -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'; - } -}; - -const defaultCalls = { - count: 7, - build: (id: string, poolType: string) => [ - { - address: - '0xBA12222222228d8Ba445958a75a0704d566BF2C8' as `0x${string}`, - abi: vaultAbi, - functionName: 'getPoolTokens', - args: [id], - }, - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: getTotalSupplyFn(poolType), - }, - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: getSwapFeeFn(poolType), - }, - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: 'getPausedState', - }, - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: 'inRecoveryMode', - }, - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: 'getRate', - }, - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: 'getScalingFactors', - }, - ], - parse: (results: any, shift: number) => ({ - balances: results[shift].result[1], - totalSupply: results[shift + 1].result, - swapFee: results[shift + 2].result, - isPaused: results[shift + 3].result, - inRecoveryMode: results[shift + 4].result, - poolRate: results[shift + 5].result, - scalingFactors: results[shift + 6].result, - queryFailed: false, - }), -}; - -const weightedCalls = { - count: 1, - build: (id: string) => [ - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: 'getNormalizedWeights', - }, - ], - parse: (results: any, shift: number) => ({ - weights: results[shift].result, - }), -}; - -const linearCalls = { - count: 2, - build: (id: string) => [ - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: 'getTargets', - }, - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: 'getWrappedTokenRate', - }, - ], - parse: (results: any, shift: number) => ({ - linearTargets: results[shift].result, - wrappedTokenRate: results[shift + 1].result, - }), -}; - -const stableCalls = { - count: 1, - build: (id: string) => [ - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: 'getAmplificationParameter', - }, - ], - parse: (results: any, shift: number) => ({ - amplificationParameter: formatUnits( - results[shift].result[0], - String(results[shift].result[2]).length - 1, - ), - }), -}; - -const gyroECalls = { - count: 1, - build: (id: string) => [ - { - address: getPoolAddress(id) as `0x${string}`, - abi, - functionName: 'getTokenRates', - }, - ], - parse: (results: any, shift: number) => ({ - tokenRates: results[shift].result, - }), -}; - -const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { - const do_nothing = { - count: 0, - build: () => [], - parse: () => ({}), - }; - - 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; - } -}; - -export const fetchAdditionalPoolData = async ( - pools: { - id: string; - poolType: string; - poolTypeVersion: number; - }[], - client: PublicClient, -) => { - if (pools.length === 0) { - return []; - } - - const contracts = pools.flatMap(({ id, poolType, poolTypeVersion }) => [ - ...defaultCalls.build(id, poolType), - ...poolTypeCalls(poolType, poolTypeVersion).build(id), - ]); - - const results = await client.multicall({ - contracts, - batchSize: 128, // 128 is the max batch size for zkEVM RPCs - }); - - let shift = 0; - - return pools.map(({ id, poolType }) => { - const result = { - id, - ...defaultCalls.parse(results, shift), - ...poolTypeCalls(poolType).parse( - results, - shift + defaultCalls.count, - ), - }; - shift += defaultCalls.count + poolTypeCalls(poolType).count; - return result; - }); -}; diff --git a/src/data/onChainPoolDataViaReadContract.ts b/src/data/onChainPoolDataViaReadContract.ts new file mode 100644 index 00000000..0cd316bf --- /dev/null +++ b/src/data/onChainPoolDataViaReadContract.ts @@ -0,0 +1,432 @@ +import { + PublicClient, + parseAbi, + multicall3Abi, + encodeFunctionData, + Address, + Abi, + Hex, + decodeFunctionResult, +} from 'viem'; +import { getPoolAddress } from '../utils'; +import { OnChainPoolData } from './enrichers/onChainPoolDataEnricher'; +import { SwapOptions } from '../types'; + +type Result = + | { + error: Error; + result?: undefined; + status: 'failure'; + } + | { + error?: undefined; + result: any; + status: 'success'; + }; +[]; + +type Results = Result[]; + +const abi = parseAbi([ + 'function getPoolTokens(bytes32 poolId) view returns (address[] tokens, uint256 lastChangeBlock)', + '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)', +]); + +// Extract the functionName property values into a union type +type FunctionNameUnion = typeof abi[number]['name']; + +type BuildReturn = { + address: Address; + abi: Abi; + functionName: FunctionNameUnion; + args?: readonly [`0x${string}`]; +}; + +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'; + } +}; + +const defaultCalls = { + count: 4, + build: (id: string, poolType: string): BuildReturn[] => [ + { + address: + '0xBA12222222228d8Ba445958a75a0704d566BF2C8' as `0x${string}`, + abi, + functionName: 'getPoolTokens', + args: [id as Hex], + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: getTotalSupplyFn(poolType), + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: getSwapFeeFn(poolType), + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getPausedState', + }, + ], + parse: (results: Results, shift: number) => { + return { + balances: results[shift].result[1], + totalSupply: results[shift + 1].result, + swapFee: results[shift + 2].result, + isPaused: results[shift + 3].result, + }; + }, +}; + +// These don't exist on some earlier pool versions +const defaultCallsAux = { + count: 2 + defaultCalls.count, + build: (id: string, poolType: string): BuildReturn[] => [ + ...defaultCalls.build(id, poolType), + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'inRecoveryMode', + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getScalingFactors', + }, + ], + parse: (results: Results, shift: number) => ({ + ...defaultCalls.parse(results, shift), + ...{ + inRecoveryMode: results[shift + defaultCalls.count].result, + scalingFactors: results[shift + defaultCalls.count + +1].result, + }, + }), +}; + +const weightedCalls = { + count: 1, + build: (id: string): BuildReturn[] => [ + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getNormalizedWeights', + }, + ], + parse: (results: Results, shift: number) => ({ + weights: results[shift].result, + }), +}; + +const linearCalls = { + count: 3, + build: (id: string): BuildReturn[] => [ + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getTargets', + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getWrappedTokenRate', + }, + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getRate', + }, + ], + parse: (results: Results, shift: number) => ({ + linearTargets: results[shift].result, + wrappedTokenRate: results[shift + 1].result, + poolRate: results[shift + 2].result, + }), +}; + +const stableCalls = { + count: 1, + build: (id: string): BuildReturn[] => [ + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getAmplificationParameter', + }, + ], + parse: (results: Results, shift: number) => { + return { + amp: results[shift].result[0], + }; + }, +}; + +const gyroECalls = { + count: 1, + build: (id: string): BuildReturn[] => [ + { + address: getPoolAddress(id) as `0x${string}`, + abi, + functionName: 'getTokenRates', + }, + ], + parse: (results: Results, shift: number) => ({ + tokenRates: results[shift].result, + }), +}; + +const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { + const do_nothing = { + count: 0, + build: () => [], + parse: () => ({}), + }; + + switch (poolType) { + case 'Weighted': + case 'LiquidityBootstrapping': + case 'Investment': { + if (poolTypeVersion === 1) { + return { + count: defaultCalls.count + weightedCalls.count, + build: (id: string) => [ + ...defaultCalls.build(id, poolType), + ...weightedCalls.build(id), + ], + parse: (results: Results, shift: number) => ({ + ...defaultCalls.parse(results, shift), + ...weightedCalls.parse( + results, + shift + defaultCalls.count, + ), + }), + }; + } else + return { + count: defaultCallsAux.count + weightedCalls.count, + build: (id: string) => [ + ...defaultCallsAux.build(id, poolType), + ...weightedCalls.build(id), + ], + parse: (results: Results, shift: number) => ({ + ...defaultCallsAux.parse(results, shift), + ...weightedCalls.parse( + results, + shift + defaultCallsAux.count, + ), + }), + }; + } + case 'Stable': { + if (poolTypeVersion === 1) { + return { + count: defaultCalls.count + stableCalls.count, + build: (id: string) => [ + ...defaultCalls.build(id, poolType), + ...stableCalls.build(id), + ], + parse: (results: Results, shift: number) => ({ + ...defaultCalls.parse(results, shift), + ...stableCalls.parse( + results, + shift + defaultCalls.count, + ), + }), + }; + } else { + return { + count: defaultCallsAux.count + stableCalls.count, + build: (id: string) => [ + ...defaultCallsAux.build(id, poolType), + ...stableCalls.build(id), + ], + parse: (results: Results, shift: number) => ({ + ...defaultCallsAux.parse(results, shift), + ...stableCalls.parse( + results, + shift + defaultCallsAux.count, + ), + }), + }; + } + } + case 'StablePhantom': + case 'MetaStable': + return { + count: defaultCalls.count + stableCalls.count, + build: (id: string) => [ + ...defaultCalls.build(id, poolType), + ...stableCalls.build(id), + ], + parse: (results: Results, shift: number) => ({ + ...defaultCalls.parse(results, shift), + ...stableCalls.parse(results, shift + defaultCalls.count), + }), + }; + case 'ComposableStable': { + return { + count: defaultCallsAux.count + stableCalls.count, + build: (id: string) => [ + ...defaultCallsAux.build(id, poolType), + ...stableCalls.build(id), + ], + parse: (results: Results, shift: number) => ({ + ...defaultCallsAux.parse(results, shift), + ...stableCalls.parse( + results, + shift + defaultCallsAux.count, + ), + }), + }; + } + case 'GyroE': + if (poolTypeVersion === 2) { + return { + count: defaultCalls.count + gyroECalls.count, + build: (id: string) => [ + ...defaultCalls.build(id, poolType), + ...gyroECalls.build(id), + ], + parse: (results: Results, shift: number) => ({ + ...defaultCalls.parse(results, shift), + ...gyroECalls.parse( + results, + shift + defaultCalls.count, + ), + }), + }; + } else { + return defaultCallsAux; + } + case 'AaveLinear': + if (poolTypeVersion === 1) { + return { + count: defaultCalls.count + linearCalls.count, + build: (id: string) => [ + ...defaultCalls.build(id, poolType), + ...linearCalls.build(id), + ], + parse: (results: Results, shift: number) => ({ + ...defaultCalls.parse(results, shift), + ...linearCalls.parse( + results, + shift + defaultCalls.count, + ), + }), + }; + } else { + return defaultCallsAux; + } + default: + return do_nothing; + } +}; + +export const fetchAdditionalPoolData = async ( + multicallAddress: Address, + pools: { + id: string; + poolType: string; + poolTypeVersion: number; + }[], + client: PublicClient, + options: SwapOptions, +): Promise => { + if (pools.length === 0) { + return []; + } + + const calls = pools.flatMap(({ id, poolType, poolTypeVersion }) => + poolTypeCalls(poolType, poolTypeVersion).build(id, poolType), + ); + + const batchedCalls = calls.map(({ address, functionName, args }) => { + let callData; + if (args !== undefined) + callData = encodeFunctionData({ abi, functionName, args }); + else callData = encodeFunctionData({ abi, functionName }); + return { + target: address, + allowFailure: true, + callData, + }; + }); + + // TODO - Add batching 128 is the max batch size for zkEVM RPCs + const encodedResults = await client.readContract({ + address: multicallAddress, + abi: multicall3Abi, + functionName: 'aggregate3', + blockNumber: options.block, + args: [batchedCalls], + }); + + const results = encodedResults.map((result, i) => { + if (result.success) { + const decodedResult = decodeFunctionResult({ + abi, + functionName: calls[i].functionName, + data: result.returnData, + }); + return { + error: undefined, + result: decodedResult, + status: 'success', + } as Result; + } else { + console.error( + 'Failed request in multicall', + calls[i].address, + calls[i].functionName, + ); + return { + error: new Error('failed'), + result: undefined, + status: 'failure', + } as Result; + } + }); + + let shift = 0; + + return pools.map(({ id, poolType, poolTypeVersion }) => { + const result = { + id, + ...poolTypeCalls(poolType, poolTypeVersion).parse(results, shift), + } as OnChainPoolData; + shift += poolTypeCalls(poolType, poolTypeVersion).count; + return result; + }); +}; diff --git a/src/sor.ts b/src/sor.ts index 7274077d..42244d3f 100644 --- a/src/sor.ts +++ b/src/sor.ts @@ -1,11 +1,6 @@ import { Router } from './router'; import { BasePool, Path, Token, TokenAmount, Swap } from './entities'; -import { - BALANCER_POOL_DATA_QUERIES_ADDRESSES, - ChainId, - checkInputs, - SUBGRAPH_URLS, -} from './utils'; +import { ChainId, checkInputs, SUBGRAPH_URLS, MULTICALL } from './utils'; import { SorConfig, SwapInputRawAmount, SwapKind, SwapOptions } from './types'; import { PoolParser } from './entities/pools/parser'; import { OnChainPoolDataEnricher, SubgraphPoolProvider } from './data'; @@ -36,11 +31,7 @@ export class SmartOrderRouter { new SubgraphPoolProvider(chainId, SUBGRAPH_URLS[chainId]); poolDataEnrichers = poolDataEnrichers || - new OnChainPoolDataEnricher( - chainId, - rpcUrl, - BALANCER_POOL_DATA_QUERIES_ADDRESSES[chainId], - ); + new OnChainPoolDataEnricher(chainId, rpcUrl, MULTICALL[chainId]); this.poolDataService = new PoolDataService( Array.isArray(poolDataProviders) ? poolDataProviders diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 131939a5..300f07ff 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -112,14 +112,14 @@ export const STELLATE_URLS = { }; export const BALANCER_QUERIES = '0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5'; -export const BALANCER_POOL_DATA_QUERIES_ADDRESSES: Record = { - [ChainId.ARBITRUM_ONE]: '0x7Ba29fE8E83dd6097A7298075C4AFfdBda3121cC', - [ChainId.AVALANCHE]: '0x67af5D428d38C5176a286a2371Df691cDD914Fb8', - [ChainId.GNOSIS_CHAIN]: '0x3f170631ed9821Ca51A59D996aB095162438DC10', - [ChainId.MAINNET]: '0xf5CDdF6feD9C589f1Be04899F48f9738531daD59', - [ChainId.OPTIMISM]: '0x6B5dA774890Db7B7b96C6f44e6a4b0F657399E2e', - [ChainId.POLYGON]: '0x84813aA3e079A665C0B80F944427eE83cBA63617', - [ChainId.ZKEVM]: '0xF24917fB88261a37Cc57F686eBC831a5c0B9fD39', +export const MULTICALL: Record = { + [ChainId.ARBITRUM_ONE]: '0xca11bde05977b3631167028862be2a173976ca11', + [ChainId.AVALANCHE]: '0xca11bde05977b3631167028862be2a173976ca11', + [ChainId.GNOSIS_CHAIN]: '0xca11bde05977b3631167028862be2a173976ca11', + [ChainId.MAINNET]: '0xca11bde05977b3631167028862be2a173976ca11', + [ChainId.OPTIMISM]: '0xca11bde05977b3631167028862be2a173976ca11', + [ChainId.POLYGON]: '0xca11bde05977b3631167028862be2a173976ca11', + [ChainId.ZKEVM]: '0xca11bde05977b3631167028862be2a173976ca11', }; export const NATIVE_ASSETS = { diff --git a/test/fxPool.integration.test.ts b/test/fxPool.integration.test.ts index 7c629836..1c172602 100644 --- a/test/fxPool.integration.test.ts +++ b/test/fxPool.integration.test.ts @@ -3,7 +3,7 @@ import { beforeAll, beforeEach, describe, expect, test } from 'vitest'; import dotenv from 'dotenv'; dotenv.config(); -import { BALANCER_POOL_DATA_QUERIES_ADDRESSES, ChainId } from '../src/utils'; +import { ChainId, MULTICALL } from '../src/utils'; import { BasePool, OnChainPoolDataEnricher, @@ -18,7 +18,7 @@ import { describe('fx integration tests', () => { const chainId = ChainId.POLYGON; - const rpcUrl = process.env['POLYGON_RPC_URL'] || 'https://polygon-rpc.com'; + const rpcUrl = process.env.POLYGON_RPC_URL || 'https://polygon-rpc.com'; const USDC = new Token( chainId, @@ -49,10 +49,7 @@ describe('fx integration tests', () => { const onChainPoolDataEnricher = new OnChainPoolDataEnricher( chainId, rpcUrl, - BALANCER_POOL_DATA_QUERIES_ADDRESSES[chainId], - { - loadSwapFees: false, - }, + MULTICALL[chainId], ); sor = new SmartOrderRouter({ diff --git a/test/gyroEV2Pool.integration.test.ts b/test/gyroEV2Pool.integration.test.ts index 6c621222..a6f09c24 100644 --- a/test/gyroEV2Pool.integration.test.ts +++ b/test/gyroEV2Pool.integration.test.ts @@ -4,7 +4,7 @@ import dotenv from 'dotenv'; dotenv.config(); import testPools from './lib/testData/gyroETestPool.json'; -import { BALANCER_POOL_DATA_QUERIES_ADDRESSES, ChainId } from '../src/utils'; +import { ChainId, MULTICALL } from '../src/utils'; import { BasePool, OnChainPoolDataEnricher, @@ -21,7 +21,7 @@ import { MockPoolProvider } from './lib/utils/mockPoolProvider'; describe('gyroEV2: WMATIC-stMATIC integration tests', () => { const chainId = ChainId.POLYGON; - const rpcUrl = process.env['POLYGON_RPC_URL'] || 'https://polygon-rpc.com'; + const rpcUrl = process.env.POLYGON_RPC_URL || 'https://polygon-rpc.com'; const WMATIC = new Token( chainId, '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', @@ -45,13 +45,7 @@ describe('gyroEV2: WMATIC-stMATIC integration tests', () => { const onChainPoolDataEnricher = new OnChainPoolDataEnricher( chainId, rpcUrl, - BALANCER_POOL_DATA_QUERIES_ADDRESSES[chainId], - { - loadTokenRatesForPools: { - poolTypes: ['GyroE'], - poolTypeVersions: [2], - }, - }, + MULTICALL[chainId], ); sor = new SmartOrderRouter({ diff --git a/test/sor.test.ts b/test/sor.test.ts index 2ac4e9a5..f9d51a27 100644 --- a/test/sor.test.ts +++ b/test/sor.test.ts @@ -6,11 +6,7 @@ dotenv.config(); import { SmartOrderRouter } from '../src/sor'; import { sorGetSwapsWithPools } from '../src/static'; import { SubgraphPoolProvider } from '../src/data/providers/subgraphPoolProvider'; -import { - BALANCER_POOL_DATA_QUERIES_ADDRESSES, - ChainId, - ETH, -} from '../src/utils'; +import { ChainId, ETH, MULTICALL } from '../src/utils'; import { Token, TokenAmount } from '../src/entities'; import { OnChainPoolDataEnricher } from '../src/data/enrichers/onChainPoolDataEnricher'; import { SwapKind, SwapOptions } from '../src/types'; @@ -20,17 +16,12 @@ describe('SmartOrderRouter', () => { describe('Mainnet', () => { const chainId = ChainId.MAINNET; const rpcUrl = - process.env['ETHEREUM_RPC_URL'] || 'https://eth.llamarpc.com'; + process.env.ETHEREUM_RPC_URL || 'https://eth.llamarpc.com'; const subgraphPoolDataService = new SubgraphPoolProvider(chainId); const onChainPoolDataEnricher = new OnChainPoolDataEnricher( chainId, rpcUrl, - BALANCER_POOL_DATA_QUERIES_ADDRESSES[chainId], - { - loadAmpForPools: { - poolTypes: ['ComposableStable'], - }, - }, + MULTICALL[chainId], ); const sor = new SmartOrderRouter({ @@ -137,12 +128,7 @@ describe('SmartOrderRouter', () => { }); describe('Stable Pools', () => { - // DAI -> bb-a-DAI -> bb-a-USDT -> USDT swapGivenIn boosted - // Aave Linear + Boosted Pool - // 0x6667c6fa9f2b3fc1cc8d85320b62703d938e43850000000000000000000004fb - // 0xfebb0bbf162e64fb9d0dfe186e517d84c395f016000000000000000000000502 - // 0xa1697f9af0875b63ddc472d6eebada8c1fab85680000000000000000000004f9 - test('DAI -> USDT givenIn boosted', async () => { + test('DAI -> USDT givenIn ComposableStable', async () => { const inputAmount = TokenAmount.fromHumanAmount(DAI, '100000'); const swap = await sorGetSwapsWithPools( @@ -161,15 +147,10 @@ describe('SmartOrderRouter', () => { expect(swap.inputAmount.amount).toEqual(inputAmount.amount); expect(swap.outputAmount.amount).toEqual(swap.quote.amount); expect(swap.paths.length).toEqual(1); - expect(swap.paths[0].pools.length).toEqual(3); + expect(swap.paths[0].pools.length).toEqual(1); }); - // DAI -> bb-a-DAI -> bb-a-USDT -> USDT swapGivenOut boosted - // Aave Linear + Boosted Pool - // 0x6667c6fa9f2b3fc1cc8d85320b62703d938e43850000000000000000000004fb - // 0xfebb0bbf162e64fb9d0dfe186e517d84c395f016000000000000000000000502 - // 0xa1697f9af0875b63ddc472d6eebada8c1fab85680000000000000000000004f9 - test('USDC -> DAI givenOut boosted', async () => { + test('USDC -> DAI givenOut ComposableStable', async () => { const outputAmount = TokenAmount.fromHumanAmount( DAI, '1000000', @@ -184,6 +165,8 @@ describe('SmartOrderRouter', () => { swapOptions, ); + console.log(swap); + if (!swap) throw new Error('Swap is undefined'); const onchain = await swap.query(rpcUrl, swapOptions.block); From f2a2c9462fa653d196857a53a610fbc6bf0af062 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 11:24:52 +0100 Subject: [PATCH 08/23] fix: GyroE V1 onchain calls - doesnt have recovery/scaling factors. --- src/data/onChainPoolDataViaReadContract.ts | 2 +- test/sor.test.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/data/onChainPoolDataViaReadContract.ts b/src/data/onChainPoolDataViaReadContract.ts index 0cd316bf..12adc65f 100644 --- a/src/data/onChainPoolDataViaReadContract.ts +++ b/src/data/onChainPoolDataViaReadContract.ts @@ -328,7 +328,7 @@ const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { }), }; } else { - return defaultCallsAux; + return defaultCalls; } case 'AaveLinear': if (poolTypeVersion === 1) { diff --git a/test/sor.test.ts b/test/sor.test.ts index f9d51a27..13a4477e 100644 --- a/test/sor.test.ts +++ b/test/sor.test.ts @@ -165,8 +165,6 @@ describe('SmartOrderRouter', () => { swapOptions, ); - console.log(swap); - if (!swap) throw new Error('Swap is undefined'); const onchain = await swap.query(rpcUrl, swapOptions.block); From 8225d8a1072ff25dcbbaa2a9d121c823b1c7db70 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 11:36:17 +0100 Subject: [PATCH 09/23] feat: Add batching for multicallV3. --- src/data/enrichers/onChainPoolDataEnricher.ts | 4 ++- src/data/onChainPoolDataViaReadContract.ts | 35 ++++++++++++++----- src/sor.ts | 15 ++++++-- src/utils/constants.ts | 9 +++++ test/fxPool.integration.test.ts | 3 +- test/gyroEV2Pool.integration.test.ts | 3 +- test/sor.test.ts | 3 +- 7 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/data/enrichers/onChainPoolDataEnricher.ts b/src/data/enrichers/onChainPoolDataEnricher.ts index 4c46890d..55857059 100644 --- a/src/data/enrichers/onChainPoolDataEnricher.ts +++ b/src/data/enrichers/onChainPoolDataEnricher.ts @@ -42,9 +42,10 @@ export class OnChainPoolDataEnricher implements PoolDataEnricher { private readonly chainId: number, private readonly rpcUrl: string, private readonly multicallAddress: Address, + private readonly batchSize: number, ) { this.client = createPublicClient({ - transport: http(this.rpcUrl), + transport: http(this.rpcUrl, { timeout: 60_000 }), chain: CHAINS[this.chainId], }); } @@ -58,6 +59,7 @@ export class OnChainPoolDataEnricher implements PoolDataEnricher { data.pools, this.client, options, + this.batchSize, ); } diff --git a/src/data/onChainPoolDataViaReadContract.ts b/src/data/onChainPoolDataViaReadContract.ts index 12adc65f..dbb9b13d 100644 --- a/src/data/onChainPoolDataViaReadContract.ts +++ b/src/data/onChainPoolDataViaReadContract.ts @@ -363,6 +363,7 @@ export const fetchAdditionalPoolData = async ( }[], client: PublicClient, options: SwapOptions, + batchSize: number, ): Promise => { if (pools.length === 0) { return []; @@ -384,14 +385,32 @@ export const fetchAdditionalPoolData = async ( }; }); - // TODO - Add batching 128 is the max batch size for zkEVM RPCs - const encodedResults = await client.readContract({ - address: multicallAddress, - abi: multicall3Abi, - functionName: 'aggregate3', - blockNumber: options.block, - args: [batchedCalls], - }); + const numBatches = Math.ceil(batchedCalls.length / batchSize); + const batchPromises: Promise< + readonly { + success: boolean; + returnData: `0x${string}`; + }[] + >[] = []; + + for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) { + const batchCalls = batchedCalls.slice( + batchIndex * batchSize, + (batchIndex + 1) * batchSize, + ); + + batchPromises.push( + client.readContract({ + address: multicallAddress, + abi: multicall3Abi, + functionName: 'aggregate3', + blockNumber: options.block, + args: [batchCalls], + }), + ); + } + + const encodedResults = (await Promise.all(batchPromises)).flatMap((r) => r); const results = encodedResults.map((result, i) => { if (result.success) { diff --git a/src/sor.ts b/src/sor.ts index 42244d3f..717137c9 100644 --- a/src/sor.ts +++ b/src/sor.ts @@ -1,6 +1,12 @@ import { Router } from './router'; import { BasePool, Path, Token, TokenAmount, Swap } from './entities'; -import { ChainId, checkInputs, SUBGRAPH_URLS, MULTICALL } from './utils'; +import { + ChainId, + checkInputs, + SUBGRAPH_URLS, + MULTICALL, + BATCHSIZE, +} from './utils'; import { SorConfig, SwapInputRawAmount, SwapKind, SwapOptions } from './types'; import { PoolParser } from './entities/pools/parser'; import { OnChainPoolDataEnricher, SubgraphPoolProvider } from './data'; @@ -31,7 +37,12 @@ export class SmartOrderRouter { new SubgraphPoolProvider(chainId, SUBGRAPH_URLS[chainId]); poolDataEnrichers = poolDataEnrichers || - new OnChainPoolDataEnricher(chainId, rpcUrl, MULTICALL[chainId]); + new OnChainPoolDataEnricher( + chainId, + rpcUrl, + MULTICALL[chainId], + BATCHSIZE[this.chainId], + ); this.poolDataService = new PoolDataService( Array.isArray(poolDataProviders) ? poolDataProviders diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 300f07ff..017b04f0 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -121,6 +121,15 @@ export const MULTICALL: Record = { [ChainId.POLYGON]: '0xca11bde05977b3631167028862be2a173976ca11', [ChainId.ZKEVM]: '0xca11bde05977b3631167028862be2a173976ca11', }; +export const BATCHSIZE: Record = { + [ChainId.ARBITRUM_ONE]: 1000, + [ChainId.AVALANCHE]: 1000, + [ChainId.GNOSIS_CHAIN]: 1000, + [ChainId.MAINNET]: 1000, + [ChainId.OPTIMISM]: 1000, + [ChainId.POLYGON]: 1000, + [ChainId.ZKEVM]: 128, +}; export const NATIVE_ASSETS = { [ChainId.MAINNET]: new Token( diff --git a/test/fxPool.integration.test.ts b/test/fxPool.integration.test.ts index 1c172602..9ac8ea9a 100644 --- a/test/fxPool.integration.test.ts +++ b/test/fxPool.integration.test.ts @@ -3,7 +3,7 @@ import { beforeAll, beforeEach, describe, expect, test } from 'vitest'; import dotenv from 'dotenv'; dotenv.config(); -import { ChainId, MULTICALL } from '../src/utils'; +import { BATCHSIZE, ChainId, MULTICALL } from '../src/utils'; import { BasePool, OnChainPoolDataEnricher, @@ -50,6 +50,7 @@ describe('fx integration tests', () => { chainId, rpcUrl, MULTICALL[chainId], + BATCHSIZE[chainId], ); sor = new SmartOrderRouter({ diff --git a/test/gyroEV2Pool.integration.test.ts b/test/gyroEV2Pool.integration.test.ts index a6f09c24..2c17ce5f 100644 --- a/test/gyroEV2Pool.integration.test.ts +++ b/test/gyroEV2Pool.integration.test.ts @@ -4,7 +4,7 @@ import dotenv from 'dotenv'; dotenv.config(); import testPools from './lib/testData/gyroETestPool.json'; -import { ChainId, MULTICALL } from '../src/utils'; +import { BATCHSIZE, ChainId, MULTICALL } from '../src/utils'; import { BasePool, OnChainPoolDataEnricher, @@ -46,6 +46,7 @@ describe('gyroEV2: WMATIC-stMATIC integration tests', () => { chainId, rpcUrl, MULTICALL[chainId], + BATCHSIZE[chainId], ); sor = new SmartOrderRouter({ diff --git a/test/sor.test.ts b/test/sor.test.ts index 13a4477e..d3bb41b1 100644 --- a/test/sor.test.ts +++ b/test/sor.test.ts @@ -6,7 +6,7 @@ dotenv.config(); import { SmartOrderRouter } from '../src/sor'; import { sorGetSwapsWithPools } from '../src/static'; import { SubgraphPoolProvider } from '../src/data/providers/subgraphPoolProvider'; -import { ChainId, ETH, MULTICALL } from '../src/utils'; +import { ChainId, ETH, MULTICALL, BATCHSIZE } from '../src/utils'; import { Token, TokenAmount } from '../src/entities'; import { OnChainPoolDataEnricher } from '../src/data/enrichers/onChainPoolDataEnricher'; import { SwapKind, SwapOptions } from '../src/types'; @@ -22,6 +22,7 @@ describe('SmartOrderRouter', () => { chainId, rpcUrl, MULTICALL[chainId], + BATCHSIZE[chainId], ); const sor = new SmartOrderRouter({ From 4aad59467f7435bb62c2d98cdf6e14bb89d948f4 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 13:43:41 +0100 Subject: [PATCH 10/23] fix: Fix multicall debug. --- debug/multicall.ts | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/debug/multicall.ts b/debug/multicall.ts index 2a3ba6d9..d34b1f99 100644 --- a/debug/multicall.ts +++ b/debug/multicall.ts @@ -1,17 +1,16 @@ -import { OnChainPoolDataViaMulticallEnricher } from '../src/data/enrichers/onChainPoolDataViaMulticallEnricher'; -import { createPublicClient, http } from 'viem'; +// bun run debug/multicall.ts +import { + OnChainPoolDataEnricher, + MULTICALL, + BATCHSIZE, + ProviderSwapOptions, +} from '../src'; // rome-ignore lint/correctness/noUnusedVariables: import { mainnet, polygonZkEvm } from 'viem/chains'; const chain = polygonZkEvm; const rpc = 'https://rpc.ankr.com/polygon_zkevm'; -// rome-ignore lint/correctness/noUnusedVariables: -const client = createPublicClient({ - chain, - transport: http(), -}); - const poolsQuery = `{ pools(first: 10, orderBy: id, orderDirection: desc, where: { totalShares_gt: 0, poolType_contains: "ComposableStable" }) { id @@ -40,8 +39,21 @@ const pools = await fetch( .then((res) => res.json()) .then((res) => res.data); -const onChainEnricher = new OnChainPoolDataViaMulticallEnricher(chain.id, rpc); -const data = await onChainEnricher.fetchAdditionalPoolData(pools); -const enrichered = onChainEnricher.enrichPoolsWithData(pools.pools, data); +const onChainEnricher = new OnChainPoolDataEnricher( + chain.id, + rpc, + MULTICALL[chain.id], + BATCHSIZE[chain.id], +); +// const blockNumber = +const providerOptions: ProviderSwapOptions = { + // block: blockNumber, + timestamp: BigInt(Date.now()), +}; +const data = await onChainEnricher.fetchAdditionalPoolData( + pools, + providerOptions, +); +const enriched = onChainEnricher.enrichPoolsWithData(pools.pools, data); -console.log(enrichered, data.length); +console.log(enriched, data.length); From 67e4120af38d392c42725b6350fcd296e05364ab Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 13:58:19 +0100 Subject: [PATCH 11/23] Add changeset. --- .changeset/few-mayflies-change.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/few-mayflies-change.md diff --git a/.changeset/few-mayflies-change.md b/.changeset/few-mayflies-change.md new file mode 100644 index 00000000..80d63008 --- /dev/null +++ b/.changeset/few-mayflies-change.md @@ -0,0 +1,11 @@ +--- +"@balancer/sdk": minor +--- + +Replace dataQueries with multicallV3: + +- Add multicallV3 ABI +- Add manual batching (instead of Viems) so we can maintain custom blockNo +- Onchain calls correctly mapped to pool/version +- Filter bricked pools from vulnerability +- Fix scalingFactor scaling From 53ab35932a654b4c3bd658a92bf6e3d7d61ef613 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 14:54:30 +0100 Subject: [PATCH 12/23] chore: Add Fantom config. Update to have network specific vault addr. --- debug/multicall.ts | 2 + src/data/enrichers/onChainPoolDataEnricher.ts | 2 + src/data/onChainPoolDataViaReadContract.ts | 45 +++++--- src/entities/swap.ts | 2 +- src/sor.ts | 4 +- src/utils/constants.ts | 36 +++++- test/fxPool.integration.test.ts | 3 +- test/gyroEV2Pool.integration.test.ts | 3 +- test/sor.test.ts | 106 +++++++++++++++++- test/subgraph.test.ts | 13 +++ 10 files changed, 193 insertions(+), 23 deletions(-) diff --git a/debug/multicall.ts b/debug/multicall.ts index d34b1f99..d2538d14 100644 --- a/debug/multicall.ts +++ b/debug/multicall.ts @@ -4,6 +4,7 @@ import { MULTICALL, BATCHSIZE, ProviderSwapOptions, + VAULT, } from '../src'; // rome-ignore lint/correctness/noUnusedVariables: import { mainnet, polygonZkEvm } from 'viem/chains'; @@ -44,6 +45,7 @@ const onChainEnricher = new OnChainPoolDataEnricher( rpc, MULTICALL[chain.id], BATCHSIZE[chain.id], + VAULT[chain.id], ); // const blockNumber = const providerOptions: ProviderSwapOptions = { diff --git a/src/data/enrichers/onChainPoolDataEnricher.ts b/src/data/enrichers/onChainPoolDataEnricher.ts index 55857059..e0868425 100644 --- a/src/data/enrichers/onChainPoolDataEnricher.ts +++ b/src/data/enrichers/onChainPoolDataEnricher.ts @@ -43,6 +43,7 @@ export class OnChainPoolDataEnricher implements PoolDataEnricher { private readonly rpcUrl: string, private readonly multicallAddress: Address, private readonly batchSize: number, + private readonly vault: Address, ) { this.client = createPublicClient({ transport: http(this.rpcUrl, { timeout: 60_000 }), @@ -55,6 +56,7 @@ export class OnChainPoolDataEnricher implements PoolDataEnricher { options: SwapOptions, ): Promise { return fetchAdditionalPoolData( + this.vault, this.multicallAddress, data.pools, this.client, diff --git a/src/data/onChainPoolDataViaReadContract.ts b/src/data/onChainPoolDataViaReadContract.ts index dbb9b13d..86550f29 100644 --- a/src/data/onChainPoolDataViaReadContract.ts +++ b/src/data/onChainPoolDataViaReadContract.ts @@ -78,10 +78,9 @@ const getSwapFeeFn = (poolType: string) => { const defaultCalls = { count: 4, - build: (id: string, poolType: string): BuildReturn[] => [ + build: (id: string, poolType: string, vault: Address): BuildReturn[] => [ { - address: - '0xBA12222222228d8Ba445958a75a0704d566BF2C8' as `0x${string}`, + address: vault, abi, functionName: 'getPoolTokens', args: [id as Hex], @@ -115,8 +114,8 @@ const defaultCalls = { // These don't exist on some earlier pool versions const defaultCallsAux = { count: 2 + defaultCalls.count, - build: (id: string, poolType: string): BuildReturn[] => [ - ...defaultCalls.build(id, poolType), + build: (id: string, poolType: string, vault: Address): BuildReturn[] => [ + ...defaultCalls.build(id, poolType, vault), { address: getPoolAddress(id) as `0x${string}`, abi, @@ -207,7 +206,11 @@ const gyroECalls = { }), }; -const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { +const poolTypeCalls = ( + poolType: string, + poolTypeVersion: number, + vault: Address, +) => { const do_nothing = { count: 0, build: () => [], @@ -222,7 +225,7 @@ const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { return { count: defaultCalls.count + weightedCalls.count, build: (id: string) => [ - ...defaultCalls.build(id, poolType), + ...defaultCalls.build(id, poolType, vault), ...weightedCalls.build(id), ], parse: (results: Results, shift: number) => ({ @@ -237,7 +240,7 @@ const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { return { count: defaultCallsAux.count + weightedCalls.count, build: (id: string) => [ - ...defaultCallsAux.build(id, poolType), + ...defaultCallsAux.build(id, poolType, vault), ...weightedCalls.build(id), ], parse: (results: Results, shift: number) => ({ @@ -254,7 +257,7 @@ const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { return { count: defaultCalls.count + stableCalls.count, build: (id: string) => [ - ...defaultCalls.build(id, poolType), + ...defaultCalls.build(id, poolType, vault), ...stableCalls.build(id), ], parse: (results: Results, shift: number) => ({ @@ -269,7 +272,7 @@ const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { return { count: defaultCallsAux.count + stableCalls.count, build: (id: string) => [ - ...defaultCallsAux.build(id, poolType), + ...defaultCallsAux.build(id, poolType, vault), ...stableCalls.build(id), ], parse: (results: Results, shift: number) => ({ @@ -287,7 +290,7 @@ const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { return { count: defaultCalls.count + stableCalls.count, build: (id: string) => [ - ...defaultCalls.build(id, poolType), + ...defaultCalls.build(id, poolType, vault), ...stableCalls.build(id), ], parse: (results: Results, shift: number) => ({ @@ -299,7 +302,7 @@ const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { return { count: defaultCallsAux.count + stableCalls.count, build: (id: string) => [ - ...defaultCallsAux.build(id, poolType), + ...defaultCallsAux.build(id, poolType, vault), ...stableCalls.build(id), ], parse: (results: Results, shift: number) => ({ @@ -316,7 +319,7 @@ const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { return { count: defaultCalls.count + gyroECalls.count, build: (id: string) => [ - ...defaultCalls.build(id, poolType), + ...defaultCalls.build(id, poolType, vault), ...gyroECalls.build(id), ], parse: (results: Results, shift: number) => ({ @@ -335,7 +338,7 @@ const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { return { count: defaultCalls.count + linearCalls.count, build: (id: string) => [ - ...defaultCalls.build(id, poolType), + ...defaultCalls.build(id, poolType, vault), ...linearCalls.build(id), ], parse: (results: Results, shift: number) => ({ @@ -355,6 +358,7 @@ const poolTypeCalls = (poolType: string, poolTypeVersion = 1) => { }; export const fetchAdditionalPoolData = async ( + vault: Address, multicallAddress: Address, pools: { id: string; @@ -370,7 +374,11 @@ export const fetchAdditionalPoolData = async ( } const calls = pools.flatMap(({ id, poolType, poolTypeVersion }) => - poolTypeCalls(poolType, poolTypeVersion).build(id, poolType), + poolTypeCalls(poolType, poolTypeVersion, vault).build( + id, + poolType, + vault, + ), ); const batchedCalls = calls.map(({ address, functionName, args }) => { @@ -443,9 +451,12 @@ export const fetchAdditionalPoolData = async ( return pools.map(({ id, poolType, poolTypeVersion }) => { const result = { id, - ...poolTypeCalls(poolType, poolTypeVersion).parse(results, shift), + ...poolTypeCalls(poolType, poolTypeVersion, vault).parse( + results, + shift, + ), } as OnChainPoolData; - shift += poolTypeCalls(poolType, poolTypeVersion).count; + shift += poolTypeCalls(poolType, poolTypeVersion, vault).count; return result; }); }; diff --git a/src/entities/swap.ts b/src/entities/swap.ts index 10b37d28..733a0ef7 100644 --- a/src/entities/swap.ts +++ b/src/entities/swap.ts @@ -157,7 +157,7 @@ export class Swap { }); const queriesContract = getContract({ - address: BALANCER_QUERIES, + address: BALANCER_QUERIES[this.chainId], abi: balancerQueriesAbi, publicClient, }); diff --git a/src/sor.ts b/src/sor.ts index 717137c9..1a505e50 100644 --- a/src/sor.ts +++ b/src/sor.ts @@ -6,6 +6,7 @@ import { SUBGRAPH_URLS, MULTICALL, BATCHSIZE, + VAULT, } from './utils'; import { SorConfig, SwapInputRawAmount, SwapKind, SwapOptions } from './types'; import { PoolParser } from './entities/pools/parser'; @@ -41,7 +42,8 @@ export class SmartOrderRouter { chainId, rpcUrl, MULTICALL[chainId], - BATCHSIZE[this.chainId], + BATCHSIZE[chainId], + VAULT[chainId], ); this.poolDataService = new PoolDataService( Array.isArray(poolDataProviders) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 017b04f0..dad80a41 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -60,6 +60,7 @@ export enum ChainId { ARBITRUM_ONE = 42161, AVALANCHE = 43114, BASE_GOERLI = 84531, + FANTOM = 250, } export const CHAINS: Record = { @@ -77,6 +78,17 @@ export const CHAINS: Record = { [ChainId.BASE_GOERLI]: baseGoerli, }; +export const VAULT: Record = { + [ChainId.ARBITRUM_ONE]: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + [ChainId.AVALANCHE]: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + [ChainId.GNOSIS_CHAIN]: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + [ChainId.MAINNET]: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + [ChainId.OPTIMISM]: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + [ChainId.POLYGON]: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + [ChainId.ZKEVM]: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + [ChainId.FANTOM]: '0x20dd72Ed959b6147912C2e529F0a0C651c33c9ce', +}; + export const SUBGRAPH_URLS = { [ChainId.MAINNET]: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-v2', @@ -100,6 +112,8 @@ export const SUBGRAPH_URLS = { 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-avalanche-v2', [ChainId.BASE_GOERLI]: 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer-base-goerli-v2', + [ChainId.FANTOM]: + 'https://api.thegraph.com/subgraphs/name/beethovenxfi/beethovenx-v2-fantom', }; export const STELLATE_URLS = { @@ -111,7 +125,17 @@ export const STELLATE_URLS = { [ChainId.ARBITRUM_ONE]: 'https://balancer-arbitrum-v2.stellate.balancer.fi', }; -export const BALANCER_QUERIES = '0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5'; +export const BALANCER_QUERIES: Record = { + [ChainId.ARBITRUM_ONE]: '0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5', + [ChainId.AVALANCHE]: '0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5', + [ChainId.GNOSIS_CHAIN]: '0x0f3e0c4218b7b0108a3643cfe9d3ec0d4f57c54e', + [ChainId.MAINNET]: '0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5', + [ChainId.OPTIMISM]: '0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5', + [ChainId.POLYGON]: '0xE39B5e3B6D74016b2F6A9673D7d7493B6DF549d5', + [ChainId.ZKEVM]: '0x809b79b53f18e9bc08a961ed4678b901ac93213a', + [ChainId.FANTOM]: '0x1B0A42663DF1edeA171cD8732d288a81EFfF6d23', +}; + export const MULTICALL: Record = { [ChainId.ARBITRUM_ONE]: '0xca11bde05977b3631167028862be2a173976ca11', [ChainId.AVALANCHE]: '0xca11bde05977b3631167028862be2a173976ca11', @@ -120,6 +144,7 @@ export const MULTICALL: Record = { [ChainId.OPTIMISM]: '0xca11bde05977b3631167028862be2a173976ca11', [ChainId.POLYGON]: '0xca11bde05977b3631167028862be2a173976ca11', [ChainId.ZKEVM]: '0xca11bde05977b3631167028862be2a173976ca11', + [ChainId.FANTOM]: '0xca11bde05977b3631167028862be2a173976ca11', }; export const BATCHSIZE: Record = { [ChainId.ARBITRUM_ONE]: 1000, @@ -129,6 +154,7 @@ export const BATCHSIZE: Record = { [ChainId.OPTIMISM]: 1000, [ChainId.POLYGON]: 1000, [ChainId.ZKEVM]: 128, + [ChainId.FANTOM]: 128, }; export const NATIVE_ASSETS = { @@ -172,6 +198,14 @@ export const NATIVE_ASSETS = { 'Ether', '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', ), + [ChainId.FANTOM]: new Token( + ChainId.FANTOM, + NATIVE_ADDRESS, + 18, + 'FANTOM', + 'Fantom', + '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83', + ), }; export const ETH = NATIVE_ASSETS[ChainId.MAINNET]; diff --git a/test/fxPool.integration.test.ts b/test/fxPool.integration.test.ts index 9ac8ea9a..f07bb924 100644 --- a/test/fxPool.integration.test.ts +++ b/test/fxPool.integration.test.ts @@ -3,7 +3,7 @@ import { beforeAll, beforeEach, describe, expect, test } from 'vitest'; import dotenv from 'dotenv'; dotenv.config(); -import { BATCHSIZE, ChainId, MULTICALL } from '../src/utils'; +import { BATCHSIZE, ChainId, MULTICALL, VAULT } from '../src/utils'; import { BasePool, OnChainPoolDataEnricher, @@ -51,6 +51,7 @@ describe('fx integration tests', () => { rpcUrl, MULTICALL[chainId], BATCHSIZE[chainId], + VAULT[chainId], ); sor = new SmartOrderRouter({ diff --git a/test/gyroEV2Pool.integration.test.ts b/test/gyroEV2Pool.integration.test.ts index 2c17ce5f..654110fe 100644 --- a/test/gyroEV2Pool.integration.test.ts +++ b/test/gyroEV2Pool.integration.test.ts @@ -4,7 +4,7 @@ import dotenv from 'dotenv'; dotenv.config(); import testPools from './lib/testData/gyroETestPool.json'; -import { BATCHSIZE, ChainId, MULTICALL } from '../src/utils'; +import { BATCHSIZE, ChainId, MULTICALL, VAULT } from '../src/utils'; import { BasePool, OnChainPoolDataEnricher, @@ -47,6 +47,7 @@ describe('gyroEV2: WMATIC-stMATIC integration tests', () => { rpcUrl, MULTICALL[chainId], BATCHSIZE[chainId], + VAULT[chainId], ); sor = new SmartOrderRouter({ diff --git a/test/sor.test.ts b/test/sor.test.ts index d3bb41b1..1e2c0a1e 100644 --- a/test/sor.test.ts +++ b/test/sor.test.ts @@ -6,7 +6,14 @@ dotenv.config(); import { SmartOrderRouter } from '../src/sor'; import { sorGetSwapsWithPools } from '../src/static'; import { SubgraphPoolProvider } from '../src/data/providers/subgraphPoolProvider'; -import { ChainId, ETH, MULTICALL, BATCHSIZE } from '../src/utils'; +import { + ChainId, + ETH, + NATIVE_ASSETS, + MULTICALL, + BATCHSIZE, + VAULT, +} from '../src/utils'; import { Token, TokenAmount } from '../src/entities'; import { OnChainPoolDataEnricher } from '../src/data/enrichers/onChainPoolDataEnricher'; import { SwapKind, SwapOptions } from '../src/types'; @@ -23,6 +30,7 @@ describe('SmartOrderRouter', () => { rpcUrl, MULTICALL[chainId], BATCHSIZE[chainId], + VAULT[chainId], ); const sor = new SmartOrderRouter({ @@ -173,4 +181,100 @@ describe('SmartOrderRouter', () => { }); }); }); + describe('Fantom', () => { + const chainId = ChainId.FANTOM; + const inputToken = NATIVE_ASSETS[chainId]; + const rpcUrl = process.env.FANTOM_RPC_URL || ''; + const subgraphPoolDataService = new SubgraphPoolProvider( + chainId, + undefined, + { + poolIdIn: [ + '0x9e4341acef4147196e99d648c5e43b3fc9d026780002000000000000000005ec', + ], + }, + ); + const onChainPoolDataEnricher = new OnChainPoolDataEnricher( + chainId, + rpcUrl, + MULTICALL[chainId], + BATCHSIZE[chainId], + VAULT[chainId], + ); + + const sor = new SmartOrderRouter({ + chainId, + poolDataProviders: subgraphPoolDataService, + poolDataEnrichers: onChainPoolDataEnricher, + rpcUrl: rpcUrl, + }); + + const BEETS = new Token( + chainId, + '0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e', + 18, + 'BEETS', + ); + + const swapOptions: SwapOptions = { + block: 65313450n, + }; + + let pools: BasePool[]; + // Since constructing a Swap mutates the pool balances, we refetch for each test + // May be a better way to deep clone a BasePool[] class instead + beforeEach(async () => { + pools = await sor.fetchAndCachePools(swapOptions.block); + }); + + describe('Native Swaps', () => { + test('Native -> Token givenIn', async () => { + const inputAmount = TokenAmount.fromHumanAmount( + inputToken, + '100', + ); + + const swap = await sorGetSwapsWithPools( + inputToken, + BEETS, + SwapKind.GivenIn, + inputAmount, + pools, + swapOptions, + ); + + if (!swap) throw new Error('Swap is undefined'); + + const onchain = await swap.query(rpcUrl, swapOptions.block); + + expect(swap.quote.amount).toEqual(onchain.amount); + expect(swap.inputAmount.amount).toEqual(inputAmount.amount); + expect(swap.outputAmount.amount).toEqual(swap.quote.amount); + }); + + test('Native ETH -> Token givenOut', async () => { + const outputAmount = TokenAmount.fromHumanAmount( + BEETS, + '100000', + ); + + const swap = await sorGetSwapsWithPools( + inputToken, + BEETS, + SwapKind.GivenOut, + outputAmount, + pools, + swapOptions, + ); + + if (!swap) throw new Error('Swap is undefined'); + + const onchain = await swap.query(rpcUrl, swapOptions.block); + + expect(swap.quote.amount).toEqual(onchain.amount); + expect(swap.inputAmount.amount).toEqual(swap.quote.amount); + expect(swap.outputAmount.amount).toEqual(outputAmount.amount); + }); + }); + }); }); diff --git a/test/subgraph.test.ts b/test/subgraph.test.ts index de56c4eb..0ecfda94 100644 --- a/test/subgraph.test.ts +++ b/test/subgraph.test.ts @@ -12,6 +12,19 @@ describe('SubgraphPoolProvider', () => { timestamp: BigInt(Math.floor(new Date().getTime() / 1000)), }; + const { pools } = await subgraphPoolDataService.getPools( + providerOptions, + ); + expect(pools.length).toBeGreaterThan(0); + }); + test('getPools fantom', async () => { + const chainId = ChainId.FANTOM; + const subgraphPoolDataService = new SubgraphPoolProvider(chainId); + + const providerOptions: ProviderSwapOptions = { + timestamp: BigInt(Math.floor(new Date().getTime() / 1000)), + }; + const { pools } = await subgraphPoolDataService.getPools( providerOptions, ); From f6d1051103e96481e933fd9ce9d6c7a8b1e96273 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 14:55:04 +0100 Subject: [PATCH 13/23] Changeset. --- .changeset/eleven-trains-kneel.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eleven-trains-kneel.md diff --git a/.changeset/eleven-trains-kneel.md b/.changeset/eleven-trains-kneel.md new file mode 100644 index 00000000..753d4307 --- /dev/null +++ b/.changeset/eleven-trains-kneel.md @@ -0,0 +1,5 @@ +--- +"@balancer/sdk": minor +--- + +Add Fantom config. Update to have network specific vault addr. From c8e194867600e169515a6358fc1d68c5d923b905 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 15:10:35 +0100 Subject: [PATCH 14/23] Fix linting. --- src/data/providers/subgraphPoolProvider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/providers/subgraphPoolProvider.ts b/src/data/providers/subgraphPoolProvider.ts index 142c1c55..08b0f91c 100644 --- a/src/data/providers/subgraphPoolProvider.ts +++ b/src/data/providers/subgraphPoolProvider.ts @@ -7,7 +7,6 @@ import { import { fetchWithRetry } from '../../utils/fetch'; import { SUBGRAPH_URLS, brickedPools } from '../../utils'; -// rome-ignore lint/complexity/useLiteralKeys: BigInt.prototype['toJSON'] = function () { return this.toString(); }; From d2582f9568dacda2d0a81ae20d44e5a76765bb3b Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Wed, 11 Oct 2023 16:01:02 +0100 Subject: [PATCH 15/23] fix: Lint. --- src/data/providers/subgraphPoolProvider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/providers/subgraphPoolProvider.ts b/src/data/providers/subgraphPoolProvider.ts index 142c1c55..08b0f91c 100644 --- a/src/data/providers/subgraphPoolProvider.ts +++ b/src/data/providers/subgraphPoolProvider.ts @@ -7,7 +7,6 @@ import { import { fetchWithRetry } from '../../utils/fetch'; import { SUBGRAPH_URLS, brickedPools } from '../../utils'; -// rome-ignore lint/complexity/useLiteralKeys: BigInt.prototype['toJSON'] = function () { return this.toString(); }; From a7d0bff4b70391d3525b90c0ad9180f95fb702fe Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Oct 2023 08:44:33 +0000 Subject: [PATCH 16/23] Version Packages --- .changeset/perfect-ties-play.md | 5 ----- CHANGELOG.md | 6 ++++++ package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/perfect-ties-play.md diff --git a/.changeset/perfect-ties-play.md b/.changeset/perfect-ties-play.md deleted file mode 100644 index 0415a01a..00000000 --- a/.changeset/perfect-ties-play.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@balancer/sdk": patch ---- - -Add missing gyro3 to SupportedRawPoolTypes. diff --git a/CHANGELOG.md b/CHANGELOG.md index 750294ba..0f9305b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @balancer/sdk +## 0.1.2 + +### Patch Changes + +- 7004561: Add missing gyro3 to SupportedRawPoolTypes. + ## 0.1.1 ### Patch Changes diff --git a/package.json b/package.json index 05862910..b541a04b 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "publishConfig": { "access": "public" }, - "version": "0.1.1", + "version": "0.1.2", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", From 811431fcce7ca1363a4fff4cf598491b18af1c05 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 12 Oct 2023 10:07:38 +0100 Subject: [PATCH 17/23] CI: Add FANTOM_RPC_URL for tests. --- .github/workflows/checks.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 83ce0682..45400b72 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -47,6 +47,7 @@ jobs: env: ETHEREUM_RPC_URL: ${{ secrets.ETHEREUM_RPC_URL }} POLYGON_RPC_URL: ${{ secrets.POLYGON_RPC_URL }} + FANTOM_RPC_URL: ${{ secrets.FANTOM_RPC_URL }} build: name: Build From e0d591ace102b2f23584ad7bc62d38c68b404a30 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 12 Oct 2023 10:10:24 +0100 Subject: [PATCH 18/23] test: Add longer timeout to Subgraph tests. --- test/subgraph.test.ts | 54 ++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/test/subgraph.test.ts b/test/subgraph.test.ts index 0ecfda94..470c4c8e 100644 --- a/test/subgraph.test.ts +++ b/test/subgraph.test.ts @@ -3,31 +3,37 @@ import { SubgraphPoolProvider } from '../src/data/providers/subgraphPoolProvider import { ChainId } from '../src/utils'; import { ProviderSwapOptions } from '../src/data/types'; -describe('SubgraphPoolProvider', () => { - test('getPools mainnet', async () => { - const chainId = ChainId.MAINNET; - const subgraphPoolDataService = new SubgraphPoolProvider(chainId); +describe( + 'SubgraphPoolProvider', + () => { + test('getPools mainnet', async () => { + const chainId = ChainId.MAINNET; + const subgraphPoolDataService = new SubgraphPoolProvider(chainId); - const providerOptions: ProviderSwapOptions = { - timestamp: BigInt(Math.floor(new Date().getTime() / 1000)), - }; + const providerOptions: ProviderSwapOptions = { + timestamp: BigInt(Math.floor(new Date().getTime() / 1000)), + }; - const { pools } = await subgraphPoolDataService.getPools( - providerOptions, - ); - expect(pools.length).toBeGreaterThan(0); - }); - test('getPools fantom', async () => { - const chainId = ChainId.FANTOM; - const subgraphPoolDataService = new SubgraphPoolProvider(chainId); + const { pools } = await subgraphPoolDataService.getPools( + providerOptions, + ); + expect(pools.length).toBeGreaterThan(0); + }); + test('getPools fantom', async () => { + const chainId = ChainId.FANTOM; + const subgraphPoolDataService = new SubgraphPoolProvider(chainId); - const providerOptions: ProviderSwapOptions = { - timestamp: BigInt(Math.floor(new Date().getTime() / 1000)), - }; + const providerOptions: ProviderSwapOptions = { + timestamp: BigInt(Math.floor(new Date().getTime() / 1000)), + }; - const { pools } = await subgraphPoolDataService.getPools( - providerOptions, - ); - expect(pools.length).toBeGreaterThan(0); - }); -}); + const { pools } = await subgraphPoolDataService.getPools( + providerOptions, + ); + expect(pools.length).toBeGreaterThan(0); + }); + }, + { + timeout: 60000, + }, +); From c83bdcfadd7fc7074cfd93448740e724eb7362a9 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 12 Oct 2023 10:20:45 +0100 Subject: [PATCH 19/23] test: Add longer timeout to sor.test. --- test/sor.test.ts | 508 ++++++++++++++++++++++++----------------------- 1 file changed, 262 insertions(+), 246 deletions(-) diff --git a/test/sor.test.ts b/test/sor.test.ts index 1e2c0a1e..ba67b437 100644 --- a/test/sor.test.ts +++ b/test/sor.test.ts @@ -19,262 +19,278 @@ import { OnChainPoolDataEnricher } from '../src/data/enrichers/onChainPoolDataEn import { SwapKind, SwapOptions } from '../src/types'; import { BasePool } from '../src/entities/pools'; -describe('SmartOrderRouter', () => { - describe('Mainnet', () => { - const chainId = ChainId.MAINNET; - const rpcUrl = - process.env.ETHEREUM_RPC_URL || 'https://eth.llamarpc.com'; - const subgraphPoolDataService = new SubgraphPoolProvider(chainId); - const onChainPoolDataEnricher = new OnChainPoolDataEnricher( - chainId, - rpcUrl, - MULTICALL[chainId], - BATCHSIZE[chainId], - VAULT[chainId], - ); - - const sor = new SmartOrderRouter({ - chainId, - poolDataProviders: subgraphPoolDataService, - poolDataEnrichers: onChainPoolDataEnricher, - rpcUrl: rpcUrl, - }); - - const BAL = new Token( - chainId, - '0xba100000625a3754423978a60c9317c58a424e3D', - 18, - 'BAL', - ); - const USDC = new Token( - chainId, - '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - 6, - 'USDC', - ); - const USDT = new Token( - chainId, - '0xdAC17F958D2ee523a2206206994597C13D831ec7', - 6, - 'USDT', - ); - const DAI = new Token( - chainId, - '0x6B175474E89094C44Da98b954EedeAC495271d0F', - 18, - 'DAI', - ); - - const swapOptions: SwapOptions = { - block: 17473810n, - }; - - let pools: BasePool[]; - // Since constructing a Swap mutates the pool balances, we refetch for each test - // May be a better way to deep clone a BasePool[] class instead - beforeEach(async () => { - pools = await sor.fetchAndCachePools(swapOptions.block); - }); - - describe('Weighted Pools', () => { - // ETH -> BAL swapGivenIn single hop - // Weighted pool - // 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014 - test('Native ETH -> Token givenIn single hop', async () => { - const inputAmount = TokenAmount.fromHumanAmount(ETH, '1'); - - const swap = await sorGetSwapsWithPools( - ETH, - BAL, - SwapKind.GivenIn, - inputAmount, - pools, - swapOptions, - ); - - if (!swap) throw new Error('Swap is undefined'); - - const onchain = await swap.query(rpcUrl, swapOptions.block); - - expect(swap.quote.amount).toEqual(onchain.amount); - expect(swap.inputAmount.amount).toEqual(inputAmount.amount); - expect(swap.outputAmount.amount).toEqual(swap.quote.amount); - expect(swap.paths.length).toEqual(1); - expect(swap.paths[0].pools.length).toEqual(1); - expect(swap.paths[0].pools[0].id).toEqual( - '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014', - ); +describe( + 'SmartOrderRouter', + () => { + describe('Mainnet', () => { + const chainId = ChainId.MAINNET; + const rpcUrl = + process.env.ETHEREUM_RPC_URL || 'https://eth.llamarpc.com'; + const subgraphPoolDataService = new SubgraphPoolProvider(chainId); + const onChainPoolDataEnricher = new OnChainPoolDataEnricher( + chainId, + rpcUrl, + MULTICALL[chainId], + BATCHSIZE[chainId], + VAULT[chainId], + ); + + const sor = new SmartOrderRouter({ + chainId, + poolDataProviders: subgraphPoolDataService, + poolDataEnrichers: onChainPoolDataEnricher, + rpcUrl: rpcUrl, }); - // ETH -> BAL swapGivenOut single hop - // Weighted pool - // 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014 - test('Native ETH -> Token givenOut single hop', async () => { - const outputAmount = TokenAmount.fromHumanAmount(BAL, '100'); - - const swap = await sorGetSwapsWithPools( - ETH, - BAL, - SwapKind.GivenOut, - outputAmount, - pools, - swapOptions, - ); - - if (!swap) throw new Error('Swap is undefined'); - - const onchain = await swap.query(rpcUrl, swapOptions.block); - - expect(swap.quote.amount).toEqual(onchain.amount); - expect(swap.inputAmount.amount).toEqual(swap.quote.amount); - expect(swap.outputAmount.amount).toEqual(outputAmount.amount); - expect(swap.paths.length).toEqual(1); - expect(swap.paths[0].pools.length).toEqual(1); - expect(swap.paths[0].pools[0].id).toEqual( - '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014', - ); + const BAL = new Token( + chainId, + '0xba100000625a3754423978a60c9317c58a424e3D', + 18, + 'BAL', + ); + const USDC = new Token( + chainId, + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + 6, + 'USDC', + ); + const USDT = new Token( + chainId, + '0xdAC17F958D2ee523a2206206994597C13D831ec7', + 6, + 'USDT', + ); + const DAI = new Token( + chainId, + '0x6B175474E89094C44Da98b954EedeAC495271d0F', + 18, + 'DAI', + ); + + const swapOptions: SwapOptions = { + block: 17473810n, + }; + + let pools: BasePool[]; + // Since constructing a Swap mutates the pool balances, we refetch for each test + // May be a better way to deep clone a BasePool[] class instead + beforeEach(async () => { + pools = await sor.fetchAndCachePools(swapOptions.block); }); - }); - describe('Stable Pools', () => { - test('DAI -> USDT givenIn ComposableStable', async () => { - const inputAmount = TokenAmount.fromHumanAmount(DAI, '100000'); - - const swap = await sorGetSwapsWithPools( - DAI, - USDT, - SwapKind.GivenIn, - inputAmount, - pools, - swapOptions, - ); - - if (!swap) throw new Error('Swap is undefined'); - - const onchain = await swap.query(rpcUrl, swapOptions.block); - expect(swap.quote.amount).toEqual(onchain.amount); - expect(swap.inputAmount.amount).toEqual(inputAmount.amount); - expect(swap.outputAmount.amount).toEqual(swap.quote.amount); - expect(swap.paths.length).toEqual(1); - expect(swap.paths[0].pools.length).toEqual(1); + describe('Weighted Pools', () => { + // ETH -> BAL swapGivenIn single hop + // Weighted pool + // 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014 + test('Native ETH -> Token givenIn single hop', async () => { + const inputAmount = TokenAmount.fromHumanAmount(ETH, '1'); + + const swap = await sorGetSwapsWithPools( + ETH, + BAL, + SwapKind.GivenIn, + inputAmount, + pools, + swapOptions, + ); + + if (!swap) throw new Error('Swap is undefined'); + + const onchain = await swap.query(rpcUrl, swapOptions.block); + + expect(swap.quote.amount).toEqual(onchain.amount); + expect(swap.inputAmount.amount).toEqual(inputAmount.amount); + expect(swap.outputAmount.amount).toEqual(swap.quote.amount); + expect(swap.paths.length).toEqual(1); + expect(swap.paths[0].pools.length).toEqual(1); + expect(swap.paths[0].pools[0].id).toEqual( + '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014', + ); + }); + + // ETH -> BAL swapGivenOut single hop + // Weighted pool + // 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014 + test('Native ETH -> Token givenOut single hop', async () => { + const outputAmount = TokenAmount.fromHumanAmount( + BAL, + '100', + ); + + const swap = await sorGetSwapsWithPools( + ETH, + BAL, + SwapKind.GivenOut, + outputAmount, + pools, + swapOptions, + ); + + if (!swap) throw new Error('Swap is undefined'); + + const onchain = await swap.query(rpcUrl, swapOptions.block); + + expect(swap.quote.amount).toEqual(onchain.amount); + expect(swap.inputAmount.amount).toEqual(swap.quote.amount); + expect(swap.outputAmount.amount).toEqual( + outputAmount.amount, + ); + expect(swap.paths.length).toEqual(1); + expect(swap.paths[0].pools.length).toEqual(1); + expect(swap.paths[0].pools[0].id).toEqual( + '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014', + ); + }); }); - test('USDC -> DAI givenOut ComposableStable', async () => { - const outputAmount = TokenAmount.fromHumanAmount( - DAI, - '1000000', - ); - - const swap = await sorGetSwapsWithPools( - USDC, - DAI, - SwapKind.GivenOut, - outputAmount, - pools, - swapOptions, - ); - - if (!swap) throw new Error('Swap is undefined'); - - const onchain = await swap.query(rpcUrl, swapOptions.block); - expect(swap.quote.amount).toEqual(onchain.amount); + describe('Stable Pools', () => { + test('DAI -> USDT givenIn ComposableStable', async () => { + const inputAmount = TokenAmount.fromHumanAmount( + DAI, + '100000', + ); + + const swap = await sorGetSwapsWithPools( + DAI, + USDT, + SwapKind.GivenIn, + inputAmount, + pools, + swapOptions, + ); + + if (!swap) throw new Error('Swap is undefined'); + + const onchain = await swap.query(rpcUrl, swapOptions.block); + expect(swap.quote.amount).toEqual(onchain.amount); + expect(swap.inputAmount.amount).toEqual(inputAmount.amount); + expect(swap.outputAmount.amount).toEqual(swap.quote.amount); + expect(swap.paths.length).toEqual(1); + expect(swap.paths[0].pools.length).toEqual(1); + }); + + test('USDC -> DAI givenOut ComposableStable', async () => { + const outputAmount = TokenAmount.fromHumanAmount( + DAI, + '1000000', + ); + + const swap = await sorGetSwapsWithPools( + USDC, + DAI, + SwapKind.GivenOut, + outputAmount, + pools, + swapOptions, + ); + + if (!swap) throw new Error('Swap is undefined'); + + const onchain = await swap.query(rpcUrl, swapOptions.block); + expect(swap.quote.amount).toEqual(onchain.amount); + }); }); }); - }); - describe('Fantom', () => { - const chainId = ChainId.FANTOM; - const inputToken = NATIVE_ASSETS[chainId]; - const rpcUrl = process.env.FANTOM_RPC_URL || ''; - const subgraphPoolDataService = new SubgraphPoolProvider( - chainId, - undefined, - { - poolIdIn: [ - '0x9e4341acef4147196e99d648c5e43b3fc9d026780002000000000000000005ec', - ], - }, - ); - const onChainPoolDataEnricher = new OnChainPoolDataEnricher( - chainId, - rpcUrl, - MULTICALL[chainId], - BATCHSIZE[chainId], - VAULT[chainId], - ); - - const sor = new SmartOrderRouter({ - chainId, - poolDataProviders: subgraphPoolDataService, - poolDataEnrichers: onChainPoolDataEnricher, - rpcUrl: rpcUrl, - }); - - const BEETS = new Token( - chainId, - '0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e', - 18, - 'BEETS', - ); - - const swapOptions: SwapOptions = { - block: 65313450n, - }; - - let pools: BasePool[]; - // Since constructing a Swap mutates the pool balances, we refetch for each test - // May be a better way to deep clone a BasePool[] class instead - beforeEach(async () => { - pools = await sor.fetchAndCachePools(swapOptions.block); - }); - - describe('Native Swaps', () => { - test('Native -> Token givenIn', async () => { - const inputAmount = TokenAmount.fromHumanAmount( - inputToken, - '100', - ); - - const swap = await sorGetSwapsWithPools( - inputToken, - BEETS, - SwapKind.GivenIn, - inputAmount, - pools, - swapOptions, - ); - - if (!swap) throw new Error('Swap is undefined'); - - const onchain = await swap.query(rpcUrl, swapOptions.block); - - expect(swap.quote.amount).toEqual(onchain.amount); - expect(swap.inputAmount.amount).toEqual(inputAmount.amount); - expect(swap.outputAmount.amount).toEqual(swap.quote.amount); + describe('Fantom', () => { + const chainId = ChainId.FANTOM; + const inputToken = NATIVE_ASSETS[chainId]; + const rpcUrl = process.env.FANTOM_RPC_URL || ''; + const subgraphPoolDataService = new SubgraphPoolProvider( + chainId, + undefined, + { + poolIdIn: [ + '0x9e4341acef4147196e99d648c5e43b3fc9d026780002000000000000000005ec', + ], + }, + ); + const onChainPoolDataEnricher = new OnChainPoolDataEnricher( + chainId, + rpcUrl, + MULTICALL[chainId], + BATCHSIZE[chainId], + VAULT[chainId], + ); + + const sor = new SmartOrderRouter({ + chainId, + poolDataProviders: subgraphPoolDataService, + poolDataEnrichers: onChainPoolDataEnricher, + rpcUrl: rpcUrl, }); - test('Native ETH -> Token givenOut', async () => { - const outputAmount = TokenAmount.fromHumanAmount( - BEETS, - '100000', - ); - - const swap = await sorGetSwapsWithPools( - inputToken, - BEETS, - SwapKind.GivenOut, - outputAmount, - pools, - swapOptions, - ); - - if (!swap) throw new Error('Swap is undefined'); - - const onchain = await swap.query(rpcUrl, swapOptions.block); + const BEETS = new Token( + chainId, + '0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e', + 18, + 'BEETS', + ); + + const swapOptions: SwapOptions = { + block: 65313450n, + }; + + let pools: BasePool[]; + // Since constructing a Swap mutates the pool balances, we refetch for each test + // May be a better way to deep clone a BasePool[] class instead + beforeEach(async () => { + pools = await sor.fetchAndCachePools(swapOptions.block); + }); - expect(swap.quote.amount).toEqual(onchain.amount); - expect(swap.inputAmount.amount).toEqual(swap.quote.amount); - expect(swap.outputAmount.amount).toEqual(outputAmount.amount); + describe('Native Swaps', () => { + test('Native -> Token givenIn', async () => { + const inputAmount = TokenAmount.fromHumanAmount( + inputToken, + '100', + ); + + const swap = await sorGetSwapsWithPools( + inputToken, + BEETS, + SwapKind.GivenIn, + inputAmount, + pools, + swapOptions, + ); + + if (!swap) throw new Error('Swap is undefined'); + + const onchain = await swap.query(rpcUrl, swapOptions.block); + + expect(swap.quote.amount).toEqual(onchain.amount); + expect(swap.inputAmount.amount).toEqual(inputAmount.amount); + expect(swap.outputAmount.amount).toEqual(swap.quote.amount); + }); + + test('Native ETH -> Token givenOut', async () => { + const outputAmount = TokenAmount.fromHumanAmount( + BEETS, + '100000', + ); + + const swap = await sorGetSwapsWithPools( + inputToken, + BEETS, + SwapKind.GivenOut, + outputAmount, + pools, + swapOptions, + ); + + if (!swap) throw new Error('Swap is undefined'); + + const onchain = await swap.query(rpcUrl, swapOptions.block); + + expect(swap.quote.amount).toEqual(onchain.amount); + expect(swap.inputAmount.amount).toEqual(swap.quote.amount); + expect(swap.outputAmount.amount).toEqual( + outputAmount.amount, + ); + }); }); }); - }); -}); + }, + { + timeout: 120000, + }, +); From 1c644bebedc202bd0a05368b7f8eb746577d9093 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 12 Oct 2023 10:34:50 +0100 Subject: [PATCH 20/23] refactor: Default higher gyroE versions to use token rates, etc. --- src/data/onChainPoolDataViaReadContract.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/onChainPoolDataViaReadContract.ts b/src/data/onChainPoolDataViaReadContract.ts index 86550f29..6734369d 100644 --- a/src/data/onChainPoolDataViaReadContract.ts +++ b/src/data/onChainPoolDataViaReadContract.ts @@ -315,7 +315,9 @@ const poolTypeCalls = ( }; } case 'GyroE': - if (poolTypeVersion === 2) { + if (poolTypeVersion === 1) { + return defaultCalls; + } else { return { count: defaultCalls.count + gyroECalls.count, build: (id: string) => [ @@ -330,8 +332,6 @@ const poolTypeCalls = ( ), }), }; - } else { - return defaultCalls; } case 'AaveLinear': if (poolTypeVersion === 1) { From 84402ef319517387c784cf2976885d6c3b20455b Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 12 Oct 2023 11:41:03 +0100 Subject: [PATCH 21/23] refactor: Switch to Viems multicall as it does support blockNumber. Remove multicallV3 abi, etc. --- .changeset/few-mayflies-change.md | 5 +- debug/multicall.ts | 2 - src/abi/index.ts | 1 - src/abi/multicallV3.ts | 440 ------------------ src/data/enrichers/onChainPoolDataEnricher.ts | 2 - src/data/onChainPoolDataViaReadContract.ts | 75 +-- src/sor.ts | 10 +- src/utils/constants.ts | 24 +- test/sor.test.ts | 11 +- 9 files changed, 20 insertions(+), 550 deletions(-) delete mode 100644 src/abi/multicallV3.ts diff --git a/.changeset/few-mayflies-change.md b/.changeset/few-mayflies-change.md index 80d63008..cb3c86ad 100644 --- a/.changeset/few-mayflies-change.md +++ b/.changeset/few-mayflies-change.md @@ -2,10 +2,9 @@ "@balancer/sdk": minor --- -Replace dataQueries with multicallV3: +Replace dataQueries with multicall (using Viem): -- Add multicallV3 ABI -- Add manual batching (instead of Viems) so we can maintain custom blockNo +- Add `BATCHSIZE` config per network - Onchain calls correctly mapped to pool/version - Filter bricked pools from vulnerability - Fix scalingFactor scaling diff --git a/debug/multicall.ts b/debug/multicall.ts index d2538d14..1dd10c95 100644 --- a/debug/multicall.ts +++ b/debug/multicall.ts @@ -1,7 +1,6 @@ // bun run debug/multicall.ts import { OnChainPoolDataEnricher, - MULTICALL, BATCHSIZE, ProviderSwapOptions, VAULT, @@ -43,7 +42,6 @@ const pools = await fetch( const onChainEnricher = new OnChainPoolDataEnricher( chain.id, rpc, - MULTICALL[chain.id], BATCHSIZE[chain.id], VAULT[chain.id], ); diff --git a/src/abi/index.ts b/src/abi/index.ts index 21dcc51f..a13e6d1b 100644 --- a/src/abi/index.ts +++ b/src/abi/index.ts @@ -1,4 +1,3 @@ export * from './vault'; export * from './balancerQueries'; export * from './erc20'; -export * from './multicallV3'; diff --git a/src/abi/multicallV3.ts b/src/abi/multicallV3.ts deleted file mode 100644 index 0950f8f0..00000000 --- a/src/abi/multicallV3.ts +++ /dev/null @@ -1,440 +0,0 @@ -export const multicall = [ - { - 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', - }, -]; diff --git a/src/data/enrichers/onChainPoolDataEnricher.ts b/src/data/enrichers/onChainPoolDataEnricher.ts index e0868425..2522c07e 100644 --- a/src/data/enrichers/onChainPoolDataEnricher.ts +++ b/src/data/enrichers/onChainPoolDataEnricher.ts @@ -41,7 +41,6 @@ export class OnChainPoolDataEnricher implements PoolDataEnricher { constructor( private readonly chainId: number, private readonly rpcUrl: string, - private readonly multicallAddress: Address, private readonly batchSize: number, private readonly vault: Address, ) { @@ -57,7 +56,6 @@ export class OnChainPoolDataEnricher implements PoolDataEnricher { ): Promise { return fetchAdditionalPoolData( this.vault, - this.multicallAddress, data.pools, this.client, options, diff --git a/src/data/onChainPoolDataViaReadContract.ts b/src/data/onChainPoolDataViaReadContract.ts index 6734369d..04dafceb 100644 --- a/src/data/onChainPoolDataViaReadContract.ts +++ b/src/data/onChainPoolDataViaReadContract.ts @@ -1,13 +1,4 @@ -import { - PublicClient, - parseAbi, - multicall3Abi, - encodeFunctionData, - Address, - Abi, - Hex, - decodeFunctionResult, -} from 'viem'; +import { PublicClient, parseAbi, Address, Abi, Hex } from 'viem'; import { getPoolAddress } from '../utils'; import { OnChainPoolData } from './enrichers/onChainPoolDataEnricher'; import { SwapOptions } from '../types'; @@ -359,7 +350,6 @@ const poolTypeCalls = ( export const fetchAdditionalPoolData = async ( vault: Address, - multicallAddress: Address, pools: { id: string; poolType: string; @@ -381,69 +371,20 @@ export const fetchAdditionalPoolData = async ( ), ); - const batchedCalls = calls.map(({ address, functionName, args }) => { - let callData; - if (args !== undefined) - callData = encodeFunctionData({ abi, functionName, args }); - else callData = encodeFunctionData({ abi, functionName }); - return { - target: address, - allowFailure: true, - callData, - }; + const results = await client.multicall({ + contracts: calls, + batchSize: batchSize, + blockNumber: options.block, }); - const numBatches = Math.ceil(batchedCalls.length / batchSize); - const batchPromises: Promise< - readonly { - success: boolean; - returnData: `0x${string}`; - }[] - >[] = []; - - for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) { - const batchCalls = batchedCalls.slice( - batchIndex * batchSize, - (batchIndex + 1) * batchSize, - ); - - batchPromises.push( - client.readContract({ - address: multicallAddress, - abi: multicall3Abi, - functionName: 'aggregate3', - blockNumber: options.block, - args: [batchCalls], - }), - ); - } - - const encodedResults = (await Promise.all(batchPromises)).flatMap((r) => r); - - const results = encodedResults.map((result, i) => { - if (result.success) { - const decodedResult = decodeFunctionResult({ - abi, - functionName: calls[i].functionName, - data: result.returnData, - }); - return { - error: undefined, - result: decodedResult, - status: 'success', - } as Result; - } else { + results.forEach((r, i) => { + if (r.status === 'failure') console.error( 'Failed request in multicall', calls[i].address, calls[i].functionName, + r.error, ); - return { - error: new Error('failed'), - result: undefined, - status: 'failure', - } as Result; - } }); let shift = 0; diff --git a/src/sor.ts b/src/sor.ts index 1a505e50..5a13785b 100644 --- a/src/sor.ts +++ b/src/sor.ts @@ -1,13 +1,6 @@ import { Router } from './router'; import { BasePool, Path, Token, TokenAmount, Swap } from './entities'; -import { - ChainId, - checkInputs, - SUBGRAPH_URLS, - MULTICALL, - BATCHSIZE, - VAULT, -} from './utils'; +import { ChainId, checkInputs, SUBGRAPH_URLS, BATCHSIZE, VAULT } from './utils'; import { SorConfig, SwapInputRawAmount, SwapKind, SwapOptions } from './types'; import { PoolParser } from './entities/pools/parser'; import { OnChainPoolDataEnricher, SubgraphPoolProvider } from './data'; @@ -41,7 +34,6 @@ export class SmartOrderRouter { new OnChainPoolDataEnricher( chainId, rpcUrl, - MULTICALL[chainId], BATCHSIZE[chainId], VAULT[chainId], ); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index dad80a41..02bd4737 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -13,6 +13,7 @@ import { polygonZkEvm, zkSync, zkSyncTestnet, + fantom, } from 'viem/chains'; export const ZERO_ADDRESS: Address = @@ -76,6 +77,7 @@ export const CHAINS: Record = { [ChainId.ARBITRUM_ONE]: arbitrum, [ChainId.AVALANCHE]: avalanche, [ChainId.BASE_GOERLI]: baseGoerli, + [ChainId.FANTOM]: fantom, }; export const VAULT: Record = { @@ -136,23 +138,13 @@ export const BALANCER_QUERIES: Record = { [ChainId.FANTOM]: '0x1B0A42663DF1edeA171cD8732d288a81EFfF6d23', }; -export const MULTICALL: Record = { - [ChainId.ARBITRUM_ONE]: '0xca11bde05977b3631167028862be2a173976ca11', - [ChainId.AVALANCHE]: '0xca11bde05977b3631167028862be2a173976ca11', - [ChainId.GNOSIS_CHAIN]: '0xca11bde05977b3631167028862be2a173976ca11', - [ChainId.MAINNET]: '0xca11bde05977b3631167028862be2a173976ca11', - [ChainId.OPTIMISM]: '0xca11bde05977b3631167028862be2a173976ca11', - [ChainId.POLYGON]: '0xca11bde05977b3631167028862be2a173976ca11', - [ChainId.ZKEVM]: '0xca11bde05977b3631167028862be2a173976ca11', - [ChainId.FANTOM]: '0xca11bde05977b3631167028862be2a173976ca11', -}; export const BATCHSIZE: Record = { - [ChainId.ARBITRUM_ONE]: 1000, - [ChainId.AVALANCHE]: 1000, - [ChainId.GNOSIS_CHAIN]: 1000, - [ChainId.MAINNET]: 1000, - [ChainId.OPTIMISM]: 1000, - [ChainId.POLYGON]: 1000, + [ChainId.ARBITRUM_ONE]: 800, + [ChainId.AVALANCHE]: 800, + [ChainId.GNOSIS_CHAIN]: 800, + [ChainId.MAINNET]: 800, + [ChainId.OPTIMISM]: 800, + [ChainId.POLYGON]: 800, [ChainId.ZKEVM]: 128, [ChainId.FANTOM]: 128, }; diff --git a/test/sor.test.ts b/test/sor.test.ts index ba67b437..c3ca0667 100644 --- a/test/sor.test.ts +++ b/test/sor.test.ts @@ -6,14 +6,7 @@ dotenv.config(); import { SmartOrderRouter } from '../src/sor'; import { sorGetSwapsWithPools } from '../src/static'; import { SubgraphPoolProvider } from '../src/data/providers/subgraphPoolProvider'; -import { - ChainId, - ETH, - NATIVE_ASSETS, - MULTICALL, - BATCHSIZE, - VAULT, -} from '../src/utils'; +import { ChainId, ETH, NATIVE_ASSETS, BATCHSIZE, VAULT } from '../src/utils'; import { Token, TokenAmount } from '../src/entities'; import { OnChainPoolDataEnricher } from '../src/data/enrichers/onChainPoolDataEnricher'; import { SwapKind, SwapOptions } from '../src/types'; @@ -30,7 +23,6 @@ describe( const onChainPoolDataEnricher = new OnChainPoolDataEnricher( chainId, rpcUrl, - MULTICALL[chainId], BATCHSIZE[chainId], VAULT[chainId], ); @@ -207,7 +199,6 @@ describe( const onChainPoolDataEnricher = new OnChainPoolDataEnricher( chainId, rpcUrl, - MULTICALL[chainId], BATCHSIZE[chainId], VAULT[chainId], ); From 4fdfb72ef64a86a4c7125e354167e499c290ea00 Mon Sep 17 00:00:00 2001 From: johngrantuk Date: Thu, 12 Oct 2023 11:45:13 +0100 Subject: [PATCH 22/23] tests: Remove MULTICALL references. --- test/fxPool.integration.test.ts | 3 +-- test/gyroEV2Pool.integration.test.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/test/fxPool.integration.test.ts b/test/fxPool.integration.test.ts index f07bb924..a4ef6217 100644 --- a/test/fxPool.integration.test.ts +++ b/test/fxPool.integration.test.ts @@ -3,7 +3,7 @@ import { beforeAll, beforeEach, describe, expect, test } from 'vitest'; import dotenv from 'dotenv'; dotenv.config(); -import { BATCHSIZE, ChainId, MULTICALL, VAULT } from '../src/utils'; +import { BATCHSIZE, ChainId, VAULT } from '../src/utils'; import { BasePool, OnChainPoolDataEnricher, @@ -49,7 +49,6 @@ describe('fx integration tests', () => { const onChainPoolDataEnricher = new OnChainPoolDataEnricher( chainId, rpcUrl, - MULTICALL[chainId], BATCHSIZE[chainId], VAULT[chainId], ); diff --git a/test/gyroEV2Pool.integration.test.ts b/test/gyroEV2Pool.integration.test.ts index 654110fe..fa07611e 100644 --- a/test/gyroEV2Pool.integration.test.ts +++ b/test/gyroEV2Pool.integration.test.ts @@ -4,7 +4,7 @@ import dotenv from 'dotenv'; dotenv.config(); import testPools from './lib/testData/gyroETestPool.json'; -import { BATCHSIZE, ChainId, MULTICALL, VAULT } from '../src/utils'; +import { BATCHSIZE, ChainId, VAULT } from '../src/utils'; import { BasePool, OnChainPoolDataEnricher, @@ -45,7 +45,6 @@ describe('gyroEV2: WMATIC-stMATIC integration tests', () => { const onChainPoolDataEnricher = new OnChainPoolDataEnricher( chainId, rpcUrl, - MULTICALL[chainId], BATCHSIZE[chainId], VAULT[chainId], ); From 08c0b853a743da3a838cf56e561764ab776de941 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 12 Oct 2023 12:33:07 +0000 Subject: [PATCH 23/23] Version Packages --- .changeset/eleven-trains-kneel.md | 5 ----- .changeset/few-mayflies-change.md | 10 ---------- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 4 files changed, 13 insertions(+), 16 deletions(-) delete mode 100644 .changeset/eleven-trains-kneel.md delete mode 100644 .changeset/few-mayflies-change.md diff --git a/.changeset/eleven-trains-kneel.md b/.changeset/eleven-trains-kneel.md deleted file mode 100644 index 753d4307..00000000 --- a/.changeset/eleven-trains-kneel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@balancer/sdk": minor ---- - -Add Fantom config. Update to have network specific vault addr. diff --git a/.changeset/few-mayflies-change.md b/.changeset/few-mayflies-change.md deleted file mode 100644 index cb3c86ad..00000000 --- a/.changeset/few-mayflies-change.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@balancer/sdk": minor ---- - -Replace dataQueries with multicall (using Viem): - -- Add `BATCHSIZE` config per network -- Onchain calls correctly mapped to pool/version -- Filter bricked pools from vulnerability -- Fix scalingFactor scaling diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f9305b3..8b598ba2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # @balancer/sdk +## 0.2.0 + +### Minor Changes + +- f6d1051: Add Fantom config. Update to have network specific vault addr. +- 67e4120: Replace dataQueries with multicall (using Viem): + + - Add `BATCHSIZE` config per network + - Onchain calls correctly mapped to pool/version + - Filter bricked pools from vulnerability + - Fix scalingFactor scaling + ## 0.1.2 ### Patch Changes diff --git a/package.json b/package.json index b541a04b..066a21f3 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "publishConfig": { "access": "public" }, - "version": "0.1.2", + "version": "0.2.0", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts",