1
0
Fork 0
metamask-extension/ui/selectors/nonce-sorted-transactions-s...

350 lines
8.8 KiB
JavaScript

import { head, last } from 'lodash';
import { CHAIN_IDS } from '../../shared/constants/network';
import {
TransactionStatus,
TransactionType,
} from '../../shared/constants/transaction';
import { nonceSortedTransactionsSelector } from './transactions';
const RECIPIENTS = {
ONE: '0xRecipient1',
TWO: '0xRecipient2',
};
const SENDERS = {
ONE: '0xSender1',
TWO: '0xSender2',
};
const INCOMING_TX = {
id: '0-incoming',
type: TransactionType.incoming,
txParams: {
value: '0x0',
from: RECIPIENTS.ONE,
to: SENDERS.ONE,
},
};
const SIGNING_REQUEST = {
type: TransactionType.sign,
id: '0-signing',
status: TransactionStatus.unapproved,
};
const SIMPLE_SEND_TX = {
id: '0-simple',
txParams: {
from: SENDERS.ONE,
to: RECIPIENTS.ONE,
},
type: TransactionType.simpleSend,
};
const TOKEN_SEND_TX = {
id: '0-transfer',
txParams: {
from: SENDERS.ONE,
to: RECIPIENTS.TWO,
value: '0x0',
data: '0xdata',
},
type: TransactionType.tokenMethodTransfer,
};
const RETRY_TX = {
...SIMPLE_SEND_TX,
id: '0-retry',
type: TransactionType.retry,
};
const CANCEL_TX = {
id: '0-cancel',
txParams: {
value: '0x0',
from: SENDERS.ONE,
to: SENDERS.ONE,
},
type: TransactionType.cancel,
};
const getStateTree = ({
txList = [],
incomingTxList = [],
unapprovedMsgs = [],
} = {}) => ({
metamask: {
providerConfig: {
nickname: 'mainnet',
chainId: CHAIN_IDS.MAINNET,
},
unapprovedMsgs,
selectedAddress: SENDERS.ONE,
featureFlags: {},
transactions: [...incomingTxList],
incomingTransactions: [...incomingTxList],
currentNetworkTxList: [...txList],
incomingTransactionsPreferences: {},
},
});
const duplicateTx = (base, overrides) => {
const {
nonce = '0x0',
time = 0,
status = TransactionStatus.confirmed,
txReceipt,
} = overrides ?? {};
return {
...base,
txParams: {
...base.txParams,
nonce,
},
txReceipt,
time,
status,
};
};
describe('nonceSortedTransactionsSelector', () => {
it('should properly group a simple send that is superseded by a retry', () => {
const txList = [
duplicateTx(SIMPLE_SEND_TX, { status: TransactionStatus.dropped }),
duplicateTx(RETRY_TX, { time: 1 }),
];
const state = getStateTree({ txList });
const result = nonceSortedTransactionsSelector(state);
expect(result).toStrictEqual([
{
nonce: '0x0',
transactions: txList,
initialTransaction: head(txList),
primaryTransaction: last(txList),
hasRetried: true,
hasCancelled: false,
},
]);
});
it('should properly group a failed off-chain simple send that is superseded by a retry', () => {
const txList = [
duplicateTx(SIMPLE_SEND_TX, { status: TransactionStatus.failed }),
duplicateTx(RETRY_TX, { time: 1 }),
];
const state = getStateTree({ txList });
const result = nonceSortedTransactionsSelector(state);
expect(result).toStrictEqual([
{
nonce: '0x0',
transactions: txList,
initialTransaction: head(txList),
primaryTransaction: last(txList),
hasRetried: true,
hasCancelled: false,
},
]);
});
it('should properly group a simple send that is superseded by a cancel', () => {
const txList = [
duplicateTx(SIMPLE_SEND_TX, { status: TransactionStatus.dropped }),
duplicateTx(CANCEL_TX, { time: 1 }),
];
const state = getStateTree({ txList });
const result = nonceSortedTransactionsSelector(state);
expect(result).toStrictEqual([
{
nonce: '0x0',
transactions: txList,
initialTransaction: head(txList),
primaryTransaction: last(txList),
hasRetried: false,
hasCancelled: true,
},
]);
});
it('should properly group a simple send and retry that is superseded by a cancel', () => {
const txList = [
duplicateTx(SIMPLE_SEND_TX, { status: TransactionStatus.dropped }),
duplicateTx(RETRY_TX, { time: 1, status: TransactionStatus.dropped }),
duplicateTx(CANCEL_TX, { time: 2 }),
];
const state = getStateTree({ txList });
const result = nonceSortedTransactionsSelector(state);
expect(result).toStrictEqual([
{
nonce: '0x0',
transactions: txList,
initialTransaction: head(txList),
primaryTransaction: last(txList),
hasRetried: true,
hasCancelled: true,
},
]);
});
it('should group transactions created by an advance user attempting to manually supersede own txs', () => {
// We do not want this behavior longterm. This test just keeps us from
// changing expectations from today. It will also allow us to invert this
// test case when we move and change grouping logic.
const txList = [
duplicateTx(TOKEN_SEND_TX, { status: TransactionStatus.dropped }),
duplicateTx(SIMPLE_SEND_TX, { time: 1 }),
];
const state = getStateTree({ txList });
const result = nonceSortedTransactionsSelector(state);
expect(result).toStrictEqual([
{
nonce: '0x0',
transactions: txList,
initialTransaction: head(txList),
primaryTransaction: last(txList),
hasRetried: false,
hasCancelled: false,
},
]);
});
it('should NOT group sent and incoming tx with same nonce', () => {
const txList = [duplicateTx(SIMPLE_SEND_TX)];
const incomingTxList = [duplicateTx(INCOMING_TX, { time: 1 })];
const state = getStateTree({ txList, incomingTxList });
const result = nonceSortedTransactionsSelector(state);
expect(result).toStrictEqual([
{
nonce: '0x0',
transactions: txList,
initialTransaction: head(txList),
primaryTransaction: head(txList),
hasRetried: false,
hasCancelled: false,
},
{
nonce: '0x0',
transactions: incomingTxList,
initialTransaction: head(incomingTxList),
primaryTransaction: head(incomingTxList),
hasRetried: false,
hasCancelled: false,
},
]);
});
it('should display a signing request', () => {
const state = getStateTree({ unapprovedMsgs: [SIGNING_REQUEST] });
const result = nonceSortedTransactionsSelector(state);
expect(result).toStrictEqual([
{
nonce: undefined,
transactions: [SIGNING_REQUEST],
initialTransaction: SIGNING_REQUEST,
primaryTransaction: SIGNING_REQUEST,
hasRetried: false,
hasCancelled: false,
},
]);
});
it('should not set a failed off-chain transaction as primary, allowing additional retries', () => {
const txList = [
duplicateTx(SIMPLE_SEND_TX, { status: TransactionStatus.submitted }),
duplicateTx(RETRY_TX, { status: TransactionStatus.failed, time: 1 }),
];
const state = getStateTree({ txList });
const result = nonceSortedTransactionsSelector(state);
expect(result).toStrictEqual([
{
nonce: '0x0',
transactions: txList,
initialTransaction: head(txList),
primaryTransaction: head(txList),
hasRetried: false,
hasCancelled: false,
},
]);
});
it('should not set a failed off-chain transaction as primary or initial, regardless of tx order', () => {
// Scenario:
// 1. You submit transaction A.
// 2. Transaction A fails off-chain (the network rejects it).
// 3. You submit transaction B.
// 4. Transaction A no longer has any visual representation in the UI.
// This is desired because we have no way currently to tell the intent
// of the transactions.
const txList = [
duplicateTx(SIMPLE_SEND_TX, { status: TransactionStatus.failed }),
duplicateTx(SIMPLE_SEND_TX, {
status: TransactionStatus.submitted,
time: 1,
}),
];
const state = getStateTree({ txList });
const result = nonceSortedTransactionsSelector(state);
expect(result).toStrictEqual([
{
nonce: '0x0',
transactions: txList,
initialTransaction: last(txList),
primaryTransaction: last(txList),
hasRetried: false,
hasCancelled: false,
},
]);
});
it('should set a failed on-chain transaction as primary', () => {
const txList = [
duplicateTx(SIMPLE_SEND_TX, { status: TransactionStatus.submitted }),
duplicateTx(RETRY_TX, {
status: TransactionStatus.failed,
txReceipt: { status: '0x0' },
time: 1,
}),
];
const state = getStateTree({ txList });
const result = nonceSortedTransactionsSelector(state);
expect(result).toStrictEqual([
{
nonce: '0x0',
transactions: txList,
initialTransaction: head(txList),
primaryTransaction: last(txList),
hasRetried: false,
hasCancelled: false,
},
]);
});
});