@waku/rln
Version:
RLN (Rate Limiting Nullifier) implementation for Waku
118 lines • 4.6 kB
JavaScript
import { hexToBytes } from "@waku/utils/bytes";
import { expect } from "chai";
import * as ethers from "ethers";
import sinon from "sinon";
import { DEFAULT_RATE_LIMIT, RLN_CONTRACT } from "./constants.js";
export const mockRateLimits = {
minRate: 20,
maxRate: 600,
maxTotalRate: 1200,
currentTotalRate: 500
};
export function createMockProvider() {
return {
getLogs: () => [],
getBlockNumber: () => Promise.resolve(1000),
getNetwork: () => Promise.resolve({ chainId: 11155111 })
};
}
export function createMockFilters() {
return {
MembershipRegistered: () => ({ address: RLN_CONTRACT.address }),
MembershipErased: () => ({ address: RLN_CONTRACT.address }),
MembershipExpired: () => ({ address: RLN_CONTRACT.address })
};
}
export function createMockRegistryContract(overrides = {}) {
const filters = {
MembershipRegistered: () => ({ address: RLN_CONTRACT.address }),
MembershipErased: () => ({ address: RLN_CONTRACT.address }),
MembershipExpired: () => ({ address: RLN_CONTRACT.address })
};
const baseContract = {
minMembershipRateLimit: () => Promise.resolve(ethers.BigNumber.from(mockRateLimits.minRate)),
maxMembershipRateLimit: () => Promise.resolve(ethers.BigNumber.from(mockRateLimits.maxRate)),
maxTotalRateLimit: () => Promise.resolve(ethers.BigNumber.from(mockRateLimits.maxTotalRate)),
currentTotalRateLimit: () => Promise.resolve(ethers.BigNumber.from(mockRateLimits.currentTotalRate)),
queryFilter: () => [],
provider: createMockProvider(),
filters,
on: () => ({}),
removeAllListeners: () => ({}),
register: () => ({
wait: () => Promise.resolve({
events: [mockRLNRegisteredEvent()]
})
}),
estimateGas: {
register: () => Promise.resolve(ethers.BigNumber.from(100000))
},
functions: {
register: () => Promise.resolve()
},
getMemberIndex: () => Promise.resolve(null),
interface: {
getEvent: (eventName) => ({
name: eventName,
format: () => { }
})
},
address: RLN_CONTRACT.address
};
// Merge overrides while preserving filters
const merged = {
...baseContract,
...overrides,
filters: { ...filters, ...(overrides.filters || {}) }
};
return merged;
}
export function mockRLNRegisteredEvent(idCommitment) {
return {
args: {
idCommitment: idCommitment ||
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
membershipRateLimit: ethers.BigNumber.from(DEFAULT_RATE_LIMIT),
index: ethers.BigNumber.from(1)
},
event: "MembershipRegistered"
};
}
export function formatIdCommitment(idCommitmentBigInt) {
return "0x" + idCommitmentBigInt.toString(16).padStart(64, "0");
}
export function createRegisterStub(identity) {
return sinon.stub().callsFake(() => ({
wait: () => Promise.resolve({
events: [
{
event: "MembershipRegistered",
args: {
idCommitment: formatIdCommitment(identity.IDCommitmentBigInt),
membershipRateLimit: ethers.BigNumber.from(DEFAULT_RATE_LIMIT),
index: ethers.BigNumber.from(1)
}
}
]
})
}));
}
export function verifyRegistration(decryptedCredentials, identity, registerStub, insertMemberSpy) {
if (!decryptedCredentials) {
throw new Error("Decrypted credentials should not be undefined");
}
// Verify registration call
expect(registerStub.calledWith(sinon.match.same(identity.IDCommitmentBigInt), sinon.match.same(DEFAULT_RATE_LIMIT), sinon.match.array, sinon.match.object)).to.be.true;
// Verify credential properties
expect(decryptedCredentials).to.have.property("identity");
expect(decryptedCredentials).to.have.property("membership");
expect(decryptedCredentials.membership).to.include({
address: RLN_CONTRACT.address,
treeIndex: 1
});
// Verify member insertion
const expectedIdCommitment = ethers.utils.zeroPad(hexToBytes(formatIdCommitment(identity.IDCommitmentBigInt)), 32);
expect(insertMemberSpy.callCount).to.equal(1);
expect(insertMemberSpy.getCall(0).args[0]).to.deep.equal(expectedIdCommitment);
}
//# sourceMappingURL=test_utils.js.map