diff --git a/src/update-feeds-loops/gas-estimation.test.ts b/src/update-feeds-loops/gas-estimation.test.ts new file mode 100644 index 00000000..562e5e1f --- /dev/null +++ b/src/update-feeds-loops/gas-estimation.test.ts @@ -0,0 +1,134 @@ +import type { Api3ServerV1 } from '@api3/contracts'; + +import { generateMockApi3ServerV1 } from '../../test/fixtures/mock-contract'; +import { logger } from '../logger'; + +import { estimateMulticallGasLimit, estimateSingleBeaconGasLimit, handleRpcGasLimitFailure } from './gas-estimation'; + +describe(estimateMulticallGasLimit.name, () => { + it('estimates the gas limit for a multicall', async () => { + const mockApi3ServerV1 = generateMockApi3ServerV1(); + mockApi3ServerV1.multicall.estimateGas.mockResolvedValueOnce(BigInt(500_000)); + + const gasLimit = await estimateMulticallGasLimit( + mockApi3ServerV1 as unknown as Api3ServerV1, + ['0xBeaconId1Calldata', '0xBeaconId2Calldata', '0xBeaconSetCalldata'], + undefined + ); + + expect(gasLimit).toStrictEqual(BigInt(550_000)); // Note that the gas limit is increased by 10%. + }); + + it('uses fallback gas limit when dummy data estimation fails', async () => { + const mockApi3ServerV1 = generateMockApi3ServerV1(); + mockApi3ServerV1.multicall.estimateGas.mockRejectedValue(new Error('some-error')); + jest.spyOn(logger, 'warn'); + + const gasLimit = await estimateMulticallGasLimit( + mockApi3ServerV1 as unknown as Api3ServerV1, + ['0xBeaconId1Calldata', '0xBeaconId2Calldata', '0xBeaconSetCalldata'], + 2_000_000 + ); + + expect(gasLimit).toStrictEqual(BigInt(2_000_000)); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith('Unable to estimate gas using provider.', { + errorMessage: 'some-error', + }); + }); + + it('returns null if no fallback is provided', async () => { + const mockApi3ServerV1 = generateMockApi3ServerV1(); + mockApi3ServerV1.multicall.estimateGas.mockRejectedValue(new Error('some-error')); + jest.spyOn(logger, 'info'); + jest.spyOn(logger, 'warn'); + + const gasLimit = await estimateMulticallGasLimit( + mockApi3ServerV1 as unknown as Api3ServerV1, + ['0xBeaconId1Calldata', '0xBeaconId2Calldata', '0xBeaconSetCalldata'], + undefined + ); + + expect(gasLimit).toBeNull(); + expect(logger.info).toHaveBeenCalledTimes(1); + expect(logger.info).toHaveBeenCalledWith('No fallback gas limit provided. No gas limit to use.'); + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith('Unable to estimate gas using provider.', { + errorMessage: 'some-error', + }); + }); + + it('detects a contract revert due to timestamp check', async () => { + const mockApi3ServerV1 = generateMockApi3ServerV1(); + mockApi3ServerV1.multicall.estimateGas.mockRejectedValue(new Error('Does not update timestamp')); + jest.spyOn(logger, 'info'); + jest.spyOn(logger, 'warn'); + + const gasLimit = await estimateMulticallGasLimit( + mockApi3ServerV1 as unknown as Api3ServerV1, + ['0xBeaconId1Calldata', '0xBeaconId2Calldata', '0xBeaconSetCalldata'], + 2_000_000 + ); + + expect(gasLimit).toBe(2_000_000n); + expect(logger.info).toHaveBeenCalledTimes(1); + expect(logger.info).toHaveBeenCalledWith('Gas estimation failed because of a contract revert.', { + errorMessage: 'Does not update timestamp', + }); + expect(logger.warn).toHaveBeenCalledTimes(0); + }); +}); + +describe(handleRpcGasLimitFailure.name, () => { + it('uses a fallback gas limit', () => { + expect(handleRpcGasLimitFailure(new Error('some-error'), 2_000_000)).toStrictEqual(BigInt(2_000_000)); + }); + + it('returns null if no gas limit is provided', () => { + expect(handleRpcGasLimitFailure(new Error('some-error'), undefined)).toBeNull(); + }); + + it('logs a warning for unknown rpc error', () => { + jest.spyOn(logger, 'warn'); + + handleRpcGasLimitFailure(new Error('some-error'), 2_000_000); + + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith('Unable to estimate gas using provider.', { errorMessage: 'some-error' }); + }); + + it('logs info message when on contract revert error', () => { + jest.spyOn(logger, 'info'); + + handleRpcGasLimitFailure(new Error('Does not update timestamp'), 2_000_000); + + expect(logger.info).toHaveBeenCalledTimes(1); + expect(logger.info).toHaveBeenCalledWith('Gas estimation failed because of a contract revert.', { + errorMessage: 'Does not update timestamp', + }); + }); +}); + +describe(estimateSingleBeaconGasLimit.name, () => { + it('estimates the gas limit for a single beacon update', async () => { + const mockApi3ServerV1 = generateMockApi3ServerV1(); + mockApi3ServerV1.updateBeaconWithSignedData.estimateGas.mockResolvedValueOnce(BigInt(500_000)); + + const gasLimit = await estimateSingleBeaconGasLimit( + mockApi3ServerV1 as unknown as Api3ServerV1, + { + beaconId: '0xBeaconId', + signedData: { + airnode: '0xAirnode', + templateId: '0xTemplateId', + timestamp: '1000000', + encodedValue: '0xEncodedValue', + signature: '0xSignature', + }, + }, + undefined + ); + + expect(gasLimit).toStrictEqual(BigInt(500_000)); + }); +}); diff --git a/src/update-feeds-loops/gas-estimation.ts b/src/update-feeds-loops/gas-estimation.ts index e69de29b..aa08b4f9 100644 --- a/src/update-feeds-loops/gas-estimation.ts +++ b/src/update-feeds-loops/gas-estimation.ts @@ -0,0 +1,60 @@ +import type { Api3ServerV1 } from '@api3/contracts'; +import { go } from '@api3/promise-utils'; + +import { logger } from '../logger'; + +import type { UpdatableBeacon } from './get-updatable-feeds'; + +export const handleRpcGasLimitFailure = (error: Error, fallbackGasLimit: number | undefined) => { + const errorMessage = error.message; + // It is possible that the gas estimation failed because of a contract revert due to timestamp check, because the feed + // was updated by other provider in the meantime. Try to detect this expected case and log INFO instead. + if (errorMessage.includes('Does not update timestamp')) { + logger.info(`Gas estimation failed because of a contract revert.`, { errorMessage }); + } else { + logger.warn(`Unable to estimate gas using provider.`, { errorMessage }); + } + + if (!fallbackGasLimit) { + // Logging it as an INFO because in practice this would result in double logging of the same issue. If there is no + // fallback gas limit specified it's expected that the update transcation will be skipped in case of gas limit + // estimation failure. + logger.info('No fallback gas limit provided. No gas limit to use.'); + return null; + } + + return BigInt(fallbackGasLimit); +}; + +export const estimateSingleBeaconGasLimit = async ( + api3ServerV1: Api3ServerV1, + beacon: UpdatableBeacon, + fallbackGasLimit: number | undefined +) => { + const { signedData } = beacon; + + const goEstimateGas = await go(async () => + api3ServerV1.updateBeaconWithSignedData.estimateGas( + signedData.airnode, + signedData.templateId, + signedData.timestamp, + signedData.encodedValue, + signedData.signature + ) + ); + if (goEstimateGas.success) return BigInt(goEstimateGas.data); + return handleRpcGasLimitFailure(goEstimateGas.error, fallbackGasLimit); +}; + +export const estimateMulticallGasLimit = async ( + api3ServerV1: Api3ServerV1, + calldatas: string[], + fallbackGasLimit: number | undefined +) => { + const goEstimateGas = await go(async () => api3ServerV1.multicall.estimateGas(calldatas)); + if (goEstimateGas.success) { + // Adding a extra 10% because multicall consumes less gas than tryMulticall + return (goEstimateGas.data * BigInt(Math.round(1.1 * 100))) / 100n; + } + return handleRpcGasLimitFailure(goEstimateGas.error, fallbackGasLimit); +}; diff --git a/src/update-feeds-loops/submit-transactions.test.ts b/src/update-feeds-loops/submit-transactions.test.ts index b868eaa4..35a09ac2 100644 --- a/src/update-feeds-loops/submit-transactions.test.ts +++ b/src/update-feeds-loops/submit-transactions.test.ts @@ -8,84 +8,11 @@ import { logger } from '../logger'; import * as stateModule from '../state'; import * as utilsModule from '../utils'; +import * as gasEstimationModule from './gas-estimation'; import type { UpdatableDataFeed } from './get-updatable-feeds'; import * as submitTransactionsModule from './submit-transactions'; import * as updatabilityTimestampModule from './updatability-timestamp'; -describe(submitTransactionsModule.estimateMulticallGasLimit.name, () => { - it('estimates the gas limit for a multicall', async () => { - const mockApi3ServerV1 = generateMockApi3ServerV1(); - mockApi3ServerV1.multicall.estimateGas.mockResolvedValueOnce(BigInt(500_000)); - - const gasLimit = await submitTransactionsModule.estimateMulticallGasLimit( - mockApi3ServerV1 as unknown as Api3ServerV1, - ['0xBeaconId1Calldata', '0xBeaconId2Calldata', '0xBeaconSetCalldata'], - undefined - ); - - expect(gasLimit).toStrictEqual(BigInt(550_000)); // Note that the gas limit is increased by 10%. - }); - - it('uses fallback gas limit when dummy data estimation fails', async () => { - const mockApi3ServerV1 = generateMockApi3ServerV1(); - mockApi3ServerV1.multicall.estimateGas.mockRejectedValue(new Error('some-error')); - jest.spyOn(logger, 'warn'); - - const gasLimit = await submitTransactionsModule.estimateMulticallGasLimit( - mockApi3ServerV1 as unknown as Api3ServerV1, - ['0xBeaconId1Calldata', '0xBeaconId2Calldata', '0xBeaconSetCalldata'], - 2_000_000 - ); - - expect(gasLimit).toStrictEqual(BigInt(2_000_000)); - expect(logger.warn).toHaveBeenCalledTimes(1); - expect(logger.warn).toHaveBeenCalledWith('Unable to estimate gas for multicall using provider.', { - errorMessage: 'some-error', - }); - }); - - it('returns null if no fallback is provided', async () => { - const mockApi3ServerV1 = generateMockApi3ServerV1(); - mockApi3ServerV1.multicall.estimateGas.mockRejectedValue(new Error('some-error')); - jest.spyOn(logger, 'info'); - jest.spyOn(logger, 'warn'); - - const gasLimit = await submitTransactionsModule.estimateMulticallGasLimit( - mockApi3ServerV1 as unknown as Api3ServerV1, - ['0xBeaconId1Calldata', '0xBeaconId2Calldata', '0xBeaconSetCalldata'], - undefined - ); - - expect(gasLimit).toBeNull(); - expect(logger.info).toHaveBeenCalledTimes(1); - expect(logger.info).toHaveBeenCalledWith('No fallback gas limit provided. No gas limit to use.'); - expect(logger.warn).toHaveBeenCalledTimes(1); - expect(logger.warn).toHaveBeenCalledWith('Unable to estimate gas for multicall using provider.', { - errorMessage: 'some-error', - }); - }); - - it('detects a contract revert due to timestamp check', async () => { - const mockApi3ServerV1 = generateMockApi3ServerV1(); - mockApi3ServerV1.multicall.estimateGas.mockRejectedValue(new Error('Does not update timestamp')); - jest.spyOn(logger, 'info'); - jest.spyOn(logger, 'warn'); - - const gasLimit = await submitTransactionsModule.estimateMulticallGasLimit( - mockApi3ServerV1 as unknown as Api3ServerV1, - ['0xBeaconId1Calldata', '0xBeaconId2Calldata', '0xBeaconSetCalldata'], - 2_000_000 - ); - - expect(gasLimit).toBe(2_000_000n); - expect(logger.info).toHaveBeenCalledTimes(1); - expect(logger.info).toHaveBeenCalledWith('Gas estimation failed because of a contract revert.', { - errorMessage: 'Does not update timestamp', - }); - expect(logger.warn).toHaveBeenCalledTimes(0); - }); -}); - describe(submitTransactionsModule.createUpdateFeedCalldatas.name, () => { it('creates beacon update calldata', () => { const api3ServerV1 = generateMockApi3ServerV1(); @@ -388,7 +315,7 @@ describe(submitTransactionsModule.submitTransaction.name, () => { jest.spyOn(submitTransactionsModule, 'createUpdateFeedCalldatas').mockReturnValue(['calldata1', 'calldata2']); jest.spyOn(logger, 'debug'); jest.spyOn(logger, 'info'); - jest.spyOn(submitTransactionsModule, 'estimateMulticallGasLimit').mockResolvedValue(BigInt(500_000)); + jest.spyOn(gasEstimationModule, 'estimateMulticallGasLimit').mockResolvedValue(BigInt(500_000)); jest.spyOn(gasPriceModule, 'getRecommendedGasPrice').mockReturnValue(BigInt(100_000_000)); jest.spyOn(updatabilityTimestampModule, 'isAlreadyUpdatable').mockReturnValue(false); const api3ServerV1 = generateMockApi3ServerV1(); @@ -419,9 +346,33 @@ describe(submitTransactionsModule.submitTransaction.name, () => { provider, api3ServerV1 as unknown as Api3ServerV1, allowPartial({ + updatableBeacons: [ + { + beaconId: '0xBeaconId1', + signedData: { + airnode: '0xAirnode1', + templateId: '0xTemplateId1', + timestamp: '1629811000', + encodedValue: '0xEncodedValue', + signature: '0xSignature', + }, + }, + ], dataFeedInfo: { dapiName, dataFeedId: '0xBeaconSetId', + beaconsWithData: [ + { + beaconId: '0xBeaconId1', + airnodeAddress: '0xAirnode1', + templateId: '0xTemplateId1', + }, + { + beaconId: '0xBeaconId2', + airnodeAddress: '0xAirnode2', + templateId: '0xTemplateId2', + }, + ], }, }), 123_456 @@ -435,27 +386,27 @@ describe(submitTransactionsModule.submitTransaction.name, () => { nonce: 0, sponsorWalletAddress: '0xA772F7b103BBecA3Bb6C74Be41fCc2c192C8146c', }); - expect(logger.info).toHaveBeenNthCalledWith(2, 'Successfully submitted the multicall transaction.', { + expect(logger.info).toHaveBeenNthCalledWith(2, 'Successfully submitted the update transaction.', { txHash: '0xTransactionHash', }); // Verify the flow of the update process via the debug logs. Note, that some debug log calls are not here because // many functions are mocked. expect(logger.debug).toHaveBeenCalledTimes(6); - expect(logger.debug).toHaveBeenNthCalledWith(1, 'Creating calldatas.'); - expect(logger.debug).toHaveBeenNthCalledWith(2, 'Estimating gas limit.'); - expect(logger.debug).toHaveBeenNthCalledWith(3, 'Getting derived sponsor wallet.'); - expect(logger.debug).toHaveBeenNthCalledWith(4, 'Derived new sponsor wallet.', { + expect(logger.debug).toHaveBeenNthCalledWith(1, 'Getting derived sponsor wallet.'); + expect(logger.debug).toHaveBeenNthCalledWith(2, 'Derived new sponsor wallet.', { sponsorWalletAddress: '0xA772F7b103BBecA3Bb6C74Be41fCc2c192C8146c', }); - expect(logger.debug).toHaveBeenNthCalledWith(5, 'Getting nonce.'); - expect(logger.debug).toHaveBeenNthCalledWith(6, 'Getting recommended gas price.'); + expect(logger.debug).toHaveBeenNthCalledWith(3, 'Getting nonce.'); + expect(logger.debug).toHaveBeenNthCalledWith(4, 'Getting recommended gas price.'); + expect(logger.debug).toHaveBeenNthCalledWith(5, 'Creating calldatas.'); + expect(logger.debug).toHaveBeenNthCalledWith(6, 'Estimating beacon set update gas limit.'); }); it('logs and error when getting nonce fails', async () => { jest.spyOn(submitTransactionsModule, 'createUpdateFeedCalldatas').mockReturnValue(['calldata1', 'calldata2']); jest.spyOn(logger, 'warn'); - jest.spyOn(submitTransactionsModule, 'estimateMulticallGasLimit').mockResolvedValue(BigInt(500_000)); + jest.spyOn(gasEstimationModule, 'estimateMulticallGasLimit').mockResolvedValue(BigInt(500_000)); jest.spyOn(gasPriceModule, 'getRecommendedGasPrice').mockReturnValue(BigInt(100_000_000)); jest.spyOn(updatabilityTimestampModule, 'isAlreadyUpdatable').mockReturnValue(false); const api3ServerV1 = generateMockApi3ServerV1(); @@ -498,3 +449,108 @@ describe(submitTransactionsModule.submitTransaction.name, () => { expect(logger.warn).toHaveBeenNthCalledWith(1, 'Failed to get nonce.', new Error('some-error')); }); }); + +describe(submitTransactionsModule.submitUpdate.name, () => { + it('submits a single beacon update', async () => { + const api3ServerV1 = generateMockApi3ServerV1(); + jest.spyOn(api3ServerV1, 'connect').mockReturnValue(api3ServerV1); + jest.spyOn(api3ServerV1.updateBeaconWithSignedData, 'estimateGas').mockReturnValue(150_000n); + jest.spyOn(api3ServerV1.updateBeaconWithSignedData, 'send').mockReturnValue({ hash: '0xTransactionHash' }); + jest.spyOn(logger, 'info'); + const sponsorWallet = new ethers.Wallet('a0d8c3f6643d494b31914e7ec896215562aa358bf7ff68218afb53dfedd4167f'); + + const result = await submitTransactionsModule.submitUpdate( + api3ServerV1 as unknown as Api3ServerV1, + allowPartial({ + dataFeedInfo: { + beaconsWithData: [ + { + beaconId: '0xBeaconId', + airnodeAddress: '0xAirnode', + templateId: '0xTemplateId', + }, + ], + }, + updatableBeacons: [ + { + beaconId: '0xBeaconId', + signedData: { + airnode: '0xAirnode', + templateId: '0xTemplateId', + timestamp: '1629811000', + encodedValue: '0xEncodedValue', + signature: '0xSignature', + }, + }, + ], + }), + undefined, + sponsorWallet, + BigInt(100_000_000), + 11 + ); + + expect(result).toStrictEqual({ hash: '0xTransactionHash' }); + expect(logger.info).toHaveBeenCalledTimes(1); + expect(logger.info).toHaveBeenCalledWith('Updating single beacon.', { + sponsorWalletAddress: '0xD8Ba840Cae5c24e5Dc148355Ea3cde3CFB12f8eF', + gasPrice: '100000000', + gasLimit: '150000', + nonce: 11, + }); + }); + + it('submits a beacon set update', async () => { + const api3ServerV1 = generateMockApi3ServerV1(); + jest.spyOn(api3ServerV1, 'connect').mockReturnValue(api3ServerV1); + jest.spyOn(api3ServerV1.multicall, 'estimateGas').mockReturnValue(150_000n); + jest.spyOn(api3ServerV1.tryMulticall, 'send').mockReturnValue({ hash: '0xTransactionHash' }); + jest.spyOn(logger, 'info'); + const sponsorWallet = new ethers.Wallet('a0d8c3f6643d494b31914e7ec896215562aa358bf7ff68218afb53dfedd4167f'); + + const result = await submitTransactionsModule.submitUpdate( + api3ServerV1 as unknown as Api3ServerV1, + allowPartial({ + dataFeedInfo: { + beaconsWithData: [ + { + beaconId: '0xBeaconId1', + airnodeAddress: '0xAirnode1', + templateId: '0xTemplateId1', + }, + { + beaconId: '0xBeaconId2', + airnodeAddress: '0xAirnode2', + templateId: '0xTemplateId2', + }, + ], + }, + updatableBeacons: [ + { + beaconId: '0xBeaconId1', + signedData: { + airnode: '0xAirnode1', + templateId: '0xTemplateId1', + timestamp: '1629811000', + encodedValue: '0xEncodedValue', + signature: '0xSignature', + }, + }, + ], + }), + undefined, + sponsorWallet, + BigInt(100_000_000), + 11 + ); + + expect(result).toStrictEqual({ hash: '0xTransactionHash' }); + expect(logger.info).toHaveBeenCalledTimes(1); + expect(logger.info).toHaveBeenCalledWith('Updating data feed.', { + sponsorWalletAddress: '0xD8Ba840Cae5c24e5Dc148355Ea3cde3CFB12f8eF', + gasPrice: '100000000', + gasLimit: '165000', + nonce: 11, + }); + }); +}); diff --git a/src/update-feeds-loops/submit-transactions.ts b/src/update-feeds-loops/submit-transactions.ts index 21776624..6fbe2c38 100644 --- a/src/update-feeds-loops/submit-transactions.ts +++ b/src/update-feeds-loops/submit-transactions.ts @@ -9,7 +9,8 @@ import { logger } from '../logger'; import { getState, updateState } from '../state'; import { deriveSponsorWallet } from '../utils'; -import type { UpdatableBeacon, UpdatableDataFeed } from './get-updatable-feeds'; +import { estimateMulticallGasLimit, estimateSingleBeaconGasLimit } from './gas-estimation'; +import type { UpdatableDataFeed } from './get-updatable-feeds'; export const createUpdateFeedCalldatas = (api3ServerV1: Api3ServerV1, updatableDataFeed: UpdatableDataFeed) => { const { dataFeedInfo, updatableBeacons } = updatableDataFeed; @@ -70,7 +71,7 @@ export const submitUpdate = async ( } = beacon; return api3ServerV1 .connect(connectedSponsorWallet) - .updateBeaconWithSignedData(airnode, templateId, timestamp, encodedValue, signature, { + .updateBeaconWithSignedData.send(airnode, templateId, timestamp, encodedValue, signature, { gasPrice, gasLimit, nonce, @@ -207,60 +208,6 @@ export const submitTransactions = async ( ) ); -export const handleRpcGasLimitFailure = (error: Error, fallbackGasLimit: number | undefined) => { - const errorMessage = error.message; - // It is possible that the gas estimation failed because of a contract revert due to timestamp check, because the feed - // was updated by other provider in the meantime. Try to detect this expected case and log INFO instead. - if (errorMessage.includes('Does not update timestamp')) { - logger.info(`Gas estimation failed because of a contract revert.`, { errorMessage }); - } else { - logger.warn(`Unable to estimate gas for single beacon using provider.`, { errorMessage }); - } - - if (!fallbackGasLimit) { - // Logging it as an INFO because in practice this would result in double logging of the same issue. If there is no - // fallback gas limit specified it's expected that the update transcation will be skipped in case of gas limit - // estimation failure. - logger.info('No fallback gas limit provided. No gas limit to use.'); - return null; - } - - return BigInt(fallbackGasLimit); -}; - -export const estimateSingleBeaconGasLimit = async ( - api3ServerV1: Api3ServerV1, - beacon: UpdatableBeacon, - fallbackGasLimit: number | undefined -) => { - const { signedData } = beacon; - - const goEstimateGas = await go(async () => - api3ServerV1.updateBeaconWithSignedData.estimateGas( - signedData.airnode, - signedData.templateId, - signedData.timestamp, - signedData.encodedValue, - signedData.signature - ) - ); - if (goEstimateGas.success) return BigInt(goEstimateGas.data); - return handleRpcGasLimitFailure(goEstimateGas.error, fallbackGasLimit); -}; - -export const estimateMulticallGasLimit = async ( - api3ServerV1: Api3ServerV1, - calldatas: string[], - fallbackGasLimit: number | undefined -) => { - const goEstimateGas = await go(async () => api3ServerV1.multicall.estimateGas(calldatas)); - if (goEstimateGas.success) { - // Adding a extra 10% because multicall consumes less gas than tryMulticall - return (goEstimateGas.data * BigInt(Math.round(1.1 * 100))) / 100n; - } - return handleRpcGasLimitFailure(goEstimateGas.error, fallbackGasLimit); -}; - export const getDerivedSponsorWallet = ( sponsorWalletMnemonic: string, dapiNameOrDataFeedId: Hex, diff --git a/test/e2e/update-feeds.feature.ts b/test/e2e/update-feeds.feature.ts index 8b140838..06deeeaf 100644 --- a/test/e2e/update-feeds.feature.ts +++ b/test/e2e/update-feeds.feature.ts @@ -118,18 +118,14 @@ it('updates blockchain data', async () => { expect(logger.debug).toHaveBeenCalledTimes(7); expect(logger.debug).toHaveBeenNthCalledWith(1, 'Fetching gas price and saving it to the state.'); - expect(logger.debug).toHaveBeenNthCalledWith(2, 'Creating calldatas.'); - expect(logger.debug).toHaveBeenNthCalledWith(3, 'Estimating gas limit.'); - expect(logger.debug).toHaveBeenNthCalledWith(4, 'Getting derived sponsor wallet.'); - expect(logger.debug).toHaveBeenNthCalledWith(5, 'Derived new sponsor wallet.', expect.anything()); - expect(logger.debug).toHaveBeenNthCalledWith(6, 'Getting nonce.'); - expect(logger.debug).toHaveBeenNthCalledWith(7, 'Getting recommended gas price.'); + expect(logger.debug).toHaveBeenNthCalledWith(2, 'Getting derived sponsor wallet.'); + expect(logger.debug).toHaveBeenNthCalledWith(3, 'Derived new sponsor wallet.', expect.anything()); + expect(logger.debug).toHaveBeenNthCalledWith(4, 'Getting nonce.'); + expect(logger.debug).toHaveBeenNthCalledWith(5, 'Getting recommended gas price.'); + expect(logger.debug).toHaveBeenNthCalledWith(6, 'Creating calldatas.'); + expect(logger.debug).toHaveBeenNthCalledWith(7, 'Estimating beacon set update gas limit.'); expect(logger.info).toHaveBeenCalledTimes(2); expect(logger.info).toHaveBeenNthCalledWith(1, 'Updating data feed.', expect.anything()); - expect(logger.info).toHaveBeenNthCalledWith( - 2, - 'Successfully submitted the multicall transaction.', - expect.anything() - ); + expect(logger.info).toHaveBeenNthCalledWith(2, 'Successfully submitted the update transaction.', expect.anything()); expect(logger.warn).toHaveBeenCalledTimes(0); }); diff --git a/test/fixtures/mock-contract.ts b/test/fixtures/mock-contract.ts index 9fbdd814..43cea747 100644 --- a/test/fixtures/mock-contract.ts +++ b/test/fixtures/mock-contract.ts @@ -39,7 +39,7 @@ export const generateMockAirseekerRegistry = () => { export const generateMockApi3ServerV1 = () => { return { multicall: { estimateGas: jest.fn() }, - updateBeaconWithSignedData: { estimateGas: jest.fn() }, + updateBeaconWithSignedData: { estimateGas: jest.fn(), send: jest.fn() }, updateBeaconSetWithBeacons: { estimateGas: jest.fn() }, interface: { encodeFunctionData: jest.fn(),