/** * Integration tests for transaction flow */ import { TransactionContext } from "../../contexts/TransactionContext"; import { validateTransactionRequest, validateAddress, validateTransactionValue, validateTransactionData, RateLimiter, } from "../../utils/security"; import { TransactionExecutionMethod, TransactionStatus } from "../../types"; import { ethers } from "ethers"; import { TEST_ADDRESSES } from "../test-constants"; // Mock provider class MockProvider extends ethers.providers.BaseProvider { constructor() { super(ethers.providers.getNetwork(1)); // Mainnet network } async estimateGas(tx: any) { return ethers.BigNumber.from("21000"); } async getFeeData() { return { gasPrice: ethers.BigNumber.from("20000000000"), // 20 gwei maxFeePerGas: ethers.BigNumber.from("30000000000"), maxPriorityFeePerGas: ethers.BigNumber.from("2000000000"), lastBaseFeePerGas: ethers.BigNumber.from("28000000000"), // Required for FeeData type }; } async getTransactionCount(address: string) { return 0; } async perform(method: string, params: any): Promise { throw new Error("Not implemented"); } } describe("Transaction Flow Integration Tests", () => { let provider: MockProvider; let rateLimiter: RateLimiter; beforeEach(() => { provider = new MockProvider(); rateLimiter = new RateLimiter(10, 60000); if (typeof window !== "undefined") { localStorage.clear(); } }); describe("Transaction Creation Flow", () => { it("should create valid transaction", () => { const tx = { from: TEST_ADDRESSES.ADDRESS_1, to: TEST_ADDRESSES.ADDRESS_2, value: "1000000000000000000", // 1 ETH data: "0x", }; const validation = validateTransactionRequest(tx); expect(validation.valid).toBe(true); expect(validation.errors.length).toBe(0); }); it("should reject transaction with invalid from address", () => { const tx = { from: "invalid-address", to: TEST_ADDRESSES.ADDRESS_2, value: "1000000000000000000", data: "0x", }; const validation = validateTransactionRequest(tx); expect(validation.valid).toBe(false); expect(validation.errors.length).toBeGreaterThan(0); }); it("should reject transaction with invalid to address", () => { const tx = { from: TEST_ADDRESSES.ADDRESS_1, to: "invalid-address", value: "1000000000000000000", data: "0x", }; const validation = validateTransactionRequest(tx); expect(validation.valid).toBe(false); expect(validation.errors.length).toBeGreaterThan(0); }); it("should reject transaction with invalid value", () => { const tx = { from: TEST_ADDRESSES.ADDRESS_1, to: TEST_ADDRESSES.ADDRESS_2, value: "1000000000000000000000001", // > 1M ETH data: "0x", }; const valueValidation = validateTransactionValue(tx.value); expect(valueValidation.valid).toBe(false); }); it("should reject transaction with invalid data", () => { const tx = { from: TEST_ADDRESSES.ADDRESS_1, to: TEST_ADDRESSES.ADDRESS_2, value: "0", data: "0x" + "a".repeat(10001), // Too large }; const dataValidation = validateTransactionData(tx.data); expect(dataValidation.valid).toBe(false); }); it("should enforce rate limiting", () => { const key = TEST_ADDRESSES.ADDRESS_1; // Make 10 requests (at limit) for (let i = 0; i < 10; i++) { expect(rateLimiter.checkLimit(key)).toBe(true); } // 11th request should be rejected expect(rateLimiter.checkLimit(key)).toBe(false); }); }); describe("Transaction Approval Flow", () => { it("should track approvals correctly", () => { const transactionId = "tx_123"; const approvals: Array<{ approver: string; approved: boolean }> = []; const threshold = 2; // First approval approvals.push({ approver: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", approved: true, }); expect(approvals.filter(a => a.approved).length).toBe(1); expect(approvals.filter(a => a.approved).length).toBeLessThan(threshold); // Second approval approvals.push({ approver: "0x8ba1f109551bD432803012645Hac136c22C9e8", approved: true, }); expect(approvals.filter(a => a.approved).length).toBe(2); expect(approvals.filter(a => a.approved).length).toBeGreaterThanOrEqual(threshold); }); it("should prevent duplicate approvals", () => { const approvals: Array<{ approver: string; approved: boolean }> = []; const approver = TEST_ADDRESSES.ADDRESS_1; // First approval approvals.push({ approver, approved: true }); // Check for duplicate const isDuplicate = approvals.some( a => a.approver.toLowerCase() === approver.toLowerCase() && a.approved ); expect(isDuplicate).toBe(true); // Should not allow duplicate if (isDuplicate) { // In real implementation, this would throw an error expect(true).toBe(true); } }); it("should handle rejection", () => { const approvals: Array<{ approver: string; approved: boolean }> = []; approvals.push({ approver: TEST_ADDRESSES.ADDRESS_1, approved: false, }); expect(approvals.filter(a => a.approved).length).toBe(0); }); }); describe("Transaction Execution Flow", () => { it("should estimate gas correctly", async () => { const tx = { from: TEST_ADDRESSES.ADDRESS_1, to: TEST_ADDRESSES.ADDRESS_2, value: "1000000000000000000", data: "0x", }; const gasEstimate = await provider.estimateGas(tx); expect(gasEstimate.gt(0)).toBe(true); expect(gasEstimate.gte(21000)).toBe(true); // Minimum gas }); it("should get fee data", async () => { const feeData = await provider.getFeeData(); expect(feeData.gasPrice).toBeDefined(); expect(feeData.gasPrice!.gt(0)).toBe(true); }); it("should validate transaction before execution", () => { // Use valid Ethereum addresses from test constants const tx = { from: TEST_ADDRESSES.ADDRESS_1, to: TEST_ADDRESSES.ADDRESS_2, value: "1000000000000000000", data: "0x", }; const validation = validateTransactionRequest(tx); expect(validation.valid).toBe(true); // Transaction should only execute if valid if (validation.valid) { expect(true).toBe(true); // Would proceed to execution } }); }); describe("Transaction Deduplication", () => { it("should detect duplicate transactions", () => { // Use valid Ethereum addresses from test constants const from = TEST_ADDRESSES.ADDRESS_1; const to = TEST_ADDRESSES.ADDRESS_2; const tx1 = { from, to, value: "1000000000000000000", data: "0x", nonce: 0, }; const tx2 = { from, to, value: "1000000000000000000", data: "0x", nonce: 0, }; // Generate hash for comparison const hash1 = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( ["address", "address", "uint256", "bytes", "uint256"], [tx1.from, tx1.to, tx1.value, tx1.data, tx1.nonce] ) ); const hash2 = ethers.utils.keccak256( ethers.utils.defaultAbiCoder.encode( ["address", "address", "uint256", "bytes", "uint256"], [tx2.from, tx2.to, tx2.value, tx2.data, tx2.nonce] ) ); expect(hash1).toBe(hash2); // Same transaction }); }); });