diff --git a/packages/bridge-ui/jest.config.js b/packages/bridge-ui/jest.config.js index f13e75b6cd..041f3f7d19 100644 --- a/packages/bridge-ui/jest.config.js +++ b/packages/bridge-ui/jest.config.js @@ -45,10 +45,10 @@ export default { // - relayer-api/RelayerAPIService (partial test coverage) // - bridge/ERC20Bridge (partial test coverage) // - bridge/ETHBridge (partial test coverage) - statements: 89, - branches: 86, - functions: 91, - lines: 89, + statements: 93, + branches: 92, + functions: 97, + lines: 93, }, }, modulePathIgnorePatterns: ['/public/build/'], diff --git a/packages/bridge-ui/src/domain/provider.ts b/packages/bridge-ui/src/domain/provider.ts index c3748f7640..791351625a 100644 --- a/packages/bridge-ui/src/domain/provider.ts +++ b/packages/bridge-ui/src/domain/provider.ts @@ -2,4 +2,4 @@ import type { providers } from 'ethers'; import type { ChainID } from './chain'; -export type RecordProviders = Record; +export type ProvidersRecord = Record; diff --git a/packages/bridge-ui/src/relayer-api/RelayerAPIService.spec.ts b/packages/bridge-ui/src/relayer-api/RelayerAPIService.spec.ts index b0a4f50824..8e11b01dda 100644 --- a/packages/bridge-ui/src/relayer-api/RelayerAPIService.spec.ts +++ b/packages/bridge-ui/src/relayer-api/RelayerAPIService.spec.ts @@ -1,131 +1,103 @@ +import type { Address } from '@wagmi/core'; import axios from 'axios'; +import type { ethers } from 'ethers'; -import { RELAYER_URL } from '../constants/envVars'; -import type { APIResponse, RelayerBlockInfo } from '../domain/relayerApi'; -import { providers } from '../provider/providers'; +import { L1_CHAIN_ID, L2_CHAIN_ID, RELAYER_URL } from '../constants/envVars'; +import { MessageStatus } from '../domain/message'; +import type { ProvidersRecord } from '../domain/provider'; +import blockInfoJson from './__fixtures__/blockInfo.json'; +import eventsJson from './__fixtures__/events.json'; import { RelayerAPIService } from './RelayerAPIService'; jest.mock('axios'); jest.mock('../constants/envVars'); +jest.mock('../provider/providers'); -const dataFromAPI = { - items: [ - { - id: 1, - name: 'MessageSent', - data: { - Message: { - Id: 1, - To: '0x123', - Owner: '0x123', - Sender: '0x123', - RefundAddress: '0x123', - Data: '0x123', - SrcChainId: 1, - DestChainId: 2, - Memo: '', - GasLimit: '1', - CallValue: '1', - DepositValue: '1', - ProcessingFee: '1', - }, - Raw: { - transactionHash: '0x123', - }, - }, - status: 0, - eventType: 0, - chainID: 1, - amount: '1000000000000000000', - canonicalTokenSymbol: 'ETH', - messageOwner: '0x123', - msgHash: '0x123', - canonicalTokenAddress: '0x123', - canonicalTokenName: 'ETH', - canonicalTokenDecimals: 18, - event: 'MessageSent', - }, - { - id: 2, - name: 'MessageSent', - data: { - Message: { - Id: 2, - To: '0x456', - Owner: '0x456', - Sender: '0x456', - RefundAddress: '0x456', - Data: '0x456', - SrcChainId: 2, - DestChainId: 1, - Memo: '', - GasLimit: '1', - CallValue: '1', - DepositValue: '1', - ProcessingFee: '1', - }, - Raw: { - transactionHash: '0x456', - }, - }, - status: 1, - eventType: 0, - chainID: 2, - amount: '2000000000000000000', - canonicalTokenSymbol: 'BLL', - messageOwner: '0x456', - msgHash: '0x456', - canonicalTokenAddress: '0x456', - canonicalTokenName: 'BLL', - canonicalTokenDecimals: 18, - event: 'MessageSent', - }, - ], - page: 0, - size: 100, - max_page: 1, - total_pages: 1, - total: 3, - last: false, - first: true, - visible: 3, -} as APIResponse; - -const blockInfoFromAPI = [ - { - chainID: 1, - latestProcessedBlock: 2, - latestBlock: 3, +const mockContract = { + queryFilter: jest.fn(), + getMessageStatus: jest.fn(), + symbol: jest.fn(), + filters: { + ERC20Sent: () => jest.fn().mockReturnValue('ERC20Sent'), }, - { - chainID: 2, - latestProcessedBlock: 4, - latestBlock: 5, +}; + +jest.mock('ethers', () => ({ + ...jest.requireActual('ethers'), + Contract: function () { + return mockContract; }, -] as RelayerBlockInfo[]; +})); + +const walletAddress: Address = '0x33C887d229B5b99cdfa06B02102f8F75411C56B8'; + +const mockProviders = { + [L1_CHAIN_ID]: { + getTransactionReceipt: jest.fn(), + }, + [L2_CHAIN_ID]: { + getTransactionReceipt: jest.fn(), + }, +} as unknown as ProvidersRecord; + +const mockTxReceipt = { + blockNumber: 1, +} as ethers.providers.TransactionReceipt; + +const mockErc20Event = { + args: { + token: '0x123', + amount: '100', + msgHash: + '0x289d8464b91c2f31f6170942f29cdb815148dc527fbbbcd5ff158c0f5b9ac766', + message: { + owner: walletAddress, + data: '0x789', + }, + }, +}; + +const mockErc20Query = [mockErc20Event]; const baseUrl = RELAYER_URL.replace(/\/$/, ''); -const relayerApi = new RelayerAPIService(RELAYER_URL, providers); + +const relayerApi = new RelayerAPIService(RELAYER_URL, mockProviders); describe('RelayerAPIService', () => { + beforeEach(() => { + jest.clearAllMocks(); + + jest + .mocked(mockProviders[L1_CHAIN_ID].getTransactionReceipt) + .mockResolvedValue(mockTxReceipt); + + jest + .mocked(mockProviders[L2_CHAIN_ID].getTransactionReceipt) + .mockResolvedValue(mockTxReceipt); + + mockContract.getMessageStatus.mockResolvedValue(MessageStatus.New); + mockContract.queryFilter.mockResolvedValue(mockErc20Query); + mockContract.symbol.mockResolvedValue('BLL'); + }); + it('should get transactions from API', async () => { jest.mocked(axios.get).mockResolvedValueOnce({ - data: dataFromAPI, + data: eventsJson, }); const data = await relayerApi.getTransactionsFromAPI({ - address: '0x123', + address: walletAddress, chainID: 1, event: 'MessageSent', }); // Test parameters expect(axios.get).toHaveBeenCalledWith(`${baseUrl}/events`, { - params: { address: '0x123', chainID: 1, event: 'MessageSent' }, + params: { address: walletAddress, chainID: 1, event: 'MessageSent' }, }); // Test return value - expect(data).toEqual(dataFromAPI); + expect(data.items.length).toEqual(eventsJson.items.length); }); it('cannot get transactions from API', async () => { @@ -133,35 +105,168 @@ describe('RelayerAPIService', () => { await expect( relayerApi.getTransactionsFromAPI({ - address: '0x123', + address: walletAddress, chainID: 1, event: 'MessageSent', }), ).rejects.toThrowError('could not fetch transactions from API'); }); - // TODO: finish this test - it('should get all bridge transaction by address', async () => { - axios.get = jest.fn().mockResolvedValueOnce({ - data: dataFromAPI, - }); - - await relayerApi.getAllBridgeTransactionByAddress('0x123', { + it('should get empty list of transactions', async () => { + const mockPaginationInfo = { page: 0, size: 100, + max_page: 1, + total_pages: 1, + total: 0, + last: false, + first: true, + }; + + jest.mocked(axios.get).mockResolvedValueOnce({ + data: { + items: [], + ...mockPaginationInfo, + visible: 0, + }, }); + const { txs, paginationInfo } = + await relayerApi.getAllBridgeTransactionByAddress(walletAddress, { + page: 0, + size: 100, + }); + + expect(txs).toEqual([]); + expect(paginationInfo).toEqual(mockPaginationInfo); + }); + + it('should get filtered bridge transactions by address', async () => { + jest.mocked(axios.get).mockResolvedValueOnce({ + data: eventsJson, + }); + + const { txs } = await relayerApi.getAllBridgeTransactionByAddress( + walletAddress, + { + page: 0, + size: 100, + }, + ); + // Test parameters expect(axios.get).toHaveBeenCalledWith(`${baseUrl}/events`, { - params: { address: '0x123', event: 'MessageSent', page: 0, size: 100 }, + params: { + address: walletAddress, + event: 'MessageSent', + page: 0, + size: 100, + }, }); + + // There are transactions with duplicate transactionHash. + // We are expecting here less bridge txs than what we + // have in the fixture. + expect(txs.length).toBeLessThan(eventsJson.items.length); }); - it('should get block info', async () => { + it('should get only L2 => L1 transactions by address', async () => { jest.mocked(axios.get).mockResolvedValueOnce({ - data: { - data: blockInfoFromAPI, + data: eventsJson, + }); + + // Transactions with no receipt are not included + jest + .mocked(mockProviders[L1_CHAIN_ID].getTransactionReceipt) + .mockResolvedValue(null); + + const { txs } = await relayerApi.getAllBridgeTransactionByAddress( + walletAddress, + { + page: 0, + size: 100, }, + ); + + expect(txs.length).toBeGreaterThanOrEqual(1); + + const chainIds = txs.map((tx) => tx.message.srcChainId); + expect(chainIds).not.toContain(L1_CHAIN_ID); + }); + + it('should get all transactions by address', async () => { + jest.mocked(axios.get).mockResolvedValueOnce({ + data: eventsJson, + }); + + const { txs } = await relayerApi.getAllBridgeTransactionByAddress( + walletAddress, + { + page: 0, + size: 100, + }, + ); + + expect( + mockProviders[L1_CHAIN_ID].getTransactionReceipt, + ).toHaveBeenCalledTimes(3); + + expect( + mockProviders[L2_CHAIN_ID].getTransactionReceipt, + ).toHaveBeenCalledTimes(1); + + expect(mockContract.getMessageStatus).toHaveBeenCalledTimes(4); + expect(mockContract.queryFilter).toHaveBeenCalledTimes(1); + expect(mockContract.symbol).toHaveBeenCalledTimes(1); + }); + + it('should not get transactions with wrong address', async () => { + jest.mocked(axios.get).mockResolvedValueOnce({ + data: eventsJson, + }); + + const { txs } = await relayerApi.getAllBridgeTransactionByAddress( + '0xWrongAddress', + { + page: 0, + size: 100, + }, + ); + + expect(txs.length).toEqual(0); + }); + + it('ignores transactions from chains not supported by the bridge', async () => { + // TODO: use structuredClone(). Nodejs support? + const noSupportedTx = JSON.parse(JSON.stringify(eventsJson.items[0])); + noSupportedTx.data.Message.SrcChainId = 666; + + const newEventsJson = { + ...eventsJson, + items: [noSupportedTx, ...eventsJson.items.slice(1)], + }; + + jest.mocked(axios.get).mockResolvedValueOnce({ + data: newEventsJson, + }); + + const { txs } = await relayerApi.getAllBridgeTransactionByAddress( + walletAddress, + { + page: 0, + size: 100, + }, + ); + + // expected = total_items - duplicated_tx - unsupported_tx + expect(txs.length).toEqual(eventsJson.items.length - 1 - 1); + }); + + // TODO: there are still some branches to cover here + + it('should get block info', async () => { + jest.mocked(axios.get).mockResolvedValue({ + data: blockInfoJson, }); const blockInfo = await relayerApi.getBlockInfo(); @@ -172,8 +277,8 @@ describe('RelayerAPIService', () => { // Test return value expect(blockInfo).toEqual( new Map([ - [blockInfoFromAPI[0].chainID, blockInfoFromAPI[0]], - [blockInfoFromAPI[1].chainID, blockInfoFromAPI[1]], + [blockInfoJson.data[0].chainID, blockInfoJson.data[0]], + [blockInfoJson.data[1].chainID, blockInfoJson.data[1]], ]), ); }); diff --git a/packages/bridge-ui/src/relayer-api/RelayerAPIService.ts b/packages/bridge-ui/src/relayer-api/RelayerAPIService.ts index 594c593382..5795f4e9c6 100644 --- a/packages/bridge-ui/src/relayer-api/RelayerAPIService.ts +++ b/packages/bridge-ui/src/relayer-api/RelayerAPIService.ts @@ -5,7 +5,7 @@ import { BigNumber, Contract, ethers } from 'ethers'; import { chains } from '../chain/chains'; import { bridgeABI, erc20ABI, tokenVaultABI } from '../constants/abi'; import { MessageStatus } from '../domain/message'; -import type { RecordProviders } from '../domain/provider'; +import type { ProvidersRecord } from '../domain/provider'; import type { APIRequestParams, APIResponse, @@ -92,10 +92,10 @@ export class RelayerAPIService implements RelayerAPI { return [symbol, amountInWei]; } - private readonly providers: RecordProviders; + private readonly providers: ProvidersRecord; private readonly baseUrl: string; - constructor(baseUrl: string, providers: RecordProviders) { + constructor(baseUrl: string, providers: ProvidersRecord) { this.providers = providers; // There is a chance that by accident the env var @@ -158,9 +158,9 @@ export class RelayerAPIService implements RelayerAPI { return { txs: [], paginationInfo }; } - apiTxs.items = RelayerAPIService._filterDuplicateHashes(apiTxs.items); + const items = RelayerAPIService._filterDuplicateHashes(apiTxs.items); - const txs = apiTxs.items.map((tx: APIResponseTransaction) => { + const txs = items.map((tx: APIResponseTransaction) => { let data = tx.data.Message.Data; if (data === '') { data = '0x'; // ethers does not allow "" for empty bytes value diff --git a/packages/bridge-ui/src/relayer-api/__fixtures__/blockInfo.json b/packages/bridge-ui/src/relayer-api/__fixtures__/blockInfo.json new file mode 100644 index 0000000000..fa71491c29 --- /dev/null +++ b/packages/bridge-ui/src/relayer-api/__fixtures__/blockInfo.json @@ -0,0 +1,6 @@ +{ + "data": [ + { "chainID": 31336, "latestProcessedBlock": 66125, "latestBlock": 66138 }, + { "chainID": 167001, "latestProcessedBlock": 41781, "latestBlock": 41803 } + ] +} diff --git a/packages/bridge-ui/src/relayer-api/__fixtures__/events.json b/packages/bridge-ui/src/relayer-api/__fixtures__/events.json new file mode 100644 index 0000000000..4854071568 --- /dev/null +++ b/packages/bridge-ui/src/relayer-api/__fixtures__/events.json @@ -0,0 +1,222 @@ +{ + "items": [ + { + "id": 42752, + "name": "MessageSent", + "data": { + "Raw": { + "data": "0x123", + "address": "0xc6e7df5e7b4f2a278906862b61205850344d4e7d", + "removed": false, + "logIndex": "0x8", + "blockHash": "0xbf898a28993aa0acb5160d8f3f80200cc2a38ffbd89abc1d0399d7c6879cdfd9", + "blockNumber": "0x9d13", + "transactionHash": "0x48e10c32f89ef5c19c3148a8e26ac96bb39804b2894e41cb53f420da2075999c", + "transactionIndex": "0x3" + }, + "Message": { + "Id": 7801, + "To": "0x1000777700000000000000000000000000000002", + "Data": "Data", + "Memo": "", + "Owner": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "Sender": "0x322813fd9a801c5507c9de605d63cea4f2ce6c44", + "GasLimit": 140000, + "CallValue": 0, + "SrcChainId": 31336, + "DestChainId": 167001, + "DepositValue": 0, + "ProcessingFee": 4650000003100000, + "RefundAddress": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8" + } + }, + "status": 2, + "eventType": 1, + "chainID": 31336, + "canonicalTokenAddress": "0x9A676e781A523b5d0C0e43731313A708CB607508", + "canonicalTokenSymbol": "BLL", + "canonicalTokenName": "Bull Token", + "canonicalTokenDecimals": 18, + "amount": "5000000000000000000", + "msgHash": "0x289d8464b91c2f31f6170942f29cdb815148dc527fbbbcd5ff158c0f5b9ac766", + "messageOwner": "0x33C887d229B5b99cdfa06B02102f8F75411C56B8", + "event": "MessageSent" + }, + { + "id": 42763, + "name": "MessageSent", + "data": { + "Raw": { + "data": "0x123", + "address": "0xc6e7df5e7b4f2a278906862b61205850344d4e7d", + "removed": false, + "logIndex": "0x4", + "blockHash": "0xa35f409355d150054d82a75f122d33ea9e3f5236072cdee350b53c12b109e77b", + "blockNumber": "0x9d1a", + "transactionHash": "0xd22de4483f27ad8652a542077e8e4c1e9ed337e744574bbd1a5a2c41ac5360b2", + "transactionIndex": "0x1" + }, + "Message": { + "Id": 7803, + "To": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "Data": "", + "Memo": "", + "Owner": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "Sender": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "GasLimit": 140000, + "CallValue": 0, + "SrcChainId": 31336, + "DestChainId": 167001, + "DepositValue": 10000000000000000000, + "ProcessingFee": 1350000000900000, + "RefundAddress": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8" + } + }, + "status": 2, + "eventType": 0, + "chainID": 31336, + "canonicalTokenAddress": "0x0000000000000000000000000000000000000000", + "canonicalTokenSymbol": "", + "canonicalTokenName": "", + "canonicalTokenDecimals": 0, + "amount": "10000000000000000000", + "msgHash": "0x955e9841c5b9b6bc449800f97312c6f4e798a824a7fb510808a488d9873d7e86", + "messageOwner": "0x33C887d229B5b99cdfa06B02102f8F75411C56B8", + "event": "MessageSent" + }, + { + "id": 46456, + "name": "MessageSent", + "data": { + "Raw": { + "data": "0x123", + "address": "0x1000777700000000000000000000000000000004", + "removed": false, + "logIndex": "0xc", + "blockHash": "0xc7a9751ab93f3d2a429189ef195fb1eef8fb121525f5c121b977f2745010a741", + "blockNumber": "0x6004", + "transactionHash": "0xaf89166c616b5d80db1afe29380221e37c33b20e76fdd3bbcaa566bebb35ee80", + "transactionIndex": "0x6" + }, + "Message": { + "Id": 5785, + "To": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "Data": "", + "Memo": "", + "Owner": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "Sender": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "GasLimit": 140000, + "CallValue": 0, + "SrcChainId": 167001, + "DestChainId": 31336, + "DepositValue": 3.3e19, + "ProcessingFee": 1350000006300000, + "RefundAddress": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8" + } + }, + "status": 2, + "eventType": 0, + "chainID": 167001, + "canonicalTokenAddress": "0x0000000000000000000000000000000000000000", + "canonicalTokenSymbol": "", + "canonicalTokenName": "", + "canonicalTokenDecimals": 0, + "amount": "33000000000000000000", + "msgHash": "0x10858ddd2d21d2ee72802d864b998fac8ea38779427bc1164656e3f5374ad8a5", + "messageOwner": "0x33C887d229B5b99cdfa06B02102f8F75411C56B8", + "event": "MessageSent" + }, + { + "id": 46602, + "name": "MessageSent", + "data": { + "Raw": { + "data": "0x123", + "address": "0xc6e7df5e7b4f2a278906862b61205850344d4e7d", + "removed": false, + "logIndex": "0x4", + "blockHash": "0x91e912064c75f8fa64273b923cbcf2453b6c537046eab149e9ed646a12161487", + "blockNumber": "0xac72", + "transactionHash": "0x23902041dc0493409ebc8fdb74aa8c9a91e3c1544584a79deed99311af5ad5b1", + "transactionIndex": "0x2" + }, + "Message": { + "Id": 8465, + "To": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "Data": "", + "Memo": "", + "Owner": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "Sender": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "GasLimit": 140000, + "CallValue": 0, + "SrcChainId": 31336, + "DestChainId": 167001, + "DepositValue": 3.46e20, + "ProcessingFee": 1350000000900000, + "RefundAddress": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8" + } + }, + "status": 0, + "eventType": 0, + "chainID": 31336, + "canonicalTokenAddress": "0x0000000000000000000000000000000000000000", + "canonicalTokenSymbol": "", + "canonicalTokenName": "", + "canonicalTokenDecimals": 0, + "amount": "346000000000000000000", + "msgHash": "0x314041cfeb2282b41930fd950bd69c6af75eb50ddaa615951951310be022db25", + "messageOwner": "0x33C887d229B5b99cdfa06B02102f8F75411C56B8", + "event": "MessageSent" + }, + { + "id": 46610, + "name": "MessageSent", + "data": { + "Raw": { + "data": "0x123", + "address": "0xc6e7df5e7b4f2a278906862b61205850344d4e7d", + "removed": false, + "logIndex": "0x4", + "blockHash": "0x91e912064c75f8fa64273b923cbcf2453b6c537046eab149e9ed646a12161487", + "blockNumber": "0xac72", + "transactionHash": "0x23902041dc0493409ebc8fdb74aa8c9a91e3c1544584a79deed99311af5ad5b1", + "transactionIndex": "0x2" + }, + "Message": { + "Id": 8465, + "To": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "Data": "", + "Memo": "", + "Owner": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "Sender": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8", + "GasLimit": 140000, + "CallValue": 0, + "SrcChainId": 31336, + "DestChainId": 167001, + "DepositValue": 3.46e20, + "ProcessingFee": 1350000000900000, + "RefundAddress": "0x33c887d229b5b99cdfa06b02102f8f75411c56b8" + } + }, + "status": 2, + "eventType": 0, + "chainID": 31336, + "canonicalTokenAddress": "0x0000000000000000000000000000000000000000", + "canonicalTokenSymbol": "", + "canonicalTokenName": "", + "canonicalTokenDecimals": 0, + "amount": "346000000000000000000", + "msgHash": "0x314041cfeb2282b41930fd950bd69c6af75eb50ddaa615951951310be022db25", + "messageOwner": "0x33C887d229B5b99cdfa06B02102f8F75411C56B8", + "event": "MessageSent" + } + ], + "page": 0, + "size": 100, + "max_page": 1, + "total_pages": 1, + "total": 48, + "last": false, + "first": true, + "visible": 48 +} diff --git a/packages/bridge-ui/src/storage/StorageService.spec.ts b/packages/bridge-ui/src/storage/StorageService.spec.ts index d079c5f4a5..8e3d57d92b 100644 --- a/packages/bridge-ui/src/storage/StorageService.spec.ts +++ b/packages/bridge-ui/src/storage/StorageService.spec.ts @@ -236,7 +236,7 @@ describe('storage tests', () => { ]); }); - it('ignore txs from unsupported chains when getting all txs', async () => { + it('ignores txs from unsupported chains when getting all txs', async () => { providers[L1_CHAIN_ID] = undefined; const svc = new StorageService(mockStorage as any, providers); @@ -258,7 +258,7 @@ describe('storage tests', () => { expect(tx).toBeUndefined(); }); - it('get transaction by hash, no receipt', async () => { + it('gets transaction by hash, no receipt', async () => { mockProvider.getTransactionReceipt.mockImplementation(() => { return null; }); @@ -270,7 +270,7 @@ describe('storage tests', () => { expect(tx).toEqual(tx); }); - it('get transaction by hash, no event', async () => { + it('gets transaction by hash, no event', async () => { mockContract.queryFilter.mockImplementation(() => { return []; }); @@ -285,7 +285,7 @@ describe('storage tests', () => { }); }); - it('get transaction by hash where tx.from !== address', async () => { + it('gets transaction by hash where tx.from !== address', async () => { const svc = new StorageService(mockStorage as any, providers); const tx = await svc.getTransactionByHash('0x666', mockTx.hash); @@ -293,7 +293,7 @@ describe('storage tests', () => { expect(tx).toBeUndefined(); }); - it('get transaction by hash, ETH transfer', async () => { + it('gets transaction by hash, ETH transfer', async () => { mockContract.queryFilter.mockImplementation(() => { return mockQuery; }); @@ -315,7 +315,7 @@ describe('storage tests', () => { }); }); - it('get transaction by hash, no ERC20Sent event', async () => { + it('gets transaction by hash, no ERC20Sent event', async () => { mockContract.queryFilter.mockImplementation((filter: string) => { if (filter === 'ERC20Sent') return []; return mockErc20Query; // MessageSent @@ -334,7 +334,7 @@ describe('storage tests', () => { }); }); - it('get transaction by hash, ERC20 transfer', async () => { + it('gets transaction by hash, ERC20 transfer', async () => { mockContract.queryFilter.mockImplementation(() => { return mockErc20Query; }); @@ -360,7 +360,7 @@ describe('storage tests', () => { }); }); - it('ignore txs from unsupported chains when getting txs by hash', async () => { + it('ignores txs from unsupported chains when getting txs by hash', async () => { providers[L1_CHAIN_ID] = undefined; const svc = new StorageService(mockStorage as any, providers); @@ -370,6 +370,35 @@ describe('storage tests', () => { expect(tx).toBeUndefined(); }); + it('makes sure New transactions are on top of the list', async () => { + mockStorage.getItem.mockImplementation(() => { + return JSON.stringify([ + { ...mockTx, status: MessageStatus.New }, + { ...mockTx, status: MessageStatus.Done }, + { ...mockTx, status: MessageStatus.Retriable }, + { ...mockTx, status: MessageStatus.New }, + ]); + }); + + mockContract.queryFilter.mockImplementation(() => { + return []; + }); + + const svc = new StorageService(mockStorage as any, providers); + + const tx = await svc.getAllByAddress('0x123'); + const statuses = tx.map((t) => t.status); + + // New txs should be on top + expect(statuses).toEqual([ + MessageStatus.New, + MessageStatus.New, + //----------------// + MessageStatus.Done, + MessageStatus.Retriable, + ]); + }); + it('updates storage by address', () => { mockStorage.getItem.mockImplementation(() => { return JSON.stringify(mockTxs); diff --git a/packages/bridge-ui/src/utils/remove0xPrefixIfPresent.spec.ts b/packages/bridge-ui/src/utils/remove0xPrefixIfPresent.spec.ts deleted file mode 100644 index 3b26acbc22..0000000000 --- a/packages/bridge-ui/src/utils/remove0xPrefixIfPresent.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { remove0xPrefixIfPresent } from './remove0xPrefixIfPresent'; - -it("Should remove 0x if it is present (for 1-n sets of '0x'), and leave string alone if not", () => { - expect(remove0xPrefixIfPresent('0x555')).toStrictEqual('555'); - expect(remove0xPrefixIfPresent('0x0x0x555')).toStrictEqual('555'); - expect(remove0xPrefixIfPresent('555')).toStrictEqual('555'); -}); diff --git a/packages/bridge-ui/src/utils/remove0xPrefixIfPresent.ts b/packages/bridge-ui/src/utils/remove0xPrefixIfPresent.ts deleted file mode 100644 index d32f77eb0e..0000000000 --- a/packages/bridge-ui/src/utils/remove0xPrefixIfPresent.ts +++ /dev/null @@ -1,12 +0,0 @@ -function remove0xPrefixIfPresent(s: string): string { - if (!s.startsWith('0x')) { - return s; - } - - while (s.startsWith('0x')) { - s = s.slice(2); - } - return s; -} - -export { remove0xPrefixIfPresent };