Skip to content
This repository was archived by the owner on Jul 9, 2024. It is now read-only.

Commit 480438c

Browse files
authored
Merge pull request #471 from api3dao/449-update-single-beacon-without-multicall
449 update single beacon without multicall
2 parents 9f4278a + e1aa4af commit 480438c

File tree

2 files changed

+201
-26
lines changed

2 files changed

+201
-26
lines changed

src/update-data-feeds.test.ts

+140
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,146 @@ describe('updateDataFeedsInLoop', () => {
300300
});
301301
});
302302

303+
describe('updateBeacons', () => {
304+
it('calls updateBeaconWithSignedData in Api3ServerV1 contract for single beacon', async () => {
305+
state.updateState((currentState) => ({
306+
...currentState,
307+
beaconValues: {
308+
'0x2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c': validSignedData,
309+
'0xa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2': undefined as any,
310+
'0x8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd': validSignedData,
311+
},
312+
}));
313+
314+
const txCountSpy = jest.spyOn(ethers.providers.StaticJsonRpcProvider.prototype, 'getTransactionCount');
315+
txCountSpy.mockResolvedValueOnce(212);
316+
317+
const timestamp = 1649664085;
318+
319+
const updateBeaconWithSignedDataMock = jest
320+
.fn()
321+
.mockReturnValueOnce({ hash: ethers.utils.hexlify(ethers.utils.randomBytes(32)) });
322+
const callStaticTryMulticallMock = jest.fn().mockReturnValueOnce({
323+
successes: [true, true],
324+
returndata: [
325+
ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [ethers.BigNumber.from(41000000000), timestamp - 30]),
326+
ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [ethers.BigNumber.from(40000000000), timestamp]),
327+
],
328+
});
329+
jest.spyOn(Api3ServerV1Factory, 'connect').mockImplementation(
330+
(_dapiServerAddress, _provider) =>
331+
({
332+
connect(_signerOrProvider: ethers.Signer | ethers.providers.Provider | string) {
333+
return this;
334+
},
335+
updateBeaconWithSignedData: updateBeaconWithSignedDataMock,
336+
interface: {
337+
encodeFunctionData: (functionFragment: string, values: [any]): string => {
338+
if (functionFragment === 'dataFeeds')
339+
return '0x67a7cfb741c3d6e0ee82ae3d33356c4dceb84e98d1a0b361db0f51081fc5a2541ae51683';
340+
341+
if (functionFragment === 'readDataFeedWithId') {
342+
switch (values[0]) {
343+
case '0x2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c':
344+
return '0xa5fc076f2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c';
345+
case '0xa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2':
346+
return '0xa5fc076fa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2';
347+
case '0x8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd':
348+
return '0xa5fc076f8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd';
349+
}
350+
}
351+
352+
if (functionFragment === 'updateBeaconWithSignedData')
353+
return '0x1a0a0b3e0000000000000000000000005656d3a378b1aadfddcf4196ea364a9d786172909ec34b00a5019442dcd05a4860ff2bf015164b368cb83fcb756088fcabcdabcd000000000000000000000000000000000000000000000000000000006253e05500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000009dc41b78000000000000000000000000000000000000000000000000000000000000000418aace553ec28f53cc976c8a2469d50f16de121d248495117aca36feb4950957827570e0648f82bdbc0afa6cb69dd9fe37dc7f9d58ae3aa06450e627e06c1b8031b00000000000000000000000000000000000000000000000000000000000000';
354+
355+
if (functionFragment === 'updateBeaconSetWithBeacons')
356+
return '0x00aae33f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002924b5d4cb3ec6366ae4302a1ca6aec035594ea3ea48a102d160b50b0c43ebfb5bf7ce55d109fd196de2a8bf1515d166c56c9decbe9cb473656bbca30d5743990';
357+
358+
return '';
359+
},
360+
},
361+
callStatic: { tryMulticall: callStaticTryMulticallMock },
362+
} as any)
363+
);
364+
365+
const groups = api.groupDataFeedsByProviderSponsor();
366+
367+
await api.updateBeacons(groups[0], Date.now());
368+
369+
expect(callStaticTryMulticallMock).toHaveBeenCalledTimes(1);
370+
expect(updateBeaconWithSignedDataMock).toHaveBeenCalledTimes(1);
371+
});
372+
373+
it('calls tryMulticall in Api3ServerV1 contract for multiple beacon updates', async () => {
374+
state.updateState((currentState) => ({
375+
...currentState,
376+
beaconValues: {
377+
'0x2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c': validSignedData,
378+
'0xa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2': validSignedData,
379+
'0x8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd': validSignedData,
380+
},
381+
}));
382+
383+
const txCountSpy = jest.spyOn(ethers.providers.StaticJsonRpcProvider.prototype, 'getTransactionCount');
384+
txCountSpy.mockResolvedValueOnce(212);
385+
386+
const timestamp = 1649664085;
387+
388+
const tryMulticallMock = jest
389+
.fn()
390+
.mockReturnValueOnce({ hash: ethers.utils.hexlify(ethers.utils.randomBytes(32)) });
391+
const callStaticTryMulticallMock = jest.fn().mockReturnValueOnce({
392+
successes: [true, true],
393+
returndata: [
394+
ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [ethers.BigNumber.from(41000000000), timestamp - 30]),
395+
ethers.utils.defaultAbiCoder.encode(['int224', 'uint32'], [ethers.BigNumber.from(39000000000), timestamp - 30]),
396+
],
397+
});
398+
jest.spyOn(Api3ServerV1Factory, 'connect').mockImplementation(
399+
(_dapiServerAddress, _provider) =>
400+
({
401+
connect(_signerOrProvider: ethers.Signer | ethers.providers.Provider | string) {
402+
return this;
403+
},
404+
tryMulticall: tryMulticallMock,
405+
interface: {
406+
encodeFunctionData: (functionFragment: string, values: [any]): string => {
407+
if (functionFragment === 'dataFeeds')
408+
return '0x67a7cfb741c3d6e0ee82ae3d33356c4dceb84e98d1a0b361db0f51081fc5a2541ae51683';
409+
410+
if (functionFragment === 'readDataFeedWithId') {
411+
switch (values[0]) {
412+
case '0x2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c':
413+
return '0xa5fc076f2ba0526238b0f2671b7981fd7a263730619c8e849a528088fd4a92350a8c2f2c';
414+
case '0xa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2':
415+
return '0xa5fc076fa5ddf304a7dcec62fa55449b7fe66b33339fd8b249db06c18423d5b0da7716c2';
416+
case '0x8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd':
417+
return '0xa5fc076f8fa9d00cb8f2d95b1299623d97a97696ed03d0e3350e4ea638f469beabcdabcd';
418+
}
419+
}
420+
421+
if (functionFragment === 'updateBeaconWithSignedData')
422+
return '0x1a0a0b3e0000000000000000000000005656d3a378b1aadfddcf4196ea364a9d786172909ec34b00a5019442dcd05a4860ff2bf015164b368cb83fcb756088fcabcdabcd000000000000000000000000000000000000000000000000000000006253e05500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000009dc41b78000000000000000000000000000000000000000000000000000000000000000418aace553ec28f53cc976c8a2469d50f16de121d248495117aca36feb4950957827570e0648f82bdbc0afa6cb69dd9fe37dc7f9d58ae3aa06450e627e06c1b8031b00000000000000000000000000000000000000000000000000000000000000';
423+
424+
if (functionFragment === 'updateBeaconSetWithBeacons')
425+
return '0x00aae33f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002924b5d4cb3ec6366ae4302a1ca6aec035594ea3ea48a102d160b50b0c43ebfb5bf7ce55d109fd196de2a8bf1515d166c56c9decbe9cb473656bbca30d5743990';
426+
427+
return '';
428+
},
429+
},
430+
callStatic: { tryMulticall: callStaticTryMulticallMock },
431+
} as any)
432+
);
433+
434+
const groups = api.groupDataFeedsByProviderSponsor();
435+
436+
await api.updateBeacons(groups[0], Date.now());
437+
438+
expect(callStaticTryMulticallMock).toHaveBeenCalledTimes(1);
439+
expect(tryMulticallMock).toHaveBeenCalledTimes(1);
440+
});
441+
});
442+
303443
describe('updateBeaconSets', () => {
304444
it('calls updateBeaconSetWithBeacons in Api3ServerV1 contract', async () => {
305445
state.updateState((currentState) => ({

src/update-data-feeds.ts

+61-26
Original file line numberDiff line numberDiff line change
@@ -238,25 +238,25 @@ export const updateBeacons = async (providerSponsorDataFeeds: ProviderSponsorDat
238238
for (const readBatch of chunk(beaconUpdates, DATAFEED_READ_BATCH_SIZE)) {
239239
// Read beacon batch onchain values
240240
const goDatafeedsTryMulticall = await go(
241-
() => {
242-
const calldatas = readBatch.map((beaconUpdate) => beaconUpdate.dataFeedsCalldata);
243-
return contract.connect(voidSigner).callStatic.tryMulticall(calldatas);
244-
},
241+
() =>
242+
contract
243+
.connect(voidSigner)
244+
.callStatic.tryMulticall(readBatch.map((beaconUpdate) => beaconUpdate.dataFeedsCalldata)),
245245
{
246246
...prepareGoOptions(startTime, totalTimeout),
247247
onAttemptError: (goError) =>
248-
logger.warn(`Failed attempt to read beacon data using multicall. Error ${goError.error}`, logOptions),
248+
logger.warn(`Attempt to read beacon data using tryMulticall has failed. Error ${goError.error}`, logOptions),
249249
}
250250
);
251251
if (!goDatafeedsTryMulticall.success) {
252-
logger.warn(`Unable to read beacon data using multicall. Error: ${goDatafeedsTryMulticall.error}`, logOptions);
252+
logger.warn(`Unable to read beacon data using tryMulticall. Error: ${goDatafeedsTryMulticall.error}`, logOptions);
253253
continue;
254254
}
255255

256256
const { successes, returndata } = goDatafeedsTryMulticall.data;
257257

258258
// Process beacon update calldatas
259-
let beaconUpdateCalldatas: string[] = [];
259+
let beaconUpdates: BeaconUpdate[] = [];
260260

261261
for (let i = 0; i < readBatch.length; i++) {
262262
const beaconReturndata = returndata[i];
@@ -286,40 +286,75 @@ export const updateBeacons = async (providerSponsorDataFeeds: ProviderSponsorDat
286286
continue;
287287
}
288288

289-
beaconUpdateCalldatas = [
290-
...beaconUpdateCalldatas,
291-
contract.interface.encodeFunctionData('updateBeaconWithSignedData', [
292-
beaconUpdateData.beacon.airnode,
293-
beaconUpdateData.beacon.templateId,
294-
beaconUpdateData.newBeaconResponse.timestamp,
295-
beaconUpdateData.newBeaconResponse.encodedValue,
296-
beaconUpdateData.newBeaconResponse.signature,
297-
]),
298-
];
289+
beaconUpdates = [...beaconUpdates, beaconUpdateData];
299290
}
300291

301292
let nonce = transactionCount;
302-
for (const updateBatch of chunk(beaconUpdateCalldatas, DATAFEED_UPDATE_BATCH_SIZE)) {
293+
for (const updateBatch of chunk(beaconUpdates, DATAFEED_UPDATE_BATCH_SIZE)) {
303294
// Get the latest gas price
304295
const getGasFn = () => getGasPrice(provider.rpcProvider.getProvider(), config.chains[chainId].options);
305296
// We have to grab the limiter from the custom provider as the getGasPrice function contains its own timeouts
306297
const [logs, gasTarget] = await provider.rpcProvider.getLimiter().schedule({ expiration: 30_000 }, getGasFn);
307298
logger.logPending(logs, logOptions);
308299

309-
// Update beacon batch onchain values
310-
const tx = await go(() => contract.connect(sponsorWallet).tryMulticall(updateBatch, { nonce, ...gasTarget }), {
311-
...prepareGoOptions(startTime, totalTimeout),
312-
onAttemptError: (goError) =>
313-
logger.warn(`Failed attempt to update beacon batch. Error ${goError.error}`, logOptions),
314-
});
300+
// Update beacon onchain values
301+
const updateBatchBeaconIds = updateBatch.map((beaconUpdate) => beaconUpdate.beaconTrigger.beaconId);
302+
logger.debug(
303+
`About to update ${updateBatch.length} beacon(s) with nonce ${nonce}. Beacon id(s): ${updateBatchBeaconIds.join(
304+
', '
305+
)}`,
306+
logOptions
307+
);
308+
309+
const tx = await go(
310+
updateBatch.length === 1
311+
? () =>
312+
contract
313+
.connect(sponsorWallet)
314+
.updateBeaconWithSignedData(
315+
beaconUpdates[0].beacon.airnode,
316+
beaconUpdates[0].beacon.templateId,
317+
beaconUpdates[0].newBeaconResponse.timestamp,
318+
beaconUpdates[0].newBeaconResponse.encodedValue,
319+
beaconUpdates[0].newBeaconResponse.signature,
320+
{ nonce, ...gasTarget }
321+
)
322+
: () => {
323+
return contract.connect(sponsorWallet).tryMulticall(
324+
updateBatch.map((beaconUpdateData) =>
325+
contract.interface.encodeFunctionData('updateBeaconWithSignedData', [
326+
beaconUpdateData.beacon.airnode,
327+
beaconUpdateData.beacon.templateId,
328+
beaconUpdateData.newBeaconResponse.timestamp,
329+
beaconUpdateData.newBeaconResponse.encodedValue,
330+
beaconUpdateData.newBeaconResponse.signature,
331+
])
332+
),
333+
{ nonce, ...gasTarget }
334+
);
335+
},
336+
{
337+
...prepareGoOptions(startTime, totalTimeout),
338+
onAttemptError: (goError) =>
339+
logger.warn(
340+
`Attempt to send transaction to update ${updateBatch.length} beacon(s) has failed. Error ${goError.error}`,
341+
logOptions
342+
),
343+
}
344+
);
315345
if (!tx.success) {
316-
logger.warn(`Unable send beacon batch update transaction with nonce ${nonce}. Error: ${tx.error}`, logOptions);
346+
logger.warn(
347+
`Unable send transaction to update ${updateBatch.length} beacon(s) with nonce ${nonce}. Error: ${tx.error}`,
348+
logOptions
349+
);
350+
logger.debug(`Beacon id(s) that failed to be updated: ${updateBatchBeaconIds.join(', ')}`, logOptions);
317351
return;
318352
}
319353
logger.info(
320-
`Beacon batch update transaction was successfully sent with nonce ${nonce}. Tx hash ${tx.data.hash}.`,
354+
`Transaction to update ${updateBatch.length} beacon(s) was successfully sent with nonce ${nonce}. Tx hash ${tx.data.hash}`,
321355
logOptions
322356
);
357+
323358
nonce++;
324359
}
325360
}

0 commit comments

Comments
 (0)