Skip to content

Commit

Permalink
Fix tests and CI
Browse files Browse the repository at this point in the history
  • Loading branch information
Siegrift committed Oct 31, 2023
1 parent e58cbf4 commit 72d4571
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 45 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ jobs:
cache: 'pnpm'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Compile contracts
run: pnpm run contracts:compile
- name: Start Hardhat
run: pnpm dev:eth-node&
- name: Test E2E
Expand Down
13 changes: 9 additions & 4 deletions src/update-feeds/dapi-data-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const getDapiDataRegistry = (address: string, provider: ethers.providers.
export const verifyMulticallResponse = (
response: Awaited<ReturnType<DapiDataRegistry['callStatic']['tryMulticall']>>
) => {
const [successes, returndata] = response;
const { successes, returndata } = response;

if (!successes.every(Boolean)) throw new Error('One of the multicalls failed');
return returndata;
Expand All @@ -19,7 +19,6 @@ export const decodeDapisCountResponse = (dapiDataRegistry: DapiDataRegistry, dap
const dapisCount = dapiDataRegistry.interface.decodeFunctionResult('dapisCount', dapisCountReturndata)[0] as Awaited<
ReturnType<DapiDataRegistry['dapisCount']>
>;

return dapisCount.toNumber();
};

Expand All @@ -36,10 +35,16 @@ export const decodeReadDapiWithIndexResponse = (

// Ethers responses are returned as a combination of array and object. When such object is logged, only the array part
// is logged. To make the logs more readable, we convert the object part to a plain object.
const { deviationReference, deviationThresholdInPercentage, heartbeatInterval } = updateParameters;
const { value, timestamp } = dataFeedValue;
return {
dapiName,
updateParameters,
dataFeedValue,
updateParameters: {
deviationReference,
deviationThresholdInPercentage,
heartbeatInterval,
},
dataFeedValue: { value, timestamp },
dataFeed,
signedApiUrls,
};
Expand Down
66 changes: 39 additions & 27 deletions src/update-feeds/update-feeds.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ethers } from 'ethers';

import { generateMockDapiDataRegistry, generateReadDapisResponse } from '../../test/fixtures/dapi-data-registry';
import {
generateMockDapiDataRegistry,
generateReadDapiWithIndexResponse,
} from '../../test/fixtures/dapi-data-registry';
import { allowPartial } from '../../test/utils';
import type { DapiDataRegistry } from '../../typechain-types';
import type { Chain } from '../config/schema';
Expand Down Expand Up @@ -38,7 +41,7 @@ describe(startUpdateFeedLoops.name, () => {

// Expect the intervals to be called with the correct stagger time.
expect(setInterval).toHaveBeenCalledTimes(2);
expect(intervalCalls[1]! - intervalCalls[0]!).toBeGreaterThanOrEqual(40); // Reserving 10s as the buffer for computing stagger time.
expect(intervalCalls[1]! - intervalCalls[0]!).toBeGreaterThanOrEqual(40); // Reserving 10ms as the buffer for computing stagger time.

// Expect the logs to be called with the correct context.
expect(logger.debug).toHaveBeenCalledTimes(3);
Expand Down Expand Up @@ -92,21 +95,21 @@ describe(startUpdateFeedLoops.name, () => {

// Expect the logs to be called with the correct context.
expect(logger.debug).toHaveBeenCalledTimes(4);
expect(logger.debug).toHaveBeenCalledWith('Starting update loops for chain', {
expect(logger.debug).toHaveBeenNthCalledWith(1, 'Starting update loops for chain', {
chainId: '123',
staggerTime: 100,
providerNames: ['first-provider'],
});
expect(logger.debug).toHaveBeenCalledWith('Starting update loops for chain', {
expect(logger.debug).toHaveBeenNthCalledWith(2, 'Starting update feed loop', {
chainId: '123',
providerName: 'first-provider',
});
expect(logger.debug).toHaveBeenNthCalledWith(3, 'Starting update loops for chain', {
chainId: '456',
staggerTime: 100,
providerNames: ['another-provider'],
});
expect(logger.debug).toHaveBeenCalledWith('Starting update feed loop', {
chainId: '123',
providerName: 'first-provider',
});
expect(logger.debug).toHaveBeenCalledWith('Starting update feed loop', {
expect(logger.debug).toHaveBeenNthCalledWith(4, 'Starting update feed loop', {
chainId: '456',
providerName: 'another-provider',
});
Expand All @@ -119,7 +122,7 @@ describe(runUpdateFeed.name, () => {
jest
.spyOn(dapiDataRegistryModule, 'getDapiDataRegistry')
.mockReturnValue(dapiDataRegistry as unknown as DapiDataRegistry);
dapiDataRegistry.readDapis.mockRejectedValueOnce(new Error('provider-error'));
dapiDataRegistry.callStatic.tryMulticall.mockRejectedValueOnce(new Error('provider-error'));
jest.spyOn(logger, 'error');

await runUpdateFeed(
Expand All @@ -145,16 +148,19 @@ describe(runUpdateFeed.name, () => {

it('fetches other batches in a staggered way and logs errors', async () => {
// Prepare the mocked contract so it returns three batches (of size 1) of dAPIs and the second batch fails to load.
const firstBatch = generateReadDapisResponse();
const thirdBatch = generateReadDapisResponse();
const firstBatch = generateReadDapiWithIndexResponse();
const thirdBatch = generateReadDapiWithIndexResponse();
const dapiDataRegistry = generateMockDapiDataRegistry();
jest
.spyOn(dapiDataRegistryModule, 'getDapiDataRegistry')
.mockReturnValue(dapiDataRegistry as unknown as DapiDataRegistry);
dapiDataRegistry.readDapis.mockResolvedValueOnce(firstBatch);
dapiDataRegistry.readDapis.mockRejectedValueOnce(new Error('provider-error'));
dapiDataRegistry.readDapis.mockResolvedValueOnce(thirdBatch);
dapiDataRegistry.dapisCount.mockResolvedValueOnce(ethers.BigNumber.from(3));
dapiDataRegistry.interface.decodeFunctionResult.mockImplementation((_fn, value) => value);
dapiDataRegistry.callStatic.tryMulticall.mockResolvedValueOnce({
successes: [true, true],
returndata: [[ethers.BigNumber.from(3)], firstBatch],
});
dapiDataRegistry.callStatic.tryMulticall.mockResolvedValueOnce({ successes: [false], returndata: [] });
dapiDataRegistry.callStatic.tryMulticall.mockResolvedValueOnce({ successes: [true], returndata: [thirdBatch] });
const sleepCalls = [] as number[];
const originalSleep = utilsModule.sleep;
jest.spyOn(utilsModule, 'sleep').mockImplementation(async (ms) => {
Expand All @@ -179,36 +185,42 @@ describe(runUpdateFeed.name, () => {

// Expect the contract to fetch the batches to be called with the correct stagger time.
expect(utilsModule.sleep).toHaveBeenCalledTimes(3);
expect(sleepCalls[0]).toBeGreaterThanOrEqual(40); // Reserving 10s as the buffer for computing stagger time.
expect(sleepCalls[0]).toBeGreaterThanOrEqual(40); // Reserving 10ms as the buffer for computing stagger time.
expect(sleepCalls[1]).toBeGreaterThanOrEqual(0);
expect(sleepCalls[2]).toBe(49.999_999_999_999_99); // Stagger time is actually 150 / 3 = 50, but there is an rounding error.

// Expect the logs to be called with the correct context.
expect(logger.error).toHaveBeenCalledTimes(1);
expect(logger.error).toHaveBeenCalledWith('Failed to get active dAPIs batch', new Error('provider-error'), {
chainId: '123',
providerName: 'provider-name',
});
expect(logger.debug).toHaveBeenCalledTimes(4);
expect(logger.debug).toHaveBeenCalledWith('Fetching first batch of dAPIs batches', {
expect(logger.error).toHaveBeenCalledWith(
'Failed to get active dAPIs batch',
new Error('One of the multicalls failed'),
{
chainId: '123',
providerName: 'provider-name',
}
);
expect(logger.debug).toHaveBeenCalledTimes(6);
expect(logger.debug).toHaveBeenNthCalledWith(1, 'Fetching first batch of dAPIs batches', {
chainId: '123',
providerName: 'provider-name',
});
expect(logger.debug).toHaveBeenCalledWith('Fetching batches of active dAPIs', {
expect(logger.debug).toHaveBeenNthCalledWith(2, 'Processing batch of active dAPIs', expect.anything());
expect(logger.debug).toHaveBeenNthCalledWith(3, 'Fetching batches of active dAPIs', {
batchesCount: 3,
staggerTime: 49.999_999_999_999_99,
chainId: '123',
providerName: 'provider-name',
staggerTime: 49.999_999_999_999_99,
});
expect(logger.debug).toHaveBeenCalledWith('Fetching batch of active dAPIs', {
expect(logger.debug).toHaveBeenNthCalledWith(4, 'Fetching batch of active dAPIs', {
batchIndex: 1,
chainId: '123',
providerName: 'provider-name',
});
expect(logger.debug).toHaveBeenCalledWith('Fetching batch of active dAPIs', {
expect(logger.debug).toHaveBeenNthCalledWith(5, 'Fetching batch of active dAPIs', {
batchIndex: 2,
chainId: '123',
providerName: 'provider-name',
});
expect(logger.debug).toHaveBeenNthCalledWith(6, 'Processing batch of active dAPIs', expect.anything());
});
});
40 changes: 27 additions & 13 deletions test/fixtures/dapi-data-registry.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
import { ethers } from 'ethers';

import type { ReadDapiWithIndexResponse } from '../../src/update-feeds/dapi-data-registry';
import type { DapiDataRegistry } from '../../typechain-types';
import type { DeepPartial } from '../utils';

export const generateReadDapisResponse = () => [
{
totalCount: 1,
dapiNames: ['MOCK_FEED'],
dataFeedIds: ['0xebba8507d616ed80766292d200a3598fdba656d9938cecc392765d4a284a69a4'],
updateParameters: [{ deviationThresholdInPercentage: 0.5, deviationReference: 0.5, heartbeatInterval: 100 }],
// NOTE: We will need to decode this from the contract, because it will store the template IDs as encoded bytes.
dataFeedTemplateIds: [['0xcc35bd1800c06c12856a87311dd95bfcbb3add875844021d59a929d79f3c99bd']],
signedApiUrls: [['http://localhost:8080']],
airnodeAddresses: ['0xbF3137b0a7574563a23a8fC8badC6537F98197CC'],
export const generateReadDapiWithIndexResponse = (): ReadDapiWithIndexResponse => ({
dapiName: 'MOCK_FEED',
updateParameters: {
deviationThresholdInPercentage: ethers.BigNumber.from(0.5 * 1e8),
deviationReference: ethers.BigNumber.from(0.5 * 1e8),
heartbeatInterval: 100,
},
dataFeedValue: {
value: ethers.BigNumber.from(123 * 1e6),
timestamp: 1_629_811_200,
},
];
dataFeed: '0xebba8507d616ed80766292d200a3598fdba656d9938cecc392765d4a284a69a4',
signedApiUrls: ['http://localhost:8080'],
});

export const generateMockDapiDataRegistry = () => {
return {
readDapis: jest.fn(),
interface: {
encodeFunctionData: jest.fn(),
decodeFunctionResult: jest.fn(),
},
callStatic: {
tryMulticall: jest.fn(),
},
tryMulticall: jest.fn(),
readDapiWithIndex: jest.fn(),
dapisCount: jest.fn(),
} satisfies Partial<DapiDataRegistry>;
} satisfies DeepPartial<DapiDataRegistry>;
};
2 changes: 1 addition & 1 deletion test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const signData = async (signer: ethers.Signer, templateId: string, timest

export const generateRandomBytes32 = () => ethers.utils.hexlify(ethers.utils.randomBytes(32));

type DeepPartial<T> = T extends object
export type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>;
}
Expand Down

0 comments on commit 72d4571

Please sign in to comment.