211 lines
7.0 KiB
TypeScript
211 lines
7.0 KiB
TypeScript
|
|
/**
|
||
|
|
* Integration tests for multi-sig approval flow
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { validateAddress } from "../../utils/security";
|
||
|
|
|
||
|
|
describe("Multi-Sig Approval Integration Tests", () => {
|
||
|
|
describe("Approval Flow", () => {
|
||
|
|
it("should require threshold approvals before execution", () => {
|
||
|
|
const threshold = 2;
|
||
|
|
const owners = [
|
||
|
|
"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
|
||
|
|
"0x8ba1f109551bD432803012645Hac136c22C9e8",
|
||
|
|
"0x9ba1f109551bD432803012645Hac136c22C9e9",
|
||
|
|
];
|
||
|
|
const approvals: Array<{ approver: string; approved: boolean; timestamp: number }> = [];
|
||
|
|
|
||
|
|
// First approval
|
||
|
|
const approver1 = owners[0];
|
||
|
|
const validation1 = validateAddress(approver1);
|
||
|
|
expect(validation1.valid).toBe(true);
|
||
|
|
|
||
|
|
approvals.push({
|
||
|
|
approver: validation1.checksummed!,
|
||
|
|
approved: true,
|
||
|
|
timestamp: Date.now(),
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(approvals.filter(a => a.approved).length).toBe(1);
|
||
|
|
expect(approvals.filter(a => a.approved).length).toBeLessThan(threshold);
|
||
|
|
|
||
|
|
// Second approval
|
||
|
|
const approver2 = owners[1];
|
||
|
|
const validation2 = validateAddress(approver2);
|
||
|
|
expect(validation2.valid).toBe(true);
|
||
|
|
|
||
|
|
approvals.push({
|
||
|
|
approver: validation2.checksummed!,
|
||
|
|
approved: true,
|
||
|
|
timestamp: Date.now(),
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(approvals.filter(a => a.approved).length).toBe(2);
|
||
|
|
expect(approvals.filter(a => a.approved).length).toBeGreaterThanOrEqual(threshold);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should verify approver is a wallet owner", () => {
|
||
|
|
const owners = [
|
||
|
|
"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
|
||
|
|
"0x8ba1f109551bD432803012645Hac136c22C9e8",
|
||
|
|
];
|
||
|
|
const approver = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb";
|
||
|
|
const unauthorizedApprover = "0x9ba1f109551bD432803012645Hac136c22C9e9";
|
||
|
|
|
||
|
|
// Valid approver
|
||
|
|
const isOwner1 = owners.some(
|
||
|
|
o => o.toLowerCase() === approver.toLowerCase()
|
||
|
|
);
|
||
|
|
expect(isOwner1).toBe(true);
|
||
|
|
|
||
|
|
// Invalid approver
|
||
|
|
const isOwner2 = owners.some(
|
||
|
|
o => o.toLowerCase() === unauthorizedApprover.toLowerCase()
|
||
|
|
);
|
||
|
|
expect(isOwner2).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should prevent duplicate approvals from same owner", () => {
|
||
|
|
const approver = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb";
|
||
|
|
const approvals: Array<{ approver: string; approved: boolean }> = [];
|
||
|
|
|
||
|
|
// First approval
|
||
|
|
approvals.push({ approver, approved: true });
|
||
|
|
|
||
|
|
// Check for duplicate
|
||
|
|
const alreadyApproved = approvals.some(
|
||
|
|
a => a.approver.toLowerCase() === approver.toLowerCase() && a.approved
|
||
|
|
);
|
||
|
|
expect(alreadyApproved).toBe(true);
|
||
|
|
|
||
|
|
// Should not allow duplicate approval
|
||
|
|
if (alreadyApproved) {
|
||
|
|
// In real implementation, this would throw an error
|
||
|
|
expect(true).toBe(true);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should handle mixed approvals and rejections", () => {
|
||
|
|
const owners = [
|
||
|
|
"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
|
||
|
|
"0x8ba1f109551bD432803012645Hac136c22C9e8",
|
||
|
|
"0x9ba1f109551bD432803012645Hac136c22C9e9",
|
||
|
|
];
|
||
|
|
const threshold = 2;
|
||
|
|
const approvals: Array<{ approver: string; approved: boolean }> = [];
|
||
|
|
|
||
|
|
// First approval
|
||
|
|
approvals.push({ approver: owners[0], approved: true });
|
||
|
|
expect(approvals.filter(a => a.approved).length).toBe(1);
|
||
|
|
|
||
|
|
// Second rejection
|
||
|
|
approvals.push({ approver: owners[1], approved: false });
|
||
|
|
expect(approvals.filter(a => a.approved).length).toBe(1);
|
||
|
|
|
||
|
|
// Third approval
|
||
|
|
approvals.push({ approver: owners[2], approved: true });
|
||
|
|
expect(approvals.filter(a => a.approved).length).toBe(2);
|
||
|
|
expect(approvals.filter(a => a.approved).length).toBeGreaterThanOrEqual(threshold);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Race Condition Prevention", () => {
|
||
|
|
it("should prevent concurrent approvals with locks", () => {
|
||
|
|
const transactionId = "tx_123";
|
||
|
|
const locks = new Map<string, boolean>();
|
||
|
|
|
||
|
|
// Simulate concurrent approval attempts
|
||
|
|
const attempt1 = () => {
|
||
|
|
if (locks.get(transactionId)) {
|
||
|
|
return false; // Locked
|
||
|
|
}
|
||
|
|
locks.set(transactionId, true);
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
|
||
|
|
const attempt2 = () => {
|
||
|
|
if (locks.get(transactionId)) {
|
||
|
|
return false; // Locked
|
||
|
|
}
|
||
|
|
locks.set(transactionId, true);
|
||
|
|
return true;
|
||
|
|
};
|
||
|
|
|
||
|
|
// First attempt succeeds
|
||
|
|
expect(attempt1()).toBe(true);
|
||
|
|
|
||
|
|
// Second attempt fails (locked)
|
||
|
|
expect(attempt2()).toBe(false);
|
||
|
|
|
||
|
|
// Release lock
|
||
|
|
locks.delete(transactionId);
|
||
|
|
|
||
|
|
// Now second attempt can succeed
|
||
|
|
expect(attempt2()).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should handle approval order correctly", () => {
|
||
|
|
const approvals: Array<{ approver: string; timestamp: number }> = [];
|
||
|
|
const threshold = 2;
|
||
|
|
|
||
|
|
// Simulate rapid approvals
|
||
|
|
const approver1 = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb";
|
||
|
|
const approver2 = "0x8ba1f109551bD432803012645Hac136c22C9e8";
|
||
|
|
|
||
|
|
approvals.push({ approver: approver1, timestamp: Date.now() });
|
||
|
|
approvals.push({ approver: approver2, timestamp: Date.now() + 1 });
|
||
|
|
|
||
|
|
// Should maintain order
|
||
|
|
expect(approvals.length).toBe(2);
|
||
|
|
expect(approvals[0].approver).toBe(approver1);
|
||
|
|
expect(approvals[1].approver).toBe(approver2);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("Threshold Validation", () => {
|
||
|
|
it("should validate threshold before allowing execution", () => {
|
||
|
|
const threshold = 2;
|
||
|
|
const approvals = [
|
||
|
|
{ approver: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", approved: true },
|
||
|
|
{ approver: "0x8ba1f109551bD432803012645Hac136c22C9e8", approved: true },
|
||
|
|
];
|
||
|
|
|
||
|
|
const approvalCount = approvals.filter(a => a.approved).length;
|
||
|
|
expect(approvalCount).toBeGreaterThanOrEqual(threshold);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should reject execution with insufficient approvals", () => {
|
||
|
|
const threshold = 2;
|
||
|
|
const approvals = [
|
||
|
|
{ approver: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", approved: true },
|
||
|
|
];
|
||
|
|
|
||
|
|
const approvalCount = approvals.filter(a => a.approved).length;
|
||
|
|
expect(approvalCount).toBeLessThan(threshold);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should allow execution with exact threshold", () => {
|
||
|
|
const threshold = 2;
|
||
|
|
const approvals = [
|
||
|
|
{ approver: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", approved: true },
|
||
|
|
{ approver: "0x8ba1f109551bD432803012645Hac136c22C9e8", approved: true },
|
||
|
|
];
|
||
|
|
|
||
|
|
const approvalCount = approvals.filter(a => a.approved).length;
|
||
|
|
expect(approvalCount).toBe(threshold);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("should allow execution with more than threshold", () => {
|
||
|
|
const threshold = 2;
|
||
|
|
const approvals = [
|
||
|
|
{ approver: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", approved: true },
|
||
|
|
{ approver: "0x8ba1f109551bD432803012645Hac136c22C9e8", approved: true },
|
||
|
|
{ approver: "0x9ba1f109551bD432803012645Hac136c22C9e9", approved: true },
|
||
|
|
];
|
||
|
|
|
||
|
|
const approvalCount = approvals.filter(a => a.approved).length;
|
||
|
|
expect(approvalCount).toBeGreaterThan(threshold);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|