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

ID-1598 Passport Fee Option optimisation #1713

Merged
merged 7 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
316 changes: 170 additions & 146 deletions packages/passport/sdk/src/zkEvm/sendTransaction.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { StaticJsonRpcProvider, TransactionRequest } from '@ethersproject/providers';
import { Signer } from '@ethersproject/abstract-signer';
import { Flow } from '@imtbl/metrics';
import { getEip155ChainId, getNonce, getSignedMetaTransactions } from './walletHelpers';
import { BigNumber } from 'ethers';
import {
getEip155ChainId,
getNonce,
getSignedMetaTransactions,
} from './walletHelpers';
import { sendTransaction } from './sendTransaction';
import { chainId, chainIdEip155, mockUserZkEvm } from '../test/mocks';
import { RelayerClient } from './relayerClient';
import { retryWithDelay } from '../network/retry';
import { RelayerTransaction, RelayerTransactionStatus } from './types';
import { FeeOption, RelayerTransaction, RelayerTransactionStatus } from './types';
import { JsonRpcError, RpcErrorCode } from './JsonRpcError';
import GuardianClient from '../guardian';
import * as walletHelpers from './walletHelpers';

jest.mock('./walletHelpers');
jest.mock('../network/retry');

describe('sendTransaction', () => {
const signedTransaction = 'signedTransaction123';
const signedTransactions = 'signedTransactions123';
const relayerTransactionId = 'relayerTransactionId123';
const transactionHash = 'transactionHash123';
Expand Down Expand Up @@ -44,8 +49,8 @@ describe('sendTransaction', () => {
addEvent: jest.fn(),
};

const imxFeeOption = {
tokenPrice: '1',
const imxFeeOption: FeeOption = {
tokenPrice: '0x1',
tokenSymbol: 'IMX',
tokenDecimals: 18,
tokenAddress: '0x1337',
Expand All @@ -57,9 +62,6 @@ describe('sendTransaction', () => {
relayerClient.imGetFeeOptions.mockResolvedValue([imxFeeOption]);
(getNonce as jest.Mock).mockResolvedValueOnce(nonce);
(getEip155ChainId as jest.Mock).mockReturnValue(chainIdEip155);
(getSignedMetaTransactions as jest.Mock).mockResolvedValueOnce(
signedTransaction,
);
(getSignedMetaTransactions as jest.Mock).mockResolvedValueOnce(
signedTransactions,
);
Expand All @@ -68,169 +70,191 @@ describe('sendTransaction', () => {
guardianClient.validateEVMTransaction.mockResolvedValue(undefined);
});

it('calls relayerClient.ethSendTransaction with the correct arguments', async () => {
(retryWithDelay as jest.Mock).mockResolvedValue({
status: RelayerTransactionStatus.SUCCESSFUL,
hash: transactionHash,
} as RelayerTransaction);

const result = await sendTransaction({
params: [transactionRequest],
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkevmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
describe('when the relayer returns a transaction with a "SUCCESSFUL" status', () => {
beforeEach(() => {
(retryWithDelay as jest.Mock).mockResolvedValue({
status: RelayerTransactionStatus.SUCCESSFUL,
hash: transactionHash,
} as RelayerTransaction);
});

expect(result).toEqual(transactionHash);
expect(relayerClient.ethSendTransaction).toHaveBeenCalledWith(
mockUserZkEvm.zkEvm.ethAddress,
signedTransactions,
);
});
it('calls relayerClient.imGetFeeOptions with the correct arguments', async () => {
(walletHelpers.encodedTransactions as jest.Mock).mockReturnValue('encodedTransactions123');

it('calls relayerClient.ethSendTransaction with sponsored meta transaction', async () => {
(retryWithDelay as jest.Mock).mockResolvedValue({
status: RelayerTransactionStatus.SUCCESSFUL,
hash: transactionHash,
} as RelayerTransaction);

const mockImxFeeOption = {
tokenPrice: '0',
tokenSymbol: 'IMX',
tokenDecimals: 18,
tokenAddress: '0x1337',
recipientAddress: '0x7331',
};

relayerClient.imGetFeeOptions.mockResolvedValue([mockImxFeeOption]);

const result = await sendTransaction({
params: [transactionRequest],
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkevmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
});
await sendTransaction({
params: [transactionRequest],
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkevmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
});

expect(result).toEqual(transactionHash);
expect(guardianClient.validateEVMTransaction).toHaveBeenCalledWith(
{
chainId: chainIdEip155,
nonce,
metaTransactions: [
{
data: transactionRequest.data,
revertOnError: true,
to: mockUserZkEvm.zkEvm.ethAddress,
value: '0x00',
nonce,
},
],
},
);
expect(relayerClient.imGetFeeOptions).toHaveBeenCalledWith(
mockUserZkEvm.zkEvm.ethAddress,
'encodedTransactions123',
);
});

expect(relayerClient.ethSendTransaction).toHaveBeenCalledWith(
mockUserZkEvm.zkEvm.ethAddress,
signedTransactions,
);
});
it('calls relayerClient.ethSendTransaction with the correct arguments', async () => {
const result = await sendTransaction({
params: [transactionRequest],
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkevmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
});

it('calls guardian.evaluateTransaction with the correct arguments', async () => {
(retryWithDelay as jest.Mock).mockResolvedValue({
status: RelayerTransactionStatus.SUCCESSFUL,
hash: transactionHash,
} as RelayerTransaction);
(getEip155ChainId as jest.Mock).mockReturnValue(`eip155:${chainId}`);

const result = await sendTransaction({
params: [transactionRequest],
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkevmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
expect(result).toEqual(transactionHash);
expect(relayerClient.ethSendTransaction).toHaveBeenCalledWith(
mockUserZkEvm.zkEvm.ethAddress,
signedTransactions,
);
});

expect(result).toEqual(transactionHash);
expect(getEip155ChainId).toHaveBeenCalledWith(chainId);
expect(guardianClient.validateEVMTransaction).toHaveBeenCalledWith(
{
chainId: chainIdEip155,
nonce,
metaTransactions: [
{
data: transactionRequest.data,
revertOnError: true,
to: mockUserZkEvm.zkEvm.ethAddress,
value: '0x00',
nonce,
},
{
revertOnError: true,
to: imxFeeOption.recipientAddress,
value: imxFeeOption.tokenPrice,
nonce,
},
],
},
);
expect(relayerClient.ethSendTransaction).toHaveBeenCalledWith(
mockUserZkEvm.zkEvm.ethAddress,
signedTransactions,
);
});
it('calls relayerClient.ethSendTransaction with sponsored meta transaction', async () => {
const mockImxFeeOption = {
tokenPrice: '0',
tokenSymbol: 'IMX',
tokenDecimals: 18,
tokenAddress: '0x1337',
recipientAddress: '0x7331',
};

it('returns and surfaces an error if the relayer does not return a successful status', async () => {
(retryWithDelay as jest.Mock).mockResolvedValue({
status: RelayerTransactionStatus.FAILED,
statusMessage: 'Unable to complete transaction',
} as RelayerTransaction);
relayerClient.imGetFeeOptions.mockResolvedValue([mockImxFeeOption]);

await expect(
sendTransaction({
const result = await sendTransaction({
params: [transactionRequest],
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkevmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
}),
).rejects.toThrow(
new JsonRpcError(
RpcErrorCode.INTERNAL_ERROR,
'Transaction failed to submit with status FAILED. Error message: Unable to complete transaction',
),
);
});
});

expect(result).toEqual(transactionHash);
expect(guardianClient.validateEVMTransaction).toHaveBeenCalledWith(
{
chainId: chainIdEip155,
nonce,
metaTransactions: [
{
data: transactionRequest.data,
revertOnError: true,
to: mockUserZkEvm.zkEvm.ethAddress,
value: '0x00',
nonce,
},
],
},
);

expect(relayerClient.ethSendTransaction).toHaveBeenCalledWith(
mockUserZkEvm.zkEvm.ethAddress,
signedTransactions,
);
});

it('returns and surfaces an error if the relayer cancels a transaction', async () => {
(retryWithDelay as jest.Mock).mockResolvedValue({
status: RelayerTransactionStatus.CANCELLED,
statusMessage: 'Transaction cancelled',
} as RelayerTransaction);
it('calls guardian.evaluateTransaction with the correct arguments', async () => {
(getEip155ChainId as jest.Mock).mockReturnValue(`eip155:${chainId}`);

await expect(
sendTransaction({
const result = await sendTransaction({
params: [transactionRequest],
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkevmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
}),
).rejects.toThrow(
new JsonRpcError(
RpcErrorCode.INTERNAL_ERROR,
'Transaction failed to submit with status CANCELLED. Error message: Transaction cancelled',
),
);
});

expect(result).toEqual(transactionHash);
expect(getEip155ChainId).toHaveBeenCalledWith(chainId);
expect(guardianClient.validateEVMTransaction).toHaveBeenCalledWith(
{
chainId: chainIdEip155,
nonce,
metaTransactions: [
{
data: transactionRequest.data,
revertOnError: true,
to: mockUserZkEvm.zkEvm.ethAddress,
value: '0x00',
nonce,
},
{
revertOnError: true,
to: imxFeeOption.recipientAddress,
value: BigNumber.from(imxFeeOption.tokenPrice),
nonce,
},
],
},
);
expect(relayerClient.ethSendTransaction).toHaveBeenCalledWith(
mockUserZkEvm.zkEvm.ethAddress,
signedTransactions,
);
});
});

describe('when the relayer returns a transaction with a "FAILED" status', () => {
beforeEach(() => {
(retryWithDelay as jest.Mock).mockResolvedValue({
status: RelayerTransactionStatus.FAILED,
statusMessage: 'Unable to complete transaction',
} as RelayerTransaction);
});

it('returns and surfaces an error', async () => {
await expect(
sendTransaction({
params: [transactionRequest],
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkevmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
}),
).rejects.toThrow(
new JsonRpcError(
RpcErrorCode.INTERNAL_ERROR,
'Transaction failed to submit with status FAILED. Error message: Unable to complete transaction',
),
);
});
});

describe('when the relayer returns a transaction with a "CANCELLED" status', () => {
beforeEach(() => {
(retryWithDelay as jest.Mock).mockResolvedValue({
status: RelayerTransactionStatus.CANCELLED,
statusMessage: 'Transaction cancelled',
} as RelayerTransaction);
});

it('returns and surfaces an error', async () => {
await expect(
sendTransaction({
params: [transactionRequest],
ethSigner,
rpcProvider: rpcProvider as unknown as StaticJsonRpcProvider,
relayerClient: relayerClient as unknown as RelayerClient,
zkevmAddress: mockUserZkEvm.zkEvm.ethAddress,
guardianClient: guardianClient as unknown as GuardianClient,
flow: flow as unknown as Flow,
}),
).rejects.toThrow(
new JsonRpcError(
RpcErrorCode.INTERNAL_ERROR,
'Transaction failed to submit with status CANCELLED. Error message: Transaction cancelled',
),
);
});
});
});