Skip to content

Commit 47a8b66

Browse files
committed
chore: refactor validate transactions
1 parent 83516bf commit 47a8b66

4 files changed

Lines changed: 372 additions & 141 deletions

File tree

app/components/UI/Predict/controllers/PredictController.ts

Lines changed: 4 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
PredictTradeStatus,
4242
PredictTradeStatusValue,
4343
} from '../constants/eventNames';
44+
import { validateDepositTransactions } from '../utils/validateTransactions';
4445
import { PolymarketProvider } from '../providers/polymarket/PolymarketProvider';
4546
import {
4647
AccountState,
@@ -1883,130 +1884,9 @@ export class PredictController extends BaseController<
18831884
})),
18841885
});
18851886

1886-
const MIN_VALID_DATA_LENGTH = 10;
1887-
const VALID_ADDRESS_LENGTH = 42;
1888-
for (let i = 0; i < transactions.length; i++) {
1889-
const tx = transactions[i];
1890-
1891-
if (!tx) {
1892-
Logger.error(
1893-
new Error(
1894-
`Invalid transaction at index ${i}: transaction is null or undefined`,
1895-
),
1896-
this.getErrorContext('depositWithConfirmation', {
1897-
providerId: params.providerId,
1898-
transactionIndex: i,
1899-
}),
1900-
);
1901-
throw new Error(
1902-
`Invalid transaction: transaction at index ${i} is null or undefined`,
1903-
);
1904-
}
1905-
1906-
if (!tx.params) {
1907-
Logger.error(
1908-
new Error(
1909-
`Invalid transaction at index ${i}: params object is missing`,
1910-
),
1911-
this.getErrorContext('depositWithConfirmation', {
1912-
providerId: params.providerId,
1913-
transactionIndex: i,
1914-
transactionType: tx.type,
1915-
}),
1916-
);
1917-
throw new Error(
1918-
`Invalid transaction: transaction at index ${i} is missing params object`,
1919-
);
1920-
}
1921-
1922-
if (!tx.params.to) {
1923-
Logger.error(
1924-
new Error(
1925-
`Invalid transaction at index ${i}: 'to' address is missing`,
1926-
),
1927-
this.getErrorContext('depositWithConfirmation', {
1928-
providerId: params.providerId,
1929-
transactionIndex: i,
1930-
transactionType: tx.type,
1931-
}),
1932-
);
1933-
throw new Error(
1934-
`Invalid transaction: transaction at index ${i} is missing 'to' address`,
1935-
);
1936-
}
1937-
1938-
if (
1939-
typeof tx.params.to !== 'string' ||
1940-
!tx.params.to.startsWith('0x') ||
1941-
tx.params.to.length !== VALID_ADDRESS_LENGTH
1942-
) {
1943-
Logger.error(
1944-
new Error(
1945-
`Invalid transaction at index ${i}: 'to' address has invalid format (${tx.params.to})`,
1946-
),
1947-
this.getErrorContext('depositWithConfirmation', {
1948-
providerId: params.providerId,
1949-
transactionIndex: i,
1950-
transactionType: tx.type,
1951-
toAddress: tx.params.to,
1952-
}),
1953-
);
1954-
throw new Error(
1955-
`Invalid transaction: transaction at index ${i} has invalid 'to' address format`,
1956-
);
1957-
}
1958-
1959-
if (!tx.params.data) {
1960-
Logger.error(
1961-
new Error(`Invalid transaction at index ${i}: data is missing`),
1962-
this.getErrorContext('depositWithConfirmation', {
1963-
providerId: params.providerId,
1964-
transactionIndex: i,
1965-
transactionType: tx.type,
1966-
}),
1967-
);
1968-
throw new Error(
1969-
`Invalid transaction: transaction at index ${i} is missing data`,
1970-
);
1971-
}
1972-
1973-
if (
1974-
typeof tx.params.data !== 'string' ||
1975-
!tx.params.data.startsWith('0x')
1976-
) {
1977-
Logger.error(
1978-
new Error(
1979-
`Invalid transaction at index ${i}: data has invalid hex format`,
1980-
),
1981-
this.getErrorContext('depositWithConfirmation', {
1982-
providerId: params.providerId,
1983-
transactionIndex: i,
1984-
transactionType: tx.type,
1985-
dataPrefix: tx.params.data?.slice?.(0, 10),
1986-
}),
1987-
);
1988-
throw new Error(
1989-
`Invalid transaction: transaction at index ${i} has invalid data format (must be hex string starting with 0x)`,
1990-
);
1991-
}
1992-
1993-
if (tx.params.data.length < MIN_VALID_DATA_LENGTH) {
1994-
Logger.error(
1995-
new Error(
1996-
`Invalid transaction at index ${i}: data length ${tx.params.data.length} is less than minimum ${MIN_VALID_DATA_LENGTH}`,
1997-
),
1998-
this.getErrorContext('depositWithConfirmation', {
1999-
providerId: params.providerId,
2000-
transactionIndex: i,
2001-
transactionType: tx.type,
2002-
dataLength: tx.params.data.length,
2003-
}),
2004-
);
2005-
throw new Error(
2006-
`Invalid transaction: transaction at index ${i} has insufficient data (length: ${tx.params.data.length}, minimum: ${MIN_VALID_DATA_LENGTH})`,
2007-
);
2008-
}
2009-
}
1887+
validateDepositTransactions(transactions, {
1888+
providerId: params.providerId,
1889+
});
20101890

