diff --git a/src/utils/security/__tests__/phishingProtection.test.ts b/src/utils/security/__tests__/phishingProtection.test.ts index d06dc5e9..785f489f 100644 --- a/src/utils/security/__tests__/phishingProtection.test.ts +++ b/src/utils/security/__tests__/phishingProtection.test.ts @@ -306,6 +306,174 @@ describe('PhishingProtection', () => { expect((PhishingProtection as any).levenshteinDistance('kitten', 'sitting')).toBe(3); expect((PhishingProtection as any).levenshteinDistance('', 'abc')).toBe(3); }); + + it('should handle two empty strings', () => { + expect((PhishingProtection as any).levenshteinDistance('', '')).toBe(0); + }); + + it('should handle one empty string', () => { + expect((PhishingProtection as any).levenshteinDistance('abc', '')).toBe(3); + }); + }); + + describe('decodeTransactionData', () => { + it('should decode standard transaction data into method selector and params', () => { + const data = '0xa9059cbb00000000000000000000000012345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000001'; + const result = (PhishingProtection as any).decodeTransactionData(data); + + expect(result).toBeDefined(); + expect(result.methodSelector).toBe('0xa9059cbb'); + expect(result.params).toBe(data.slice(10)); + expect(result.decoded).toBe(false); + }); + + it('should handle data with only method selector and no params', () => { + const data = '0xa9059cbb'; + const result = (PhishingProtection as any).decodeTransactionData(data); + + expect(result.methodSelector).toBe('0xa9059cbb'); + expect(result.params).toBe(''); + }); + + it('should handle empty data gracefully', () => { + const data = ''; + const result = (PhishingProtection as any).decodeTransactionData(data); + + expect(result.methodSelector).toBe(''); + expect(result.params).toBe(''); + }); + + it('should handle approvals (0x095ea7b3) correctly', () => { + const data = '0x095ea7b300000000000000000000000012345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000001'; + const result = (PhishingProtection as any).decodeTransactionData(data); + + expect(result.methodSelector).toBe('0x095ea7b3'); + expect(result.params.length).toBeGreaterThan(0); + }); + + it('should handle withdraw method signature', () => { + const data = '0x2e1a7d4d0000000000000000000000000000000000000000000000000000000000000010'; + const result = (PhishingProtection as any).decodeTransactionData(data); + + expect(result.methodSelector).toBe('0x2e1a7d4d'); + }); + }); + + describe('analyzeTransactionParameters', () => { + it('should return no warnings for normal decoded data', () => { + const decodedData = { + methodSelector: '0xa9059cbb', + params: '0000000000000000000000001234567890123456789012345678901234567890', + decoded: false, + }; + const result = (PhishingProtection as any).analyzeTransactionParameters(decodedData); + + expect(result.isMalicious).toBe(false); + expect(result.warnings).toHaveLength(0); + }); + + it('should flag unusually large parameter data as malicious', () => { + const largeParams = '0'.repeat(1001); + const decodedData = { + methodSelector: '0xa9059cbb', + params: largeParams, + decoded: false, + }; + const result = (PhishingProtection as any).analyzeTransactionParameters(decodedData); + + expect(result.isMalicious).toBe(true); + expect(result.warnings).toContain('Unusually large parameter data'); + }); + + it('should handle null or undefined decoded data', () => { + const resultNull = (PhishingProtection as any).analyzeTransactionParameters(null); + expect(resultNull.isMalicious).toBe(false); + expect(resultNull.warnings).toHaveLength(0); + + const resultUndefined = (PhishingProtection as any).analyzeTransactionParameters(undefined); + expect(resultUndefined.isMalicious).toBe(false); + expect(resultUndefined.warnings).toHaveLength(0); + }); + + it('should handle decoded data without params property', () => { + const decodedData = { + methodSelector: '0xa9059cbb', + decoded: false, + }; + const result = (PhishingProtection as any).analyzeTransactionParameters(decodedData); + + expect(result.isMalicious).toBe(false); + expect(result.warnings).toHaveLength(0); + }); + + it('should handle decoded data with empty params', () => { + const decodedData = { + methodSelector: '0xa9059cbb', + params: '', + decoded: false, + }; + const result = (PhishingProtection as any).analyzeTransactionParameters(decodedData); + + expect(result.isMalicious).toBe(false); + expect(result.warnings).toHaveLength(0); + }); + + it('should correctly bound false positives at exactly 1000 chars (boundary)', () => { + const exactlyAtBoundary = '0'.repeat(1000); + const decodedData = { + methodSelector: '0xa9059cbb', + params: exactlyAtBoundary, + decoded: false, + }; + const result = (PhishingProtection as any).analyzeTransactionParameters(decodedData); + + // 1000 chars is NOT > 1000, so should not be flagged + expect(result.isMalicious).toBe(false); + }); + }); + + describe('decodeTransactionData integration via validateTransactionData', () => { + it('should decode and analyze transaction data when validating', () => { + const largeParams = '0'.repeat(1001); + const largeData = `0xa9059cbb${largeParams}`; + + const result = PhishingProtection.validateTransactionData( + '0x742d35Cc6634C0532925a3b8D4C9db96C4b4Db45', + largeData + ); + + // transfer function is not automatically malicious, but large params are + expect(result.isMalicious).toBe(true); + expect(result.warnings).toContain('Suspicious function call detected'); + expect(result.warnings).toContain('Unusually large parameter data'); + expect(result.decodedData).toBeDefined(); + expect(result.decodedData.methodSelector).toBe('0xa9059cbb'); + }); + + it('should not mark transfer as malicious without large params', () => { + const result = PhishingProtection.validateTransactionData( + '0x742d35Cc6634C0532925a3b8D4C9db96C4b4Db45', + '0xa9059cbb00000000000000000000000012345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000001' + ); + + // transfer (0xa9059cbb) is suspicious but not automatically malicious + expect(result.isValid).toBe(true); + expect(result.warnings).toContain('Suspicious function call detected'); + expect(result.decodedData).toBeDefined(); + }); + + it('should handle invalid hex data without crashing', () => { + // The try/catch in validateTransactionData catches the decode error + const result = PhishingProtection.validateTransactionData( + '0x742d35Cc6634C0532925a3b8D4C9db96C4b4Db45', + 'not-hex-data' + ); + + expect(result.isValid).toBe(true); + expect(result.isMalicious).toBe(false); + // The decode happens inside the try block; if it throws, it should be caught + // and warnings will include 'Unable to decode transaction data' + }); }); }); }); \ No newline at end of file