diff --git a/src/clients/mdw-http-client.service.ts b/src/clients/mdw-http-client.service.ts index 27bf705..54d6348 100644 --- a/src/clients/mdw-http-client.service.ts +++ b/src/clients/mdw-http-client.service.ts @@ -58,7 +58,7 @@ export class MdwHttpClientService { getAccountBalanceForContractAtMicroBlockHash( contractAddress: ContractAddress, accountAddress: AccountAddress, - microBlockHash: string, + microBlockHash: MicroBlockHash, ): Promise { return this.get( `/v2/aex9/${contractAddress}/balances/${accountAddress}?hash=${microBlockHash}&${this.defaultParams}`, diff --git a/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.ts b/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.ts index 0497332..f117bed 100644 --- a/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.ts +++ b/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.ts @@ -224,7 +224,7 @@ export class PairLiquidityInfoHistoryImporterService { const pairBalances = await this.mdwClient.getContractBalancesAtMicroBlockHash( pairWithTokens.address as ContractAddress, - block.hash, + block.hash as MicroBlockHash, ); const totalSupply = pairBalances .map((contractBalance) => BigInt(contractBalance.amount)) @@ -235,7 +235,7 @@ export class PairLiquidityInfoHistoryImporterService { await this.mdwClient.getAccountBalanceForContractAtMicroBlockHash( pairWithTokens.token0.address as ContractAddress, contractAddrToAccountAddr(pairWithTokens.address as ContractAddress), - block.hash, + block.hash as MicroBlockHash, ) ).amount; @@ -244,7 +244,7 @@ export class PairLiquidityInfoHistoryImporterService { await this.mdwClient.getAccountBalanceForContractAtMicroBlockHash( pairWithTokens.token1.address as ContractAddress, contractAddrToAccountAddr(pairWithTokens.address as ContractAddress), - block.hash, + block.hash as MicroBlockHash, ) ).amount; diff --git a/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator-v2.service.spec.ts b/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator-v2.service.spec.ts new file mode 100644 index 0000000..742a352 --- /dev/null +++ b/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator-v2.service.spec.ts @@ -0,0 +1,263 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { MdwHttpClientService } from '@/clients/mdw-http-client.service'; +import { SdkClientService } from '@/clients/sdk-client.service'; +import { PairLiquidityInfoHistoryV2DbService } from '@/database/pair-liquidity-info-history/pair-liquidity-info-history-v2-db.service'; +import { PairLiquidityInfoHistoryValidatorV2Service } from '@/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator-v2.service'; +import { + historyEntry1, + historyEntry2, + historyEntry3, + historyEntry4, + pairWithTokens, +} from '@/test/mock-data/pair-liquidity-info-history-mock-data'; +import resetAllMocks = jest.resetAllMocks; +import { + AccountAddress, + ContractAddress, + MicroBlockHash, +} from '@/clients/sdk-client.model'; +import { PairDbService } from '@/database/pair/pair-db.service'; + +const mockPairLiquidityInfoHistoryV2Db = { + getWithinHeightSorted: jest.fn(), + deleteFromMicroBlockTime: jest.fn(), +}; + +const pairDbMock = { + get: jest.fn(), +}; + +const mockMdwClient = { + getAccountBalanceForContractAtMicroBlockHash: jest.fn(), +}; + +const mockSdkClient = { + getHeight: jest.fn(), +}; + +describe('PairLiquidityInfoHistoryValidatorV2Service', () => { + let service: PairLiquidityInfoHistoryValidatorV2Service; + let logSpy: jest.SpyInstance; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + PairLiquidityInfoHistoryValidatorV2Service, + SdkClientService, + { + provide: PairLiquidityInfoHistoryV2DbService, + useValue: mockPairLiquidityInfoHistoryV2Db, + }, + { provide: PairDbService, useValue: pairDbMock }, + { provide: MdwHttpClientService, useValue: mockMdwClient }, + { provide: SdkClientService, useValue: mockSdkClient }, + ], + }).compile(); + service = module.get( + PairLiquidityInfoHistoryValidatorV2Service, + ); + logSpy = jest.spyOn(service.logger, 'log'); + resetAllMocks(); + }); + + describe('validate', () => { + it('should not delete entries if the mdw data matches with the last element of a local micro block', async () => { + // Mock functions + mockSdkClient.getHeight.mockResolvedValue(90000); + mockPairLiquidityInfoHistoryV2Db.getWithinHeightSorted.mockResolvedValue([ + historyEntry2, + historyEntry3, + historyEntry4, + ]); + + pairDbMock.get.mockResolvedValue(pairWithTokens); + + mockMdwClient.getAccountBalanceForContractAtMicroBlockHash.mockImplementation( + ( + contractAddress: ContractAddress, + accountAddress: AccountAddress, + microBlockHash: MicroBlockHash, + ) => { + if (microBlockHash === historyEntry2.microBlockHash) { + if (contractAddress === pairWithTokens.token0.address) { + return { + amount: historyEntry2.reserve0, + }; + } else { + return { + amount: historyEntry2.reserve1, + }; + } + } else if (microBlockHash === historyEntry4.microBlockHash) { + if (contractAddress === pairWithTokens.token0.address) { + return { + amount: historyEntry4.reserve0, + }; + } else { + return { + amount: historyEntry4.reserve1, + }; + } + } else { + return {}; + } + }, + ); + + // Start validation + await service.validate(); + + // Assertions + expect( + mockPairLiquidityInfoHistoryV2Db.getWithinHeightSorted, + ).toHaveBeenCalledWith(89980); // Current height - VALIDATION_WINDOW_BLOCKS + expect(pairDbMock.get).toHaveBeenCalledTimes(2); + + expect( + mockMdwClient.getAccountBalanceForContractAtMicroBlockHash, + ).toHaveBeenCalledTimes(4); + + expect( + mockPairLiquidityInfoHistoryV2Db.deleteFromMicroBlockTime, + ).toHaveBeenCalledTimes(0); + expect(logSpy.mock.calls).toEqual([ + ['Started validating pair liquidity info history.'], + ['No problems in pair liquidity info history found.'], + ['Finished validating pair liquidity info history.'], + ]); + }); + + it("should delete entries if the mdw data doesn't match with the local data", async () => { + // Mock functions + mockSdkClient.getHeight.mockResolvedValue(90000); + mockPairLiquidityInfoHistoryV2Db.getWithinHeightSorted.mockResolvedValue([ + historyEntry1, + historyEntry2, + historyEntry3, + historyEntry4, + ]); + + pairDbMock.get.mockResolvedValue(pairWithTokens); + + mockMdwClient.getAccountBalanceForContractAtMicroBlockHash.mockImplementation( + ( + contractAddress: ContractAddress, + accountAddress: AccountAddress, + microBlockHash: MicroBlockHash, + ) => { + if (microBlockHash === historyEntry1.microBlockHash) { + if (contractAddress === pairWithTokens.token0.address) { + return { + amount: historyEntry1.reserve0, + }; + } else { + return { + amount: historyEntry1.reserve1, + }; + } + } else if (microBlockHash === historyEntry2.microBlockHash) { + if (contractAddress === pairWithTokens.token0.address) { + return { + amount: '123', + }; + } else { + return { + amount: '123', + }; + } + } else { + return {}; + } + }, + ); + + mockPairLiquidityInfoHistoryV2Db.deleteFromMicroBlockTime.mockResolvedValue( + { count: 3 }, + ); + + // Start validation + await service.validate(); + + // Assertions + expect(pairDbMock.get).toHaveBeenCalledTimes(2); + + expect( + mockMdwClient.getAccountBalanceForContractAtMicroBlockHash, + ).toHaveBeenCalledTimes(4); + + expect( + mockPairLiquidityInfoHistoryV2Db.deleteFromMicroBlockTime, + ).toHaveBeenCalledWith(historyEntry2.microBlockTime); + expect(logSpy.mock.calls).toEqual([ + ['Started validating pair liquidity info history.'], + [ + 'Found an inconsistency in pair liquidity info history. Deleted 3 entries.', + ], + ['Finished validating pair liquidity info history.'], + ]); + }); + + it('should delete entries if mdw return an error', async () => { + // Mock functions + mockSdkClient.getHeight.mockResolvedValue(90000); + mockPairLiquidityInfoHistoryV2Db.getWithinHeightSorted.mockResolvedValue([ + historyEntry1, + historyEntry2, + historyEntry3, + historyEntry4, + ]); + + pairDbMock.get.mockResolvedValue(pairWithTokens); + + mockMdwClient.getAccountBalanceForContractAtMicroBlockHash.mockImplementation( + ( + contractAddress: ContractAddress, + accountAddress: AccountAddress, + microBlockHash: MicroBlockHash, + ) => { + if (microBlockHash === historyEntry1.microBlockHash) { + if (contractAddress === pairWithTokens.token0.address) { + return { + amount: historyEntry1.reserve0, + }; + } else { + return { + amount: historyEntry1.reserve1, + }; + } + } else if (microBlockHash === historyEntry2.microBlockHash) { + if (contractAddress === pairWithTokens.token0.address) { + throw new Error('mdw error'); + } + } + }, + ); + + mockPairLiquidityInfoHistoryV2Db.deleteFromMicroBlockTime.mockResolvedValue( + { count: 3 }, + ); + + // Start validation + await service.validate(); + + // Assertions + expect(pairDbMock.get).toHaveBeenCalledTimes(2); + + expect( + mockMdwClient.getAccountBalanceForContractAtMicroBlockHash, + ).toHaveBeenCalledTimes(3); + + expect( + mockPairLiquidityInfoHistoryV2Db.deleteFromMicroBlockTime, + ).toHaveBeenCalledWith(historyEntry2.microBlockTime); + expect(logSpy.mock.calls).toEqual([ + ['Started validating pair liquidity info history.'], + [ + 'Found an inconsistency in pair liquidity info history. Deleted 3 entries.', + ], + ['Finished validating pair liquidity info history.'], + ]); + }); + }); +}); diff --git a/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator-v2.service.ts b/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator-v2.service.ts index 8eac332..679307a 100644 --- a/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator-v2.service.ts +++ b/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator-v2.service.ts @@ -5,6 +5,7 @@ import { MdwHttpClientService } from '@/clients/mdw-http-client.service'; import { ContractAddress, contractAddrToAccountAddr, + MicroBlockHash, } from '@/clients/sdk-client.model'; import { SdkClientService } from '@/clients/sdk-client.service'; import { PairDbService } from '@/database/pair/pair-db.service'; @@ -59,7 +60,7 @@ export class PairLiquidityInfoHistoryValidatorV2Service { contractAddrToAccountAddr( liquidityEntry.pair.address as ContractAddress, ), - liquidityEntry.microBlockHash, + liquidityEntry.microBlockHash as MicroBlockHash, ) ).amount, ); @@ -72,7 +73,7 @@ export class PairLiquidityInfoHistoryValidatorV2Service { contractAddrToAccountAddr( liquidityEntry.pair.address as ContractAddress, ), - liquidityEntry.microBlockHash, + liquidityEntry.microBlockHash as MicroBlockHash, ) ).amount, ); diff --git a/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator.service.spec.ts b/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator.service.spec.ts index c2bf74b..1597ae1 100644 --- a/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator.service.spec.ts +++ b/src/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator.service.spec.ts @@ -5,7 +5,7 @@ import { SdkClientService } from '@/clients/sdk-client.service'; import { PairLiquidityInfoHistoryDbService } from '@/database/pair-liquidity-info-history/pair-liquidity-info-history-db.service'; import { PairLiquidityInfoHistoryValidatorService } from '@/tasks/pair-liquidity-info-history-validator/pair-liquidity-info-history-validator.service'; -const mockMdwClientService = { +const mockMdwClient = { getKeyBlockMicroBlocks: jest.fn(), }; @@ -22,7 +22,7 @@ describe('PairLiquidityInfoHistoryValidatorService', () => { providers: [ PairLiquidityInfoHistoryValidatorService, SdkClientService, - { provide: MdwHttpClientService, useValue: mockMdwClientService }, + { provide: MdwHttpClientService, useValue: mockMdwClient }, { provide: PairLiquidityInfoHistoryDbService, useValue: mockPairLiquidityInfoHistoryDb, @@ -69,7 +69,7 @@ describe('PairLiquidityInfoHistoryValidatorService', () => { historyEntry3, historyEntry4, ]); - mockMdwClientService.getKeyBlockMicroBlocks.mockImplementation( + mockMdwClient.getKeyBlockMicroBlocks.mockImplementation( (height: number) => { if (height === historyEntry1.height) { return [ @@ -93,18 +93,18 @@ describe('PairLiquidityInfoHistoryValidatorService', () => { await service.validate(); // Assertions - expect(mockMdwClientService.getKeyBlockMicroBlocks).toHaveBeenCalledWith( + expect(mockMdwClient.getKeyBlockMicroBlocks).toHaveBeenCalledWith( historyEntry1.height, ); - expect(mockMdwClientService.getKeyBlockMicroBlocks).toHaveBeenCalledWith( + expect(mockMdwClient.getKeyBlockMicroBlocks).toHaveBeenCalledWith( historyEntry3.height, ); - expect(mockMdwClientService.getKeyBlockMicroBlocks).toHaveBeenCalledWith( + expect(mockMdwClient.getKeyBlockMicroBlocks).toHaveBeenCalledWith( historyEntry4.height, ); - expect( - mockMdwClientService.getKeyBlockMicroBlocks, - ).not.toHaveBeenCalledWith(historyEntry5.height); + expect(mockMdwClient.getKeyBlockMicroBlocks).not.toHaveBeenCalledWith( + historyEntry5.height, + ); expect( mockPairLiquidityInfoHistoryDb.deleteFromMicroBlockTime, ).toHaveBeenCalledWith(historyEntry4.microBlockTime); diff --git a/test/mock-data/pair-liquidity-info-history-mock-data.ts b/test/mock-data/pair-liquidity-info-history-mock-data.ts index f9ebdee..0524c8b 100644 --- a/test/mock-data/pair-liquidity-info-history-mock-data.ts +++ b/test/mock-data/pair-liquidity-info-history-mock-data.ts @@ -129,9 +129,9 @@ export const historyEntry4: PairLiquidityInfoHistoryV2 = { deltaReserve1: new Decimal(-50), fiatPrice: new Decimal(0), height: 300003, - microBlockHash: 'mh_entry4', + microBlockHash: 'mh_entry3', microBlockTime: 3000000000003n, - transactionHash: 'th_entry4', + transactionHash: 'th_entry3', transactionIndex: 300003n, logIndex: 2, createdAt: new Date(),