Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(bridge-ui): handle wrong bridge address #13880

Merged
merged 21 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions packages/bridge-ui/src/constants/__mocks__/envVars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export const L1_RPC = 'https://l1rpc.internal.taiko.xyz';
export const L1_TOKEN_VAULT_ADDRESS =
'0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f';

export const L1_BRIDGE_ADDRESS = '0x59b670e9fA9D0A427751Af201D676719a970857b';
export const L1_BRIDGE_ADDRESS = '0xc6e7df5e7b4f2a278906862b61205850344d4e7d';

export const L1_CROSS_CHAIN_SYNC_ADDRESS =
'0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE';
Expand All @@ -13,28 +13,28 @@ export const L1_SIGNAL_SERVICE_ADDRESS =

export const L1_CHAIN_ID = 31336;

export const L1_CHAIN_NAME = 'Ethereum A3';
export const L1_CHAIN_NAME = 'Ethereum';

export const L1_EXPLORER_URL = 'https://l1explorer.internal.taiko.xyz';

export const L2_RPC = 'https://l2rpc.internal.taiko.xyz';

export const L2_TOKEN_VAULT_ADDRESS =
'0x0000777700000000000000000000000000000002';
'0x1000777700000000000000000000000000000002';

export const L2_BRIDGE_ADDRESS =
import.meta.env?.VITE_L2_BRIDGE_ADDRESS ??
'0x0000777700000000000000000000000000000004';
'0x1000777700000000000000000000000000000004';

export const L2_CROSS_CHAIN_SYNC_ADDRESS =
'0x0000777700000000000000000000000000000001';
'0x1000777700000000000000000000000000000001';

export const L2_SIGNAL_SERVICE_ADDRESS =
'0x0000777700000000000000000000000000000007';
'0x1000777700000000000000000000000000000007';

export const L2_CHAIN_ID = 167001;

export const L2_CHAIN_NAME = 'Taiko A3';
export const L2_CHAIN_NAME = 'Taiko';

export const L2_EXPLORER_URL = 'https://l2explorer.internal.taiko.xyz';

Expand Down
2 changes: 2 additions & 0 deletions packages/bridge-ui/src/domain/relayerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ export type TransactionData = {
Data: string;
};
Raw: {
address: Address;
transactionHash: string;
transactionIndex: string;
};
};

Expand Down
188 changes: 126 additions & 62 deletions packages/bridge-ui/src/relayer-api/RelayerAPIService.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import type { Address } from '@wagmi/core';
import axios from 'axios';
import type { ethers } from 'ethers';

import { L1_CHAIN_ID, L2_CHAIN_ID, RELAYER_URL } from '../constants/envVars';
import { ethers } from 'ethers';

import {
L1_CHAIN_ID,
L2_BRIDGE_ADDRESS,
L2_CHAIN_ID,
RELAYER_URL,
} from '../constants/envVars';
import { MessageStatus } from '../domain/message';
import type { ProvidersRecord } from '../domain/provider';
import type { APIResponseTransaction } from '../domain/relayerApi';
import blockInfoJson from './__fixtures__/blockInfo.json';
import eventsJson from './__fixtures__/events.json';
import { RelayerAPIService } from './RelayerAPIService';
Expand All @@ -18,7 +24,7 @@ const mockContract = {
getMessageStatus: jest.fn(),
symbol: jest.fn(),
filters: {
ERC20Sent: () => jest.fn().mockReturnValue('ERC20Sent'),
ERC20Sent: () => 'ERC20Sent',
},
};

