From 5ce6e42a149b9f6fbd25982061996a30d796dd72 Mon Sep 17 00:00:00 2001 From: Alberto Gualis Date: Mon, 16 Sep 2024 14:23:04 +0200 Subject: [PATCH 1/3] feat: get tenderly url from error message --- app/react-query.provider.tsx | 22 +++++-- .../useAddLiquidityBuildCallDataQuery.ts | 10 ++- .../useAddLiquidityPriceImpactQuery.ts | 10 ++- .../queries/useAddLiquiditySimulationQuery.ts | 10 ++- .../RemoveLiquidityProvider.tsx | 4 +- .../useRemoveLiquidityBuildCallDataQuery.ts | 10 +-- ...idityPriceImpactQuery.integration.spec.tsx | 1 + .../useRemoveLiquidityPriceImpactQuery.ts | 13 ++-- ...uiditySimulationQuery.integration.spec.tsx | 1 + .../useRemoveLiquiditySimulationQuery.ts | 7 ++- lib/modules/swap/queries/useBuildSwapQuery.ts | 10 ++- .../swap/queries/useSimulateSwapQuery.ts | 7 +++ lib/modules/web3/useTenderly.ts | 38 ++++++----- lib/shared/utils/errors.spec.ts | 49 +++++++++++++++ lib/shared/utils/errors.ts | 63 +++++++++++++++++++ lib/shared/utils/query-errors.spec.ts | 9 ++- lib/shared/utils/query-errors.ts | 26 +++++--- 17 files changed, 240 insertions(+), 50 deletions(-) create mode 100644 lib/shared/utils/errors.spec.ts diff --git a/app/react-query.provider.tsx b/app/react-query.provider.tsx index be6aefde2..c2ec3883c 100644 --- a/app/react-query.provider.tsx +++ b/app/react-query.provider.tsx @@ -1,8 +1,14 @@ 'use client' import { isDev } from '@/lib/config/app.config' -import { captureError } from '@/lib/shared/utils/errors' -import { SentryMetadata, captureSentryError, shouldIgnore } from '@/lib/shared/utils/query-errors' +import { captureError, getTenderlyUrlFromErrorMessage } from '@/lib/shared/utils/errors' +import { + SentryMetadata, + captureSentryError, + getTenderlyUrl, + shouldIgnore, +} from '@/lib/shared/utils/query-errors' +import { ScopeContext } from '@sentry/types' import { MutationCache, QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { ReactNode } from 'react' @@ -11,14 +17,20 @@ export const queryClient = new QueryClient({ queryCache: new QueryCache({ // Global handler for every react-query error onError: (error, query) => { + const queryMeta = query?.meta + const sentryContext = query?.meta?.context as ScopeContext if (shouldIgnore(error.message, error.stack)) return - console.log('Sentry capturing query error: ', { - meta: query?.meta, + console.log('Sentry capturing query error', { + meta: queryMeta, error, queryKey: query.queryKey, }) - if (query?.meta) return captureSentryError(error, query?.meta as SentryMetadata) + if (sentryContext?.extra && !getTenderlyUrl(sentryContext.extra)) { + sentryContext.extra.tenderlyUrl = getTenderlyUrlFromErrorMessage(error, queryMeta) + } + + if (queryMeta) return captureSentryError(error, queryMeta as SentryMetadata) // Unexpected error in query (as expected errors should have query.meta) captureError(error, { extra: { queryKey: query.queryKey } }) diff --git a/lib/modules/pool/actions/add-liquidity/queries/useAddLiquidityBuildCallDataQuery.ts b/lib/modules/pool/actions/add-liquidity/queries/useAddLiquidityBuildCallDataQuery.ts index a572b730f..29aabfb31 100644 --- a/lib/modules/pool/actions/add-liquidity/queries/useAddLiquidityBuildCallDataQuery.ts +++ b/lib/modules/pool/actions/add-liquidity/queries/useAddLiquidityBuildCallDataQuery.ts @@ -11,6 +11,7 @@ import { AddLiquidityHandler } from '../handlers/AddLiquidity.handler' import { AddLiquiditySimulationQueryResult } from './useAddLiquiditySimulationQuery' import { useDebounce } from 'use-debounce' import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types' +import { useBlockNumber } from 'wagmi' export type AddLiquidityBuildQueryResponse = ReturnType @@ -31,7 +32,8 @@ export function useAddLiquidityBuildCallDataQuery({ }) { const { userAddress, isConnected } = useUserAccount() const { slippage } = useUserSettings() - const { pool } = usePool() + const { pool, chainId } = usePool() + const { data: blockNumber } = useBlockNumber({ chainId }) const { relayerApprovalSignature } = useRelayerSignature() const debouncedHumanAmountsIn = useDebounce(humanAmountsIn, defaultDebounceMs)[0] @@ -64,7 +66,11 @@ export function useAddLiquidityBuildCallDataQuery({ queryFn, enabled: enabled && isConnected && !!simulationQuery.data, gcTime: 0, - meta: sentryMetaForAddLiquidityHandler('Error in add liquidity buildCallData query', params), + meta: sentryMetaForAddLiquidityHandler('Error in add liquidity buildCallData query', { + ...params, + chainId, + blockNumber, + }), ...onlyExplicitRefetch, }) } diff --git a/lib/modules/pool/actions/add-liquidity/queries/useAddLiquidityPriceImpactQuery.ts b/lib/modules/pool/actions/add-liquidity/queries/useAddLiquidityPriceImpactQuery.ts index 7b21624bb..62a7ac11c 100644 --- a/lib/modules/pool/actions/add-liquidity/queries/useAddLiquidityPriceImpactQuery.ts +++ b/lib/modules/pool/actions/add-liquidity/queries/useAddLiquidityPriceImpactQuery.ts @@ -11,6 +11,7 @@ import { useQuery } from '@tanstack/react-query' import { usePool } from '../../../PoolProvider' import { sentryMetaForAddLiquidityHandler } from '@/lib/shared/utils/query-errors' import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types' +import { useBlockNumber } from 'wagmi' type Params = { handler: AddLiquidityHandler @@ -19,10 +20,11 @@ type Params = { } export function useAddLiquidityPriceImpactQuery({ handler, humanAmountsIn, enabled }: Params) { - const { pool } = usePool() + const { pool, chainId } = usePool() const { userAddress, isConnected } = useUserAccount() const { slippage } = useUserSettings() const debouncedHumanAmountsIn = useDebounce(humanAmountsIn, defaultDebounceMs)[0] + const { data: blockNumber } = useBlockNumber({ chainId }) const params: AddLiquidityParams = { handler, @@ -42,7 +44,11 @@ export function useAddLiquidityPriceImpactQuery({ handler, humanAmountsIn, enabl queryFn, enabled: enabled && isConnected && !areEmptyAmounts(debouncedHumanAmountsIn), gcTime: 0, - meta: sentryMetaForAddLiquidityHandler('Error in add liquidity priceImpact query', params), + meta: sentryMetaForAddLiquidityHandler('Error in add liquidity priceImpact query', { + ...params, + chainId, + blockNumber, + }), ...onlyExplicitRefetch, }) } diff --git a/lib/modules/pool/actions/add-liquidity/queries/useAddLiquiditySimulationQuery.ts b/lib/modules/pool/actions/add-liquidity/queries/useAddLiquiditySimulationQuery.ts index b11d8465d..3378af814 100644 --- a/lib/modules/pool/actions/add-liquidity/queries/useAddLiquiditySimulationQuery.ts +++ b/lib/modules/pool/actions/add-liquidity/queries/useAddLiquiditySimulationQuery.ts @@ -11,6 +11,7 @@ import { useQuery } from '@tanstack/react-query' import { usePool } from '../../../PoolProvider' import { sentryMetaForAddLiquidityHandler } from '@/lib/shared/utils/query-errors' import { HumanTokenAmountWithAddress } from '@/lib/modules/tokens/token.types' +import { useBlockNumber } from 'wagmi' export type AddLiquiditySimulationQueryResult = ReturnType @@ -22,7 +23,8 @@ type Params = { export function useAddLiquiditySimulationQuery({ handler, humanAmountsIn, enabled }: Params) { const { userAddress } = useUserAccount() - const { pool } = usePool() + const { pool, chainId } = usePool() + const { data: blockNumber } = useBlockNumber({ chainId }) const { slippage } = useUserSettings() const debouncedHumanAmountsIn = useDebounce(humanAmountsIn, defaultDebounceMs)[0] @@ -44,7 +46,11 @@ export function useAddLiquiditySimulationQuery({ handler, humanAmountsIn, enable queryFn, enabled: enabled && !areEmptyAmounts(debouncedHumanAmountsIn), gcTime: 0, - meta: sentryMetaForAddLiquidityHandler('Error in add liquidity simulation query', params), + meta: sentryMetaForAddLiquidityHandler('Error in add liquidity simulation query', { + ...params, + chainId, + blockNumber, + }), ...onlyExplicitRefetch, }) } diff --git a/lib/modules/pool/actions/remove-liquidity/RemoveLiquidityProvider.tsx b/lib/modules/pool/actions/remove-liquidity/RemoveLiquidityProvider.tsx index 166419543..16b3fc61d 100644 --- a/lib/modules/pool/actions/remove-liquidity/RemoveLiquidityProvider.tsx +++ b/lib/modules/pool/actions/remove-liquidity/RemoveLiquidityProvider.tsx @@ -43,7 +43,7 @@ export function _useRemoveLiquidity(urlTxHash?: Hash) { const [quoteAmountsOut, setQuoteAmountsOut] = useState([]) const [quotePriceImpact, setQuotePriceImpact] = useState() - const { pool, bptPrice, isLoading } = usePool() + const { pool, chainId, bptPrice, isLoading } = usePool() const { getToken, usdValueForToken, getNativeAssetToken, getWrappedNativeAssetToken } = useTokens() const { isConnected } = useUserAccount() @@ -118,6 +118,7 @@ export function _useRemoveLiquidity(urlTxHash?: Hash) { const simulationQuery = useRemoveLiquiditySimulationQuery({ handler, poolId: pool.id, + chainId, humanBptIn, tokenOut: wethIsEth && wNativeAsset ? (wNativeAsset.address as Address) : singleTokenOutAddress, enabled: !urlTxHash, @@ -126,6 +127,7 @@ export function _useRemoveLiquidity(urlTxHash?: Hash) { const priceImpactQuery = useRemoveLiquidityPriceImpactQuery({ handler, poolId: pool.id, + chainId, humanBptIn, tokenOut: wethIsEth && wNativeAsset ? (wNativeAsset.address as Address) : singleTokenOutAddress, enabled: !urlTxHash, diff --git a/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityBuildCallDataQuery.ts b/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityBuildCallDataQuery.ts index 5682e3a34..c29367f22 100644 --- a/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityBuildCallDataQuery.ts +++ b/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityBuildCallDataQuery.ts @@ -40,7 +40,7 @@ export function useRemoveLiquidityBuildCallDataQuery({ }) { const { userAddress, isConnected } = useUserAccount() const { slippage } = useUserSettings() - const { pool } = usePool() + const { pool, chainId } = usePool() const { relayerApprovalSignature } = useRelayerSignature() const debouncedHumanBptIn = useDebounce(humanBptIn, defaultDebounceMs)[0] @@ -75,10 +75,10 @@ export function useRemoveLiquidityBuildCallDataQuery({ queryFn, enabled: enabled && isConnected && !!simulationQuery.data, gcTime: 0, - meta: sentryMetaForRemoveLiquidityHandler( - 'Error in remove liquidity buildCallData query', - params - ), + meta: sentryMetaForRemoveLiquidityHandler('Error in remove liquidity buildCallData query', { + ...params, + chainId, + }), ...onlyExplicitRefetch, }) } diff --git a/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityPriceImpactQuery.integration.spec.tsx b/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityPriceImpactQuery.integration.spec.tsx index 18294c034..1e448fb14 100644 --- a/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityPriceImpactQuery.integration.spec.tsx +++ b/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityPriceImpactQuery.integration.spec.tsx @@ -19,6 +19,7 @@ async function testQuery(humanBptIn: HumanAmount) { ) const { result } = testHook(() => useRemoveLiquidityPriceImpactQuery({ + chainId: 1, handler, poolId: poolMock.id, humanBptIn, diff --git a/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityPriceImpactQuery.ts b/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityPriceImpactQuery.ts index e3b201551..0b7b7a0fa 100644 --- a/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityPriceImpactQuery.ts +++ b/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquidityPriceImpactQuery.ts @@ -10,10 +10,12 @@ import { RemoveLiquidityParams, removeLiquidityKeys } from './remove-liquidity-k import { HumanAmount } from '@balancer/sdk' import { useQuery } from '@tanstack/react-query' import { sentryMetaForRemoveLiquidityHandler } from '@/lib/shared/utils/query-errors' +import { useBlockNumber } from 'wagmi' type Params = { handler: RemoveLiquidityHandler poolId: string + chainId: number humanBptIn: HumanAmount tokenOut: Address enabled?: boolean @@ -22,12 +24,14 @@ type Params = { export function useRemoveLiquidityPriceImpactQuery({ handler, poolId, + chainId, humanBptIn, tokenOut, enabled = true, }: Params) { const { userAddress, isConnected } = useUserAccount() const { slippage } = useUserSettings() + const { data: blockNumber } = useBlockNumber({ chainId }) const debouncedBptIn = useDebounce(humanBptIn, defaultDebounceMs)[0] const params: RemoveLiquidityParams = { @@ -52,10 +56,11 @@ export function useRemoveLiquidityPriceImpactQuery({ queryFn, enabled: enabled && isConnected && Number(debouncedBptIn) > 0, gcTime: 0, - meta: sentryMetaForRemoveLiquidityHandler( - 'Error in remove liquidity price impact query', - params - ), + meta: sentryMetaForRemoveLiquidityHandler('Error in remove liquidity price impact query', { + ...params, + chainId, + blockNumber, + }), ...onlyExplicitRefetch, }) } diff --git a/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquiditySimulationQuery.integration.spec.tsx b/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquiditySimulationQuery.integration.spec.tsx index 34456941d..cb52c9cfc 100644 --- a/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquiditySimulationQuery.integration.spec.tsx +++ b/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquiditySimulationQuery.integration.spec.tsx @@ -19,6 +19,7 @@ async function testQuery(humanBptIn: HumanAmount) { const emptyTokenOut = '' as Address // We don't use it but it is required to simplify TS checks const { result } = testHook(() => useRemoveLiquiditySimulationQuery({ + chainId: 1, handler, poolId, humanBptIn, diff --git a/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquiditySimulationQuery.ts b/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquiditySimulationQuery.ts index 0eca534d2..ad382b96d 100644 --- a/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquiditySimulationQuery.ts +++ b/lib/modules/pool/actions/remove-liquidity/queries/useRemoveLiquiditySimulationQuery.ts @@ -18,6 +18,7 @@ export type RemoveLiquiditySimulationQueryResult = ReturnType< type Params = { handler: RemoveLiquidityHandler poolId: string + chainId: number humanBptIn: HumanAmount tokenOut: Address enabled?: boolean @@ -26,6 +27,7 @@ type Params = { export function useRemoveLiquiditySimulationQuery({ handler, poolId, + chainId, humanBptIn, tokenOut, enabled = true, @@ -55,7 +57,10 @@ export function useRemoveLiquiditySimulationQuery({ queryFn, enabled: enabled && isConnected && Number(debouncedHumanBptIn) > 0, gcTime: 0, - meta: sentryMetaForRemoveLiquidityHandler('Error in remove liquidity simulation query', params), + meta: sentryMetaForRemoveLiquidityHandler('Error in remove liquidity simulation query', { + ...params, + chainId, + }), ...onlyExplicitRefetch, }) return result diff --git a/lib/modules/swap/queries/useBuildSwapQuery.ts b/lib/modules/swap/queries/useBuildSwapQuery.ts index 5c1a9d027..4d4b1c51d 100644 --- a/lib/modules/swap/queries/useBuildSwapQuery.ts +++ b/lib/modules/swap/queries/useBuildSwapQuery.ts @@ -8,7 +8,9 @@ import { SimulateSwapResponse, SwapState } from '../swap.types' import { swapQueryKeys } from './swapQueryKeys' import { SwapSimulationQueryResult } from './useSimulateSwapQuery' import { useRelayerSignature } from '../../relayer/RelayerSignatureProvider' -import { SwapBuildCallExtras, sentryMetaForSwapHandler } from '@/lib/shared/utils/query-errors' +import { SwapMetaParams, sentryMetaForSwapHandler } from '@/lib/shared/utils/query-errors' +import { getChainId } from '@/lib/config/app.config' +import { useBlockNumber } from 'wagmi' export type BuildSwapQueryResponse = ReturnType @@ -34,6 +36,8 @@ export function useBuildSwapQuery({ const { relayerApprovalSignature } = useRelayerSignature() const { selectedChain, tokenIn, tokenOut, swapType } = swapState + const chainId = getChainId(selectedChain) + const { data: blockNumber } = useBlockNumber({ chainId }) const queryKey = swapQueryKeys.build({ selectedChain, @@ -67,11 +71,13 @@ export function useBuildSwapQuery({ enabled: enabled && isConnected && !!simulationQuery.data, gcTime: 0, meta: sentryMetaForSwapHandler('Error in swap buildCallData query', { + chainId, + blockNumber, handler, swapState, slippage, wethIsEth, - } as SwapBuildCallExtras), + } as SwapMetaParams), ...onlyExplicitRefetch, }) } diff --git a/lib/modules/swap/queries/useSimulateSwapQuery.ts b/lib/modules/swap/queries/useSimulateSwapQuery.ts index 1f2368561..c3ebc2b8f 100644 --- a/lib/modules/swap/queries/useSimulateSwapQuery.ts +++ b/lib/modules/swap/queries/useSimulateSwapQuery.ts @@ -8,6 +8,8 @@ import { swapQueryKeys } from './swapQueryKeys' import { SimulateSwapInputs, SimulateSwapResponse } from '../swap.types' import { sentryMetaForSwapHandler } from '@/lib/shared/utils/query-errors' import { isZero } from '@/lib/shared/utils/numbers' +import { getChainId } from '@/lib/config/app.config' +import { useBlockNumber } from 'wagmi' export type SwapSimulationQueryResult = ReturnType @@ -32,6 +34,9 @@ export function useSimulateSwapQuery({ chain, } + const chainId = getChainId(chain) + const { data: blockNumber } = useBlockNumber({ chainId }) + const queryKey = swapQueryKeys.simulation(inputs) const queryFn = async () => handler.simulate(inputs) @@ -42,6 +47,8 @@ export function useSimulateSwapQuery({ enabled: enabled && !isZero(debouncedSwapAmount), gcTime: 0, meta: sentryMetaForSwapHandler('Error in swap simulation query', { + chainId: getChainId(chain), + blockNumber, handler, swapInputs: inputs, enabled, diff --git a/lib/modules/web3/useTenderly.ts b/lib/modules/web3/useTenderly.ts index 737be4054..40f201fb2 100644 --- a/lib/modules/web3/useTenderly.ts +++ b/lib/modules/web3/useTenderly.ts @@ -5,24 +5,30 @@ import { TransactionConfig } from './contracts/contract.types' Used in sentry metadata to be able to simulate tx from Sentry issues in Tenderly */ export function useTenderly({ chainId }: { chainId: number }) { - const { data: _blockNumber } = useBlockNumber({ chainId }) - const { data: _gasPrice } = useGasPrice({ chainId }) + const { data: blockNumber } = useBlockNumber({ chainId }) + const { data: gasPrice } = useGasPrice({ chainId }) - function buildTenderlyUrl(txConfig?: TransactionConfig) { - if (!txConfig) return - const { chainId: buildCallChainId, account, to, data, value } = txConfig - if (chainId !== buildCallChainId) { - throw new Error( - `Chain Id mismatch (${buildCallChainId} VS ${chainId}) when building Tenderly simulation URL` - ) - } + const _buildTenderlyUrl = (txConfig?: TransactionConfig) => + buildTenderlyUrl({ txConfig, blockNumber, gasPrice }) - const txValue = value ? value.toString() : '0' - const blockNumber = _blockNumber ? _blockNumber.toString() : '0' - const gasPrice = _gasPrice ? _gasPrice.toString() : '0' + return { blockNumber, gasPrice, buildTenderlyUrl: _buildTenderlyUrl } +} + +export function buildTenderlyUrl({ + txConfig, + blockNumber, + gasPrice, +}: { + txConfig?: TransactionConfig + blockNumber?: bigint + gasPrice?: bigint +}): string | undefined { + if (!txConfig) return + const { chainId, account, to, data, value } = txConfig - return `https://dashboard.tenderly.co/balancer/v2/simulator/new?rawFunctionInput=${data}&block=${blockNumber}&blockIndex=0&from=${account}&gas=8000000&gasPrice=${gasPrice}&value=${txValue}&contractAddress=${to}&network=${chainId}` - } + const txValue = value ? value.toString() : '0' + const blockNumberString = blockNumber ? blockNumber.toString() : '0' + const gasPriceString = gasPrice ? gasPrice.toString() : '0' - return { buildTenderlyUrl } + return `https://dashboard.tenderly.co/balancer/v2/simulator/new?rawFunctionInput=${data}&block=${blockNumberString}&blockIndex=0&from=${account}&gas=8000000&gasPrice=${gasPriceString}&value=${txValue}&contractAddress=${to}&network=${chainId}` } diff --git a/lib/shared/utils/errors.spec.ts b/lib/shared/utils/errors.spec.ts new file mode 100644 index 000000000..f75141c4e --- /dev/null +++ b/lib/shared/utils/errors.spec.ts @@ -0,0 +1,49 @@ +/* eslint-disable max-len */ +import { getTenderlyUrlFromErrorMessage } from './errors' + +describe('getTenderlyUrlFromError', () => { + const queryMeta = { context: { extra: { params: { chainId: 100, blockNumber: 36029196n } } } } + + test('when queryMeta does not have chainId', () => { + const queryMeta = { context: { extra: { params: {} } } } + const error = new Error('Random error') + expect(getTenderlyUrlFromErrorMessage(error, queryMeta)).toBeUndefined() + }) + + test('when error is not "RPC Request failed"', () => { + const error = new Error('Random error') + expect(getTenderlyUrlFromErrorMessage(error, queryMeta)).toBeUndefined() + }) + + test('when error is an "RPC Request failed"', () => { + // Typical message from a ContractFunctionExecutionError from viem + const errorMessage = `RPC Request failed. + + URL: http://localhost:3000/api/rpc/GNOSIS + Request body: {"method":"eth_call","params":[{"data":"0xc7b2c52c4d7d7b769cc617a00ed98ff3426c350a50e727120001000000000000000000c1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000000002a22f9c3b484c3629090feed35f17ff8f88f76f00000000000000000000000004ecaba5870353805a9f068101a40e0f32ed605c60000000000000000000000007ef541e2a22058048904fe5744f9c7e4c57af7170000000000000000000000008fed19c7e7b59e7cfe8d0a1f570c96a721198710000000000000000000000000af204776c7245bf4147c2612bf6e5972ee4837010000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016954c3922357c2d1710000000000000000000000000000000000000000000000000000000000000000","to":"0x0f3e0c4218b7b0108a3643cfe9d3ec0d4f57c54e"},"latest"]} + + Raw Call Arguments: + to: 0x0f3e0c4218b7b0108a3643cfe9d3ec0d4f57c54e + data: 0xc7b2c52c4d7d7b769cc617a00ed98ff3426c350a50e727120001000000000000000000c1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000000002a22f9c3b484c3629090feed35f17ff8f88f76f00000000000000000000000004ecaba5870353805a9f068101a40e0f32ed605c60000000000000000000000007ef541e2a22058048904fe5744f9c7e4c57af7170000000000000000000000008fed19c7e7b59e7cfe8d0a1f570c96a721198710000000000000000000000000af204776c7245bf4147c2612bf6e5972ee4837010000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016954c3922357c2d1710000000000000000000000000000000000000000000000000000000000000000 + + Contract Call: + address: 0x0f3e0c4218b7b0108a3643cfe9d3ec0d4f57c54e + function: queryExit(bytes32 poolId, address sender, address recipient, (address[] assets, uint256[] minAmountsOut, bytes userData, bool toInternalBalance)) + args: (0x4d7d7b769cc617a00ed98ff3426c350a50e727120001000000000000000000c1, 0x0000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000, {"assets":["0x2a22f9c3b484c3629090feed35f17ff8f88f76f0","0x4ecaba5870353805a9f068101a40e0f32ed605c6","0x7ef541e2a22058048904fe5744f9c7e4c57af717","0x8fed19c7e7b59e7cfe8d0a1f570c96a721198710","0xaf204776c7245bf4147c2612bf6e5972ee483701"],"minAmountsOut":["1","1","1","1","1"],"userData":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016954c3922357c2d1710000000000000000000000000000000000000000000000000000000000000000","toInternalBalance":false}) + + Docs: https://viem.sh/docs/contract/simulateContract + Details: vm execution error. + Version: 2.21.6 + at getContractError (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/viem@2.21.6_bufferutil@4.0.8_typescript@5.4.5_utf-8-validate@5.0.10_zod@3.22.4/node_modules/viem/_esm/utils/errors/getContractError.js:34:12) + at simulateContract (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/viem@2.21.6_bufferutil@4.0.8_typescript@5.4.5_utf-8-validate@5.0.10_zod@3.22.4/node_modules/viem/_esm/actions/public/simulateContract.js:83:98) + at async doRemoveLiquidityQuery (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@balancer+sdk@0.25.0_bufferutil@4.0.8_typescript@5.4.5_utf-8-validate@5.0.10_zod@3.22.4/node_modules/@balancer/sdk/dist/index.mjs:17702:7) + at async RemoveLiquidityWeighted.query (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/@balancer+sdk@0.25.0_bufferutil@4.0.8_typescript@5.4.5_utf-8-validate@5.0.10_zod@3.22.4/node_modules/@balancer/sdk/dist/index.mjs:17732:25) + at async SingleTokenRemoveLiquidityHandler.simulate (webpack-internal:///(app-pages-browser)/./lib/modules/pool/actions/remove-liquidity/handlers/SingleTokenRemoveLiquidity.handler.ts:22:32) + ` + const error = new Error(errorMessage) + + expect(getTenderlyUrlFromErrorMessage(error, queryMeta)).toBe( + 'https://dashboard.tenderly.co/balancer/v2/simulator/new?rawFunctionInput=0xc7b2c52c4d7d7b769cc617a00ed98ff3426c350a50e727120001000000000000000000c1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000000002a22f9c3b484c3629090feed35f17ff8f88f76f00000000000000000000000004ecaba5870353805a9f068101a40e0f32ed605c60000000000000000000000007ef541e2a22058048904fe5744f9c7e4c57af7170000000000000000000000008fed19c7e7b59e7cfe8d0a1f570c96a721198710000000000000000000000000af204776c7245bf4147c2612bf6e5972ee4837010000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016954c3922357c2d1710000000000000000000000000000000000000000000000000000000000000000&block=36029196&blockIndex=0&from=0x0000000000000000000000000000000000000000&gas=8000000&gasPrice=0&value=0&contractAddress=0x0f3e0c4218b7b0108a3643cfe9d3ec0d4f57c54e&network=100' + ) + }) +}) diff --git a/lib/shared/utils/errors.ts b/lib/shared/utils/errors.ts index 7511832ac..27a17dd70 100644 --- a/lib/shared/utils/errors.ts +++ b/lib/shared/utils/errors.ts @@ -14,6 +14,8 @@ * }) * } */ +import { TransactionConfig } from '@/lib/modules/web3/contracts/contract.types' +import { buildTenderlyUrl } from '@/lib/modules/web3/useTenderly' import { captureException } from '@sentry/nextjs' import { ScopeContext } from '@sentry/types/types/scope' @@ -84,3 +86,64 @@ class ErrorWithShortMessage extends Error { Object.setPrototypeOf(this, ErrorWithShortMessage.prototype) } } + +type QueryMeta = { + context?: { + extra?: { + params?: { chainId?: number; blockNumber?: bigint } + } + } +} + +/* + When present, it parses the build call data from the error message and builds a tenderly simulation url. +*/ +export function getTenderlyUrlFromErrorMessage( + error: Error, + queryMeta?: QueryMeta +): string | undefined { + const queryParams = queryMeta?.context?.extra?.params + const chainId = queryParams?.chainId + if (!chainId) return + + const txConfig = parseRequestError(error, chainId) + if (!txConfig) return + + return buildTenderlyUrl({ txConfig, blockNumber: queryParams?.blockNumber }) +} + +/* + When present, parses viem's exception message to extract the transaction config (build call data) +*/ +function parseRequestError(error: Error, chainId: number): TransactionConfig | undefined { + if (!error.message.startsWith('RPC Request failed')) return + const requestBodyRegex = /Request body: ({.*})/ + + const match = error?.stack?.match(requestBodyRegex) + + if (match && match[1]) { + const jsonString = match[1] + + try { + const parsedBody = JSON.parse(jsonString) + const rawCall = parsedBody?.params?.[0] + if (rawCall) { + const txConfig: TransactionConfig = { + data: rawCall.data, + to: rawCall.to, + account: rawCall.from, + chainId, + } + if (!txConfig.account) { + txConfig.account = '0x0000000000000000000000000000000000000000' // Unknown account in tenderly + } + return txConfig + } + } catch (error) { + // Ignore errors when parsing + return + } + } + + return +} diff --git a/lib/shared/utils/query-errors.spec.ts b/lib/shared/utils/query-errors.spec.ts index 675c3da43..8341de747 100644 --- a/lib/shared/utils/query-errors.spec.ts +++ b/lib/shared/utils/query-errors.spec.ts @@ -58,7 +58,10 @@ describe('Captures sentry error', () => { } const error = new Error('test cause error') - const meta = sentryMetaForRemoveLiquidityHandler('Test error message', params) + const meta = sentryMetaForRemoveLiquidityHandler('Test error message', { + ...params, + chainId: 1, + }) captureSentryError(error, meta) const report = await getSentryReport() @@ -70,6 +73,7 @@ describe('Captures sentry error', () => { { "handler": "RecoveryRemoveLiquidityHandler", "params": { + "chainId": 1, "handler": { "helpers": "[LiquidityActionHelpers]", }, @@ -97,7 +101,7 @@ describe('Captures sentry error', () => { } const error = new Error('test cause error') - const meta = sentryMetaForAddLiquidityHandler('Test error message', params) + const meta = sentryMetaForAddLiquidityHandler('Test error message', { ...params, chainId: 1 }) captureSentryError(error, meta) const report = await getSentryReport() @@ -109,6 +113,7 @@ describe('Captures sentry error', () => { { "handler": "UnbalancedAddLiquidityHandler", "params": { + "chainId": 1, "handler": { "helpers": "[LiquidityActionHelpers]", }, diff --git a/lib/shared/utils/query-errors.ts b/lib/shared/utils/query-errors.ts index 1a2308f77..89b80e2f2 100644 --- a/lib/shared/utils/query-errors.ts +++ b/lib/shared/utils/query-errors.ts @@ -28,13 +28,15 @@ export type SentryMetadata = { context?: Partial } -export function sentryMetaForAddLiquidityHandler(errorMessage: string, params: AddLiquidityParams) { +type AddMetaParams = AddLiquidityParams & { chainId: number; blockNumber?: bigint } +export function sentryMetaForAddLiquidityHandler(errorMessage: string, params: AddMetaParams) { return createAddHandlerMetadata('HandlerQueryError', errorMessage, params) } +type RemoveMetaParams = RemoveLiquidityParams & { chainId: number; blockNumber?: bigint } export function sentryMetaForRemoveLiquidityHandler( errorMessage: string, - params: RemoveLiquidityParams + params: RemoveMetaParams ) { return createRemoveHandlerMetadata('HandlerQueryError', errorMessage, params) } @@ -45,10 +47,12 @@ export type SwapBuildCallExtras = { slippage: string wethIsEth: boolean } -export function sentryMetaForSwapHandler( - errorMessage: string, - params: SimulateSwapParams | SwapBuildCallExtras -) { + +export type SwapMetaParams = (SimulateSwapParams | SwapBuildCallExtras) & { + chainId: number + blockNumber?: bigint +} +export function sentryMetaForSwapHandler(errorMessage: string, params: SwapMetaParams) { return createSwapHandlerMetadata('HandlerQueryError', errorMessage, params) } @@ -131,7 +135,7 @@ function createAddHandlerMetadata( function createRemoveHandlerMetadata( errorName: string, errorMessage: string, - params: RemoveLiquidityParams + params: RemoveMetaParams ) { const extra: Extras = { handler: params.handler.constructor.name, @@ -146,7 +150,7 @@ function createRemoveHandlerMetadata( function createSwapHandlerMetadata( errorName: string, errorMessage: string, - params: SimulateSwapParams | SwapBuildCallExtras + params: SwapMetaParams ) { const { handler, ...rest } = params const extra: Extras = { @@ -213,6 +217,7 @@ export function captureSentryError( e: unknown, { context, errorMessage, errorName }: SentryMetadata ) { + console.log('Context en captureSentryError', context) const causeError = ensureError(e) if (isUserRejectedError(causeError)) return @@ -341,3 +346,8 @@ function sentryStackFramesToString(sentryStack?: SentryStack): string { .join() ) } + +export function getTenderlyUrl(sentryExtras?: Extras) { + if (!sentryExtras) return + return sentryExtras.tenderlyUrl as string | undefined +} From ab4425b507bee290d196fd1853db7771998269a1 Mon Sep 17 00:00:00 2001 From: Alberto Gualis Date: Mon, 16 Sep 2024 14:28:06 +0200 Subject: [PATCH 2/3] chore: improve error message for RPC Request failed error --- lib/shared/components/errors/GenericError.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/shared/components/errors/GenericError.tsx b/lib/shared/components/errors/GenericError.tsx index 20a873900..93788372c 100644 --- a/lib/shared/components/errors/GenericError.tsx +++ b/lib/shared/components/errors/GenericError.tsx @@ -29,6 +29,19 @@ export function GenericError({ error: _error, customErrorName, ...rest }: Props) ) } const errorMessage = error?.shortMessage || error.message + + if (errorMessage === 'RPC Request failed.') { + return ( + + + It looks like there was an RPC Request issue. You can report the problem in{' '} + our discord if the issue + persists. + + + ) + } + return ( From 56d70522c42a95e976d9603c5c331f9f049e8901 Mon Sep 17 00:00:00 2001 From: Alberto Gualis Date: Mon, 16 Sep 2024 14:44:29 +0200 Subject: [PATCH 3/3] chore: move const --- app/react-query.provider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/react-query.provider.tsx b/app/react-query.provider.tsx index c2ec3883c..cfe1fb6f9 100644 --- a/app/react-query.provider.tsx +++ b/app/react-query.provider.tsx @@ -18,7 +18,6 @@ export const queryClient = new QueryClient({ // Global handler for every react-query error onError: (error, query) => { const queryMeta = query?.meta - const sentryContext = query?.meta?.context as ScopeContext if (shouldIgnore(error.message, error.stack)) return console.log('Sentry capturing query error', { meta: queryMeta, @@ -26,6 +25,7 @@ export const queryClient = new QueryClient({ queryKey: query.queryKey, }) + const sentryContext = query?.meta?.context as ScopeContext if (sentryContext?.extra && !getTenderlyUrl(sentryContext.extra)) { sentryContext.extra.tenderlyUrl = getTenderlyUrlFromErrorMessage(error, queryMeta) }