Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/phishing-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Fixed the Transaction Controller listener to correctly pick up state changes for mobile ([#7139](https://github.com/MetaMask/core/pull/7139))

## [15.0.0]

### Changed
Expand Down
319 changes: 170 additions & 149 deletions packages/phishing-controller/src/PhishingController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3600,194 +3600,215 @@ describe('URL Scan Cache', () => {
`);
});
});
});

describe('Transaction Controller State Change Integration', () => {
let controller: PhishingController;
let globalMessenger: RootMessenger;
let bulkScanTokensSpy: jest.SpyInstance;
describe('Transaction Controller State Change Integration', () => {
let controller: PhishingController;
let globalMessenger: RootMessenger;
let bulkScanTokensSpy: jest.SpyInstance;

beforeEach(() => {
const { messenger, rootMessenger } = setupMessenger();
beforeEach(() => {
const { messenger, rootMessenger } = setupMessenger();

globalMessenger = rootMessenger;
globalMessenger = rootMessenger;

controller = new PhishingController({
messenger,
});

bulkScanTokensSpy = jest
.spyOn(controller, 'bulkScanTokens')
.mockResolvedValue({});
controller = new PhishingController({
messenger,
});

afterEach(() => {
bulkScanTokensSpy.mockRestore();
});
bulkScanTokensSpy = jest
.spyOn(controller, 'bulkScanTokens')
.mockResolvedValue({});
});

it('should trigger bulk token scanning when transaction with token balance changes is added', async () => {
const mockTransaction = createMockTransaction('test-tx-1', [
TEST_ADDRESSES.USDC,
TEST_ADDRESSES.MOCK_TOKEN_1,
]);
const stateChangePayload = createMockStateChangePayload([
mockTransaction,
]);
afterEach(() => {
bulkScanTokensSpy.mockRestore();
});

globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0],
value: mockTransaction,
},
],
);
it('triggers bulk token scanning when transaction with token balance changes is added', async () => {
const mockTransaction = createMockTransaction('test-tx-1', [
TEST_ADDRESSES.USDC,
TEST_ADDRESSES.MOCK_TOKEN_1,
]);
const stateChangePayload = createMockStateChangePayload([mockTransaction]);

globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0],
value: mockTransaction,
},
],
);

await new Promise(process.nextTick);
await new Promise(process.nextTick);

expect(bulkScanTokensSpy).toHaveBeenCalledWith({
chainId: mockTransaction.chainId.toLowerCase(),
tokens: [
TEST_ADDRESSES.USDC.toLowerCase(),
TEST_ADDRESSES.MOCK_TOKEN_1.toLowerCase(),
],
});
expect(bulkScanTokensSpy).toHaveBeenCalledWith({
chainId: mockTransaction.chainId.toLowerCase(),
tokens: [
TEST_ADDRESSES.USDC.toLowerCase(),
TEST_ADDRESSES.MOCK_TOKEN_1.toLowerCase(),
],
});
});

it('should skip processing when patch operation is remove', async () => {
const mockTransaction = createMockTransaction('test-tx-1', [
TEST_ADDRESSES.USDC,
]);
it('triggers bulk token scanning when patch path includes simulationData', async () => {
const mockTransaction = createMockTransaction('test-tx-1', [
TEST_ADDRESSES.USDC,
TEST_ADDRESSES.MOCK_TOKEN_1,
]);
const stateChangePayload = createMockStateChangePayload([mockTransaction]);

globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0, 'simulationData'],
value: mockTransaction.simulationData,
},
],
);
await new Promise(process.nextTick);

const stateChangePayload = createMockStateChangePayload([]);
expect(bulkScanTokensSpy).toHaveBeenCalledWith({
chainId: mockTransaction.chainId.toLowerCase(),
tokens: [
TEST_ADDRESSES.USDC.toLowerCase(),
TEST_ADDRESSES.MOCK_TOKEN_1.toLowerCase(),
],
});
});

globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'remove' as const,
path: ['transactions', 0],
value: mockTransaction,
},
],
);
it('skips processing when patch operation is remove', async () => {
const mockTransaction = createMockTransaction('test-tx-1', [
TEST_ADDRESSES.USDC,
]);

await new Promise(process.nextTick);
const stateChangePayload = createMockStateChangePayload([]);

expect(bulkScanTokensSpy).not.toHaveBeenCalled();
});
globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'remove' as const,
path: ['transactions', 0],
value: mockTransaction,
},
],
);

it('should not trigger bulk token scanning when transaction has no token balance changes', async () => {
const mockTransaction = createMockTransaction('test-tx-1', []);
await new Promise(process.nextTick);

const stateChangePayload = createMockStateChangePayload([
mockTransaction,
]);
expect(bulkScanTokensSpy).not.toHaveBeenCalled();
});

globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0],
value: mockTransaction,
},
],
);
it('does not trigger bulk token scanning when transaction has no token balance changes', async () => {
const mockTransaction = createMockTransaction('test-tx-1', []);

await new Promise(process.nextTick);
const stateChangePayload = createMockStateChangePayload([mockTransaction]);

expect(bulkScanTokensSpy).not.toHaveBeenCalled();
});
globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0],
value: mockTransaction,
},
],
);

it('should not trigger bulk token scanning when using default tokenAddresses parameter', async () => {
const mockTransaction = createMockTransaction('test-tx-2');
await new Promise(process.nextTick);

const stateChangePayload = createMockStateChangePayload([
mockTransaction,
]);
expect(bulkScanTokensSpy).not.toHaveBeenCalled();
});

globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0],
value: mockTransaction,
},
],
);
it('does not trigger bulk token scanning when using default tokenAddresses parameter', async () => {
const mockTransaction = createMockTransaction('test-tx-2');

await new Promise(process.nextTick);
const stateChangePayload = createMockStateChangePayload([mockTransaction]);

expect(bulkScanTokensSpy).not.toHaveBeenCalled();
});
globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0],
value: mockTransaction,
},
],
);

it('should handle errors in transaction state change processing', async () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
await new Promise(process.nextTick);

const stateChangePayload = createMockStateChangePayload([]);
expect(bulkScanTokensSpy).not.toHaveBeenCalled();
});

globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0],
value: null,
},
],
);
it('handles errors in transaction state change processing', async () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();

await new Promise(process.nextTick);
const stateChangePayload = createMockStateChangePayload([]);

expect(consoleErrorSpy).toHaveBeenCalledWith(
'Error processing transaction state change:',
expect.any(Error),
);
globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0],
value: null,
},
],
);

consoleErrorSpy.mockRestore();
});
await new Promise(process.nextTick);

it('should handle errors in bulk token scanning', async () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
expect(consoleErrorSpy).toHaveBeenCalledWith(
'Error processing transaction state change:',
expect.any(Error),
);

bulkScanTokensSpy.mockRejectedValue(new Error('Scanning failed'));
consoleErrorSpy.mockRestore();
});

const mockTransaction = createMockTransaction('test-tx-1', [
TEST_ADDRESSES.USDC,
]);
it('handles errors in bulk token scanning', async () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();

const stateChangePayload = createMockStateChangePayload([
mockTransaction,
]);
bulkScanTokensSpy.mockRejectedValue(new Error('Scanning failed'));

globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0],
value: mockTransaction,
},
],
);
const mockTransaction = createMockTransaction('test-tx-1', [
TEST_ADDRESSES.USDC,
]);

await new Promise(process.nextTick);
const stateChangePayload = createMockStateChangePayload([mockTransaction]);

expect(consoleErrorSpy).toHaveBeenCalledWith(
'Error scanning tokens for chain 0x1:',
expect.any(Error),
);
globalMessenger.publish(
'TransactionController:stateChange',
stateChangePayload,
[
{
op: 'add' as const,
path: ['transactions', 0],
value: mockTransaction,
},
],
);

consoleErrorSpy.mockRestore();
});
await new Promise(process.nextTick);

expect(consoleErrorSpy).toHaveBeenCalledWith(
'Error scanning tokens for chain 0x1:',
expect.any(Error),
);

consoleErrorSpy.mockRestore();
});
});
Loading