20111891
const { NetworkController } = Engine.context;
20121892
const networkClientId =

app/components/UI/Predict/providers/polymarket/safe/utils.test.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,22 @@ describe('safe utils', () => {
495495
expect(consoleErrorSpy).toHaveBeenCalled();
496496
consoleErrorSpy.mockRestore();
497497
});
498+
499+
it('throws error with "Unknown error" when non-Error is thrown', async () => {
500+
const signer = buildSigner();
501+
mockSignTypedMessage.mockRejectedValue('string error');
502+
const consoleErrorSpy = jest
503+
.spyOn(console, 'error')
504+
.mockImplementation(() => {
505+
// Mock implementation to suppress console output
506+
});
507+
508+
await expect(getDeployProxyWalletTransaction({ signer })).rejects.toThrow(
509+
'Failed to generate deploy proxy wallet transaction: Unknown error',
510+
);
511+
512+
consoleErrorSpy.mockRestore();
513+
});
498514
});
499515

500516
describe('checkProxyWalletDeployed', () => {
@@ -991,10 +1007,14 @@ describe('safe utils', () => {
9911007
const signer = buildSigner();
9921008

9931009
mockNetworkController();
994-
mockQuery.mockResolvedValueOnce(
995-
'0x0000000000000000000000000000000000000000000000000000000000000001',
996-
);
997-
mockSignPersonalMessage.mockRejectedValue(
1010+
mockQuery
1011+
.mockResolvedValueOnce(
1012+
'0x0000000000000000000000000000000000000000000000000000000000000001',
1013+
)
1014+
.mockResolvedValueOnce(
1015+
'0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd',
1016+
);
1017+
mockSignPersonalMessage.mockRejectedValueOnce(
9981018
new Error('User rejected signing'),
9991019
);
10001020

@@ -1004,19 +1024,6 @@ describe('safe utils', () => {
10041024
'Failed to generate proxy wallet allowances transaction: User rejected signing',
10051025
);
10061026
});
1007-
1008-
it('throws error when call data generation fails', async () => {
1009-
const signer = buildSigner();
1010-
1011-
mockNetworkController();
1012-
mockQuery.mockRejectedValue(new Error('Network error'));
1013-
1014-
await expect(
1015-
getProxyWalletAllowancesTransaction({ signer }),
1016-
).rejects.toThrow(
1017-
'Failed to generate proxy wallet allowances transaction: Network error',
1018-
);
1019-
});
10201027
});
10211028

10221029
describe('getClaimTransaction', () => {
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import { TransactionType } from '@metamask/transaction-controller';
2+
import { Hex } from '@metamask/utils';
3+
import Logger from '../../../../util/Logger';
4+
import { validateDepositTransactions } from './validateTransactions';
5+
6+
jest.mock('../../../../util/Logger', () => ({
7+
error: jest.fn(),
8+
}));
9+
10+
const mockLoggerError = Logger.error as jest.Mock;
11+
12+
const VALID_ADDRESS = '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174' as Hex;
13+
const VALID_DATA = '0x095ea7b3000000000000000000000000' as Hex;
14+
15+
function createValidTransaction(overrides = {}) {
16+
return {
17+
params: {
18+
to: VALID_ADDRESS,
19+
data: VALID_DATA,
20+
},
21+
type: TransactionType.contractInteraction,
22+
...overrides,
23+
};
24+
}
25+
26+
describe('validateDepositTransactions', () => {
27+
const context = { providerId: 'polymarket' };
28+
29+
beforeEach(() => {
30+
jest.clearAllMocks();
31+
});
32+
33+
it('passes validation for valid transactions', () => {
34+
const transactions = [createValidTransaction(), createValidTransaction()];
35+
36+
expect(() =>
37+
validateDepositTransactions(transactions, context),
38+
).not.toThrow();
39+
expect(mockLoggerError).not.toHaveBeenCalled();
40+
});
41+
42+
it('throws error when transaction is null', () => {
43+
const transactions = [null as never];
44+
45+
expect(() => validateDepositTransactions(transactions, context)).toThrow(
46+
'Invalid transaction: transaction at index 0 is null or undefined',
47+
);
48+
expect(mockLoggerError).toHaveBeenCalled();
49+
});
50+
51+
it('throws error when transaction is undefined', () => {
52+
const transactions = [undefined as never];
53+
54+
expect(() => validateDepositTransactions(transactions, context)).toThrow(
55+
'Invalid transaction: transaction at index 0 is null or undefined',
56+
);
57+
expect(mockLoggerError).toHaveBeenCalled();
58+
});
59+
60+
it('throws error when params object is missing', () => {
61+
const transactions = [
62+
{ type: TransactionType.contractInteraction } as never,
63+
];
64+
65+
expect(() => validateDepositTransactions(transactions, context)).toThrow(
66+
'Invalid transaction: transaction at index 0 is missing params object',
67+
);
68+
expect(mockLoggerError).toHaveBeenCalled();
69+
});
70+
71+
it('throws error when to address is missing', () => {
72+
const transactions = [
73+
createValidTransaction({ params: { data: VALID_DATA } }),
74+
];
75+
76+
expect(() => validateDepositTransactions(transactions, context)).toThrow(
77+
"Invalid transaction: transaction at index 0 is missing 'to' address",
78+
);
79+
expect(mockLoggerError).toHaveBeenCalled();
80+
});
81+
82+
it('throws error when to address is too short', () => {
83+
const transactions = [
84+
createValidTransaction({ params: { to: '0xshort', data: VALID_DATA } }),
85+
];
86+
87+
expect(() => validateDepositTransactions(transactions, context)).toThrow(
88+
"Invalid transaction: transaction at index 0 has invalid 'to' address format",
89+
);
90+
expect(mockLoggerError).toHaveBeenCalled();
91+
});
92+
93+
it('throws error when to address does not start with 0x', () => {
94+
const transactions = [
95+
createValidTransaction({
96+
params: {
97+
to: '1234567890123456789012345678901234567890ab',
98+
data: VALID_DATA,
99+
},
100+
}),
101+
];
102+
103+
expect(() => validateDepositTransactions(transactions, context)).toThrow(
104+
"Invalid transaction: transaction at index 0 has invalid 'to' address format",
105+
);
106+
expect(mockLoggerError).toHaveBeenCalled();
107+
});
108+
109+
it('throws error when data is missing', () => {
110+
const transactions = [
111+
createValidTransaction({ params: { to: VALID_ADDRESS } }),
112+
];
113+
114+
expect(() => validateDepositTransactions(transactions, context)).toThrow(
115+
'Invalid transaction: transaction at index 0 is missing data',
116+
);
117+
expect(mockLoggerError).toHaveBeenCalled();
118+
});
119+
120+
it('throws error when data does not start with 0x', () => {
121+
const transactions = [
122+
createValidTransaction({
123+
params: { to: VALID_ADDRESS, data: 'not-hex-data' },
124+
}),
125+
];
126+
127+
expect(() => validateDepositTransactions(transactions, context)).toThrow(
128+
'Invalid transaction: transaction at index 0 has invalid data format (must be hex string starting with 0x)',
129+
);
130+
expect(mockLoggerError).toHaveBeenCalled();
131+
});
132+
133+
it('throws error when data is too short', () => {
134+
const transactions = [
135+
createValidTransaction({ params: { to: VALID_ADDRESS, data: '0x1234' } }),
136+
];
137+
138+
expect(() => validateDepositTransactions(transactions, context)).toThrow(
139+
'Invalid transaction: transaction at index 0 has insufficient data (length: 6, minimum: 10)',
140+
);
141+
expect(mockLoggerError).toHaveBeenCalled();
142+
});
143+
144+
it('validates all transactions in array', () => {
145+
const transactions = [
146+
createValidTransaction(),
147+
createValidTransaction({ params: { to: VALID_ADDRESS, data: '0x1234' } }),
148+
];
149+
150+
expect(() => validateDepositTransactions(transactions, context)).toThrow(
151+
'Invalid transaction: transaction at index 1 has insufficient data (length: 6, minimum: 10)',
152+
);
153+
});
154+
155+
it('logs error with correct context', () => {
156+
const transactions = [
157+
{ type: TransactionType.contractInteraction } as never,
158+
];
159+
160+
expect(() => validateDepositTransactions(transactions, context)).toThrow();
161+
162+
expect(mockLoggerError).toHaveBeenCalledWith(
163+
expect.any(Error),
164+
expect.objectContaining({
165+
tags: expect.objectContaining({
166+
feature: 'Predict',
167+
provider: 'polymarket',
168+
}),
169+
context: expect.objectContaining({
170+
name: 'PredictController',
171+
data: expect.objectContaining({
172+
method: 'depositWithConfirmation',
173+
providerId: 'polymarket',
174+
transactionIndex: 0,
175+
}),
176+
}),
177+
}),
178+
);
179+
});
180+
});

0 commit comments

Comments
 (0)