Expand Down Expand Up @@ -48,8 +54,7 @@ const mockErc20Event = {
args: {
token: '0x123',
amount: '100',
msgHash:
'0x289d8464b91c2f31f6170942f29cdb815148dc527fbbbcd5ff158c0f5b9ac766',
msgHash: '0x123',
message: {
owner: walletAddress,
data: '0x789',
Expand All @@ -60,7 +65,6 @@ const mockErc20Event = {
const mockErc20Query = [mockErc20Event];

const baseUrl = RELAYER_URL.replace(/\/$/, '');

const relayerApi = new RelayerAPIService(RELAYER_URL, mockProviders);

describe('RelayerAPIService', () => {
Expand Down Expand Up @@ -100,16 +104,37 @@ describe('RelayerAPIService', () => {
expect(data.items.length).toEqual(eventsJson.items.length);
});

it('cannot get transactions from API', async () => {
it('should return an empty list when API fails', async () => {
const args = {
address: walletAddress,
chainID: 1,
event: 'MessageSent',
};

jest.mocked(axios.get).mockRejectedValueOnce(new Error('BAM!!'));

await expect(
relayerApi.getTransactionsFromAPI({
address: walletAddress,
chainID: 1,
event: 'MessageSent',
}),
).rejects.toThrowError('could not fetch transactions from API');
await expect(relayerApi.getTransactionsFromAPI(args)).rejects.toThrowError(
'could not fetch transactions from API',
);

// Status >= 400 is considered an error
jest.mocked(axios.get).mockResolvedValueOnce({
status: 500,
});

await expect(relayerApi.getTransactionsFromAPI(args)).rejects.toThrowError(
'could not fetch transactions from API',
);

// Status < 400 is considered a success
jest.mocked(axios.get).mockResolvedValueOnce({
status: 200,
data: eventsJson,
});

await expect(relayerApi.getTransactionsFromAPI(args)).resolves.toEqual(
eventsJson,
);
});

it('should get empty list of transactions', async () => {
Expand Down Expand Up @@ -142,8 +167,22 @@ describe('RelayerAPIService', () => {
});

it('should get filtered bridge transactions by address', async () => {
// TODO: use structuredClone(), instead of JSON.parse(JSON.stringify())
const items = JSON.parse(
JSON.stringify(eventsJson.items),
) as APIResponseTransaction[];

// Ignore transactions from unsupported chains
items[0].chainID = items[0].data.Message.SrcChainId = 666;

// Filter out duplicate transactions
items[2].data.Raw.transactionHash = items[1].data.Raw.transactionHash;

// Filter out transactions with wrong bridge address
items[4].data.Raw.address = '0x666';

jest.mocked(axios.get).mockResolvedValueOnce({
data: eventsJson,
data: { ...eventsJson, items },
});

const { txs } = await relayerApi.getAllBridgeTransactionByAddress(
Expand All @@ -154,28 +193,24 @@ describe('RelayerAPIService', () => {
},
);

// Test parameters
expect(axios.get).toHaveBeenCalledWith(`${baseUrl}/events`, {
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);
expect(txs.length).toBe(items.length - 3);
});

it('should get only L2 => L1 transactions by address', async () => {
const items = JSON.parse(
JSON.stringify(eventsJson.items),
) as APIResponseTransaction[];

// 5th items is L2 => L1
items[5].chainID = items[5].data.Message.SrcChainId = L2_CHAIN_ID;
items[5].data.Raw.address = L2_BRIDGE_ADDRESS;
items[5].data.Message.DestChainId = L1_CHAIN_ID;

jest.mocked(axios.get).mockResolvedValueOnce({
data: eventsJson,
data: { ...eventsJson, items },
});

// Transactions with no receipt are not included
// Non of the transactions L1 => L2 have receipts
jest
.mocked(mockProviders[L1_CHAIN_ID].getTransactionReceipt)
.mockResolvedValue(null);
Expand All @@ -188,36 +223,37 @@ describe('RelayerAPIService', () => {
},
);

expect(txs.length).toBeGreaterThanOrEqual(1);
expect(txs.length).toBe(1);

const chainIds = txs.map((tx) => tx.message.srcChainId);
expect(chainIds).not.toContain(L1_CHAIN_ID);
});

it('should get all transactions by address', async () => {
const items = JSON.parse(
JSON.stringify(eventsJson.items),
) as APIResponseTransaction[];

// We make one ETH bridge transaction
items[7].data.Message.Data = '';
items[7].canonicalTokenAddress = ethers.constants.AddressZero;

jest.mocked(axios.get).mockResolvedValueOnce({
data: eventsJson,
data: { ...eventsJson, items },
});

const { txs } = await relayerApi.getAllBridgeTransactionByAddress(
walletAddress,
{
page: 0,
size: 100,
},
);
await relayerApi.getAllBridgeTransactionByAddress(walletAddress, {
page: 0,
size: 100,
});

expect(
mockProviders[L1_CHAIN_ID].getTransactionReceipt,
).toHaveBeenCalledTimes(3);

expect(
mockProviders[L2_CHAIN_ID].getTransactionReceipt,
).toHaveBeenCalledTimes(1);
).toHaveBeenCalledTimes(10);

expect(mockContract.getMessageStatus).toHaveBeenCalledTimes(4);
expect(mockContract.queryFilter).toHaveBeenCalledTimes(1);
expect(mockContract.symbol).toHaveBeenCalledTimes(1);
expect(mockContract.getMessageStatus).toHaveBeenCalledTimes(10);
expect(mockContract.queryFilter).toHaveBeenCalledTimes(9);
expect(mockContract.symbol).toHaveBeenCalledTimes(9);
});

it('should not get transactions with wrong address', async () => {
Expand All @@ -236,18 +272,24 @@ describe('RelayerAPIService', () => {
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)],
};
it('should show New transactions on top', async () => {
let count = 0;
// Let's make some transactions to be New
mockContract.getMessageStatus.mockImplementation(() => {
count++;

switch (count) {
case 3:
case 5:
case 9:
return Promise.resolve(MessageStatus.New);
default:
return Promise.resolve(MessageStatus.Done);
}
});

jest.mocked(axios.get).mockResolvedValueOnce({
data: newEventsJson,
data: eventsJson,
});

const { txs } = await relayerApi.getAllBridgeTransactionByAddress(
Expand All @@ -258,8 +300,21 @@ describe('RelayerAPIService', () => {
},
);

// expected = total_items - duplicated_tx - unsupported_tx
expect(txs.length).toEqual(eventsJson.items.length - 1 - 1);
const statuses = txs.map((tx) => tx.status);

expect(statuses).toEqual([
MessageStatus.New,
MessageStatus.New,
MessageStatus.New,
//----------------//
MessageStatus.Done,
MessageStatus.Done,
MessageStatus.Done,
MessageStatus.Done,
MessageStatus.Done,
MessageStatus.Done,
MessageStatus.Done,
]);
});

// TODO: there are still some branches to cover here
Expand All @@ -283,11 +338,20 @@ describe('RelayerAPIService', () => {
);
});

it('cannot get block info', async () => {
it('handles API failure', async () => {
jest.mocked(axios.get).mockRejectedValueOnce(new Error('BAM!!'));

await expect(relayerApi.getBlockInfo()).rejects.toThrowError(
'failed to fetch block info',
);

// Status >= 400 is considered an error
jest.mocked(axios.get).mockResolvedValueOnce({
status: 400,
});

await expect(relayerApi.getBlockInfo()).rejects.toThrowError(
'failed to fetch block info',
);
});
});
Loading