From 325029b3d4b0ed3eaf6a264ad40cc16254ee70d0 Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Thu, 19 Dec 2024 09:17:34 -0800 Subject: [PATCH] handle multicall revert --- .../assets-controllers/src/multicall.test.ts | 46 +++++++++++++++++++ packages/assets-controllers/src/multicall.ts | 26 +++++++++-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/packages/assets-controllers/src/multicall.test.ts b/packages/assets-controllers/src/multicall.test.ts index e8692ac5c3..8fbbea8911 100644 --- a/packages/assets-controllers/src/multicall.test.ts +++ b/packages/assets-controllers/src/multicall.test.ts @@ -122,4 +122,50 @@ describe('multicall', () => { ]); }); }); + + describe('error handling of reverts', () => { + const call = { + contract: new Contract( + '0x0000000000000000000000000000000000000001', + abiERC20, + provider, + ), + functionSignature: 'balanceOf(address)', + arguments: ['0x0000000000000000000000000000000000000000'], + }; + + it('should fall back to parallel calls when multicall reverts', async () => { + jest.spyOn(provider, 'call').mockImplementationOnce(() => { + const error = { code: 'CALL_EXCEPTION' }; + return Promise.reject(error); + }); + + jest + .spyOn(provider, 'call') + .mockImplementationOnce(() => + Promise.resolve(defaultAbiCoder.encode(['uint256'], [1])), + ); + + const results = await multicallOrFallback([call], '0x1', provider); + + expect(results).toMatchObject([ + { + success: true, + // eslint-disable-next-line @typescript-eslint/naming-convention + value: { _hex: '0x01' }, + }, + ]); + }); + + it('should throw rpc errors other than revert', async () => { + const error = { code: 'network error' }; + jest.spyOn(provider, 'call').mockImplementationOnce(() => { + return Promise.reject(error); + }); + + await expect( + multicallOrFallback([call], '0x1', provider), + ).rejects.toMatchObject(error); + }); + }); }); diff --git a/packages/assets-controllers/src/multicall.ts b/packages/assets-controllers/src/multicall.ts index e5be69f09d..7f47952e42 100644 --- a/packages/assets-controllers/src/multicall.ts +++ b/packages/assets-controllers/src/multicall.ts @@ -392,7 +392,27 @@ export const multicallOrFallback = async ( } const multicallAddress = MULTICALL_CONTRACT_BY_CHAINID[chainId]; - return await (multicallAddress - ? multicall(calls, multicallAddress, provider, maxCallsPerMulticall) - : fallback(calls, maxCallsParallel)); + if (multicallAddress) { + try { + return await multicall( + calls, + multicallAddress, + provider, + maxCallsPerMulticall, + ); + } catch (error: unknown) { + // Fallback only on revert + // https://docs.ethers.org/v5/troubleshooting/errors/#help-CALL_EXCEPTION + if ( + !error || + typeof error !== 'object' || + !('code' in error) || + error.code !== 'CALL_EXCEPTION' + ) { + throw error; + } + } + } + + return await fallback(calls, maxCallsParallel); };