From 4f85ac6b2eb2c540940f041a9ba8062335a2d2a2 Mon Sep 17 00:00:00 2001 From: Spacebean Date: Mon, 21 Oct 2024 13:30:58 -0400 Subject: [PATCH] feat: remove multicalls from silo/updater --- projects/sdk/src/lib/BeanstalkSDK.ts | 10 - projects/ui/src/constants/rpc.ts | 25 +- .../ui/src/state/beanstalk/silo/updater.ts | 256 ++++++++++-------- projects/ui/src/util/wagmi/ethersAdapter.ts | 2 +- 4 files changed, 138 insertions(+), 155 deletions(-) diff --git a/projects/sdk/src/lib/BeanstalkSDK.ts b/projects/sdk/src/lib/BeanstalkSDK.ts index 9a65f593d..1e4557732 100644 --- a/projects/sdk/src/lib/BeanstalkSDK.ts +++ b/projects/sdk/src/lib/BeanstalkSDK.ts @@ -151,13 +151,6 @@ export class BeanstalkSDK { this.DEBUG = config.DEBUG ?? false; this.source = DataSource.LEDGER; // FIXME - - console.log("[sdk/handleConfig]", { - config, - provider: this.provider, - signer: this.signer, - providerOrSigner: this.providerOrSigner, - }); } deriveSource(config?: T): DataSource { @@ -177,8 +170,6 @@ export class BeanstalkSDK { const provider = config.signer ? (config.signer.provider as Provider) : config.provider; const networkish = provider?._network || provider?.network || ChainResolver.defaultChainId; - console.log("[sdk/getProviderFromUrl]", url, networkish); - if (url.startsWith("ws")) { return new ethers.providers.WebSocketProvider(url, networkish); } @@ -197,7 +188,6 @@ export class BeanstalkSDK { } private deriveChainId() { - console.log("[sdk/deriveChainId]", this.provider); const { _network, network } = this.provider || {}; const providerChainId = _network?.chainId || network?.chainId || ChainResolver.defaultChainId; diff --git a/projects/ui/src/constants/rpc.ts b/projects/ui/src/constants/rpc.ts index 7a16a7b2d..d30264db9 100644 --- a/projects/ui/src/constants/rpc.ts +++ b/projects/ui/src/constants/rpc.ts @@ -10,27 +10,4 @@ export const TESTNET_RPC_ADDRESSES: { [chainId: number]: string } = { 'https://rpc.vnet.tenderly.co/devnet/silo-v3/3ed19e82-a81c-45e5-9b16-5e385aa74587', [SupportedChainId.ANVIL1]: 'https://anvil1.bean.money:443', [SupportedChainId.LOCALHOST_ETH]: 'http://localhost:9545', -}; - -// BS3TODO: update me when these are ready -export const BEANSTALK_SUBGRAPH_ADDRESSES: { [chainId: number]: string } = { - [SupportedChainId.ETH_MAINNET]: - 'https://graph.node.bean.money/subgraphs/name/beanstalk', - // [SupportedChainId.MAINNET]: 'https://api.thegraph.com/subgraphs/name/cujowolf/beanstalk', - [SupportedChainId.LOCALHOST]: - 'https://api.thegraph.com/subgraphs/name/cujowolf/beanstalk-dev-replanted', - [SupportedChainId.TESTNET]: - 'http://graph.playgrounds.academy/subgraphs/name/beanstalk', -}; - -// BS3TODO: update me when these are ready -/// The BEAN subgraph is slow to index because it tracks many events. -/// To speed up development time, Bean metrics are provided from a separate subgraph. -export const BEAN_SUBGRAPH_ADDRESSES: { [chainId: number]: string } = { - [SupportedChainId.ETH_MAINNET]: - 'https://api.thegraph.com/subgraphs/name/cujowolf/bean', - [SupportedChainId.LOCALHOST]: - 'https://api.thegraph.com/subgraphs/name/cujowolf/bean', - [SupportedChainId.TESTNET]: - 'https://api.thegraph.com/subgraphs/name/cujowolf/bean', -}; +}; \ No newline at end of file diff --git a/projects/ui/src/state/beanstalk/silo/updater.ts b/projects/ui/src/state/beanstalk/silo/updater.ts index a331c8560..6fe5c2084 100644 --- a/projects/ui/src/state/beanstalk/silo/updater.ts +++ b/projects/ui/src/state/beanstalk/silo/updater.ts @@ -1,14 +1,15 @@ import { useCallback } from 'react'; import { useDispatch } from 'react-redux'; -import { tokenIshEqual, transform } from '~/util'; +import { tokenIshEqual, transform, getTokenIndex } from '~/util'; import useSdk from '~/hooks/sdk'; -import { Token } from '@beanstalk/sdk'; -import { ContractFunctionParameters } from 'viem'; -import { multicall } from '@wagmi/core'; -import { config } from '~/util/wagmi/config'; +import { + BeanstalkSDK, + Token, + Clipboard, + AdvancedPipeStruct, +} from '@beanstalk/sdk'; import BNJS from 'bignumber.js'; import { - ABISnippets, BEAN_TO_SEEDS, BEAN_TO_STALK, ONE_BN, @@ -21,12 +22,6 @@ import useL2OnlyEffect from '~/hooks/chain/useL2OnlyEffect'; import { resetBeanstalkSilo, updateBeanstalkSilo } from './actions'; import { BeanstalkSiloBalance } from '.'; -// limit to maximum of 20 calls per multicall. (5 * 4 = 20) -const MAX_BUCKETS = 5; - -// 4 queries per token -const QUERIES_PER_TK = 4; - export const useFetchBeanstalkSilo = () => { const dispatch = useDispatch(); const sdk = useSdk(); @@ -39,33 +34,24 @@ export const useFetchBeanstalkSilo = () => { if (!beanstalk) return; const BEAN = sdk.tokens.BEAN; - const STALK = sdk.tokens.STALK; const wl = await sdk.contracts.beanstalk.getWhitelistedTokens(); const whitelist = wl .map((t) => sdk.tokens.findByAddress(t)) .filter(Boolean) as Token[]; - const wlContractCalls = buildWhitelistMultiCall(beanstalk, whitelist); - const siloCalls = buildBeanstalkSiloMultiCall(beanstalk.address); - - const [stemTips, siloResults, wlResults] = await Promise.all([ + const [stemTips, siloResults, wlResults, bdvs] = await Promise.all([ sdk.silo.getStemTips(), - multicall(config, { contracts: siloCalls }), - Promise.all( - wlContractCalls.map((calls) => - multicall(config, { contracts: calls }) - ) - ), + getSiloResults(sdk), + getWhitelistResults(sdk, whitelist), + getBDVs(sdk, whitelist), ]); - const parsedSiloData = siloResults.map((r) => parseCallResult(r)); - const stalkTotal = transform(parsedSiloData[0], 'bnjs', STALK); - const rootsTotal = transform(parsedSiloData[1], 'bnjs'); - const earnedBeansTotal = transform(parsedSiloData[2], 'bnjs', BEAN); + const stalkTotal = siloResults.totalStalk; + const rootsTotal = siloResults.totalRoots; + const earnedBeansTotal = siloResults.totalEarnedBeans; const bdvTotal = ZERO_BN; - const chunked = chunkArray(wlResults.flat(), QUERIES_PER_TK); const whitelistedAssetTotals: ({ address: string; deposited: BNJS; @@ -75,9 +61,8 @@ export const useFetchBeanstalkSilo = () => { stemTip: ethers.BigNumber; } | null)[] = []; - chunked.forEach((chunk, i) => { + Object.values(wlResults).forEach((datas, i) => { const token = whitelist[i]; - const data = chunk.map((d) => parseCallResult(d)); const stemTip = stemTips.get(token.address); if (!stemTip) { @@ -86,12 +71,10 @@ export const useFetchBeanstalkSilo = () => { whitelistedAssetTotals.push({ address: token.address, - deposited: transform(data[0], 'bnjs', token), - depositedBdv: tokenIshEqual(token, sdk.tokens.BEAN) - ? ONE_BN - : transform(data[1], 'bnjs', token), - totalGerminating: transform(data[2], 'bnjs', token), - bdvPerToken: transform(data[3], 'bnjs', BEAN), + deposited: datas.deposited, + depositedBdv: datas.depositedBdv, + totalGerminating: datas.germinating, + bdvPerToken: bdvs[i], stemTip: stemTips.get(token.address) || ethers.BigNumber.from(0), }); }); @@ -107,7 +90,7 @@ export const useFetchBeanstalkSilo = () => { // because 1 bean = 1 stalk, 2 seeds const activeStalkTotal = stalkTotal; const earnedStalkTotal = earnedBeansTotal.times(BEAN_TO_STALK); - const earnedSeedTotal = earnedBeansTotal.times(BEAN_TO_SEEDS); + const earnedSeedTotal = earnedBeansTotal.times(BEAN_TO_SEEDS); // FIX ME /// Aggregate balances const balances = whitelistedAssetTotals.reduce((agg, curr) => { @@ -195,96 +178,129 @@ const BeanstalkSiloUpdater = () => { export default BeanstalkSiloUpdater; -// -- Helper Types +async function getSiloResults(sdk: BeanstalkSDK) { + const beanstalk = sdk.contracts.beanstalk; + const iBeanstalk = beanstalk.interface; -type CallParams = ContractFunctionParameters; - -type CallResult = Awaited< - ReturnType> ->[number]; - -// -- Helpers + const common = { + target: beanstalk.address, + clipboard: Clipboard.encode([]), + }; -function parseCallResult( - result: CallResult, - defaultValue: bigint = -1n -): bigint { - if (result.error) return defaultValue; - return result.result; + const fnNames = ['totalStalk', 'totalRoots', 'totalEarnedBeans'] as const; + + const calls: AdvancedPipeStruct[] = fnNames.map((fnName) => ({ + ...common, + callData: iBeanstalk.encodeFunctionData(fnName as any), + })); + + const result = await beanstalk.callStatic.advancedPipe(calls, '0'); + + const _totalStalk = iBeanstalk.decodeFunctionResult( + 'totalStalk', + result[0] + )[0]; + const _totalRoots = iBeanstalk.decodeFunctionResult( + 'totalRoots', + result[1] + )[0]; + const _totalEarnedBeans = iBeanstalk.decodeFunctionResult( + 'totalEarnedBeans', + result[2] + )[0]; + + return { + calls, + totalStalk: transform(_totalStalk, 'bnjs', sdk.tokens.STALK), + totalRoots: transform(_totalRoots, 'bnjs'), + totalEarnedBeans: transform(_totalEarnedBeans, 'bnjs', sdk.tokens.BEAN), + }; } -function buildWhitelistMultiCall( - beanstalk: ReturnType['contracts']['beanstalk'], - // ensure silo whitelist is order is consistent w/ the multiCall results - whitelist: Token[] -): CallParams[][] { - const beanstalkAddress = beanstalk.address as `0x{string}`; - const contractCalls: CallParams[][] = []; - - const shared = { - address: beanstalkAddress, - abi: ABISnippets.siloGetters, - }; +interface WhitelistPipeResult { + deposited: BNJS; + depositedBdv: BNJS; + germinating: BNJS; +} - let callBucket: CallParams[] = []; - whitelist.forEach((token, i) => { - const tokenAddress = token.address as `0x{string}`; - const calls: CallParams[] = [ - { - ...shared, - functionName: 'getTotalDeposited', - args: [tokenAddress], - }, - { - ...shared, - functionName: 'getTotalDepositedBdv', - args: [tokenAddress], - }, - { - ...shared, - functionName: 'getGerminatingTotalDeposited', - args: [tokenAddress], - }, - { - ...shared, - functionName: 'bdv', - args: [tokenAddress, BigInt(token.fromHuman(1).blockchainString)], - }, - ]; - callBucket.push(...calls); - - if (i % MAX_BUCKETS === MAX_BUCKETS - 1) { - contractCalls.push(callBucket); - callBucket = []; - } - }); +async function getWhitelistResults(sdk: BeanstalkSDK, whitelist: Token[]) { + const beanstalk = sdk.contracts.beanstalk; + const iBeanstalk = beanstalk.interface; - if (callBucket.length) contractCalls.push(callBucket); + const common = { + target: beanstalk.address, + clipboard: Clipboard.encode([]), + }; - return contractCalls; + const allCalls: AdvancedPipeStruct[] = whitelist + .map((token) => { + const tokenAddress = token.address; + const calls = [ + iBeanstalk.encodeFunctionData('getTotalDeposited', [tokenAddress]), + iBeanstalk.encodeFunctionData('getTotalDepositedBdv', [tokenAddress]), + iBeanstalk.encodeFunctionData('getGerminatingTotalDeposited', [ + tokenAddress, + ]), + ]; + + return calls.map((c) => ({ + ...common, + callData: c, + })); + }) + .flat(); + + const results = await beanstalk.callStatic.advancedPipe(allCalls, '0'); + + const chunkedByToken = chunkArray(results, 3); + + return whitelist.reduce>((prev, token, i) => { + const tokenChunk = chunkedByToken[i]; + + const deposited = iBeanstalk.decodeFunctionResult( + 'getTotalDeposited', + tokenChunk[0] + )[0]; + const depositedBdv = iBeanstalk.decodeFunctionResult( + 'getTotalDepositedBdv', + tokenChunk[1] + )[0]; + const germinating = iBeanstalk.decodeFunctionResult( + 'getGerminatingTotalDeposited', + tokenChunk[2] + )[0]; + + prev[getTokenIndex(token)] = { + deposited: transform(deposited, 'bnjs', token), + depositedBdv: transform(depositedBdv, 'bnjs', sdk.tokens.BEAN), + germinating: transform(germinating, 'bnjs', token), + }; + + return prev; + }, {}); } -function buildBeanstalkSiloMultiCall(beanstalkAddress: string): CallParams[] { - const shared = { - address: beanstalkAddress as `0x{string}`, - abi: ABISnippets.siloGetters, - }; - - return [ - { - ...shared, - functionName: 'totalStalk', - args: [], - }, - { - ...shared, - functionName: 'totalRoots', - args: [], - }, - { - ...shared, - functionName: 'totalEarnedBeans', - args: [], - }, - ]; +async function getBDVs(sdk: BeanstalkSDK, whitelist: Token[]) { + const beanstalk = sdk.contracts.beanstalk; + + return Promise.all( + whitelist.map((token) => + beanstalk + .bdv(token.address, token.fromHuman(1).blockchainString) + .then((r) => { + if (tokenIshEqual(token, sdk.tokens.BEAN)) { + return ONE_BN; + } + return transform(r, 'bnjs', sdk.tokens.BEAN) as BNJS; + }) + .catch((e) => { + console.debug( + '[beanstalk/silo/updater] bdv failed for token', + token.address, + e + ); + return ZERO_BN; + }) + ) + ); } diff --git a/projects/ui/src/util/wagmi/ethersAdapter.ts b/projects/ui/src/util/wagmi/ethersAdapter.ts index 7cb65afad..7e4b74509 100644 --- a/projects/ui/src/util/wagmi/ethersAdapter.ts +++ b/projects/ui/src/util/wagmi/ethersAdapter.ts @@ -8,7 +8,7 @@ const SHOW_DEV = import.meta.env.VITE_SHOW_DEV_CHAINS; const fallbackChain = { chainId: SHOW_DEV ? ChainId.LOCALHOST : ChainId.ARBITRUM_MAINNET, - name: SHOW_DEV ? 'locahost:8545' : 'arbitrum', + name: SHOW_DEV ? 'locahost:8545' : 'Arbitrum One', } as const; export function clientToProvider(client: Client) {