mirror of
https://github.com/logos-messaging/logos-messaging-js.git
synced 2026-03-21 22:33:10 +00:00
tests: add reusable mock functions
This commit is contained in:
parent
4fc9cca0cc
commit
d528534a1d
1008
package-lock.json
generated
1008
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -59,14 +59,9 @@
|
||||
"@types/sinon": "^17.0.3",
|
||||
"@waku/build-utils": "^1.0.0",
|
||||
"@waku/message-encryption": "^0.0.32",
|
||||
"chai": "^5.1.2",
|
||||
"chai-as-promised": "^8.0.1",
|
||||
"chai-spies": "^1.1.0",
|
||||
"chai-subset": "^1.6.0",
|
||||
"deep-equal-in-any-order": "^2.0.6",
|
||||
"fast-check": "^3.23.2",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"sinon": "^19.0.2"
|
||||
"rollup-plugin-copy": "^3.5.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@ -86,6 +81,11 @@
|
||||
"ethereum-cryptography": "^3.1.0",
|
||||
"ethers": "^5.7.2",
|
||||
"lodash": "^4.17.21",
|
||||
"uuid": "^11.0.5"
|
||||
"uuid": "^11.0.5",
|
||||
"chai": "^5.1.2",
|
||||
"chai-as-promised": "^8.0.1",
|
||||
"chai-spies": "^1.1.0",
|
||||
"chai-subset": "^1.6.0",
|
||||
"sinon": "^19.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ import {
|
||||
createEncoder,
|
||||
DecodedMessage
|
||||
} from "@waku/core/lib/message/version_0";
|
||||
import type { IProtoMessage } from "@waku/interfaces";
|
||||
import {
|
||||
generatePrivateKey,
|
||||
generateSymmetricKey,
|
||||
@ -25,40 +24,30 @@ import {
|
||||
RLNDecoder,
|
||||
RLNEncoder
|
||||
} from "./codec.js";
|
||||
import { createRLN } from "./create.js";
|
||||
import {
|
||||
createTestMetaSetter,
|
||||
createTestRLNCodecSetup,
|
||||
EMPTY_PROTO_MESSAGE,
|
||||
TEST_CONSTANTS,
|
||||
verifyRLNMessage
|
||||
} from "./codec.test-utils.js";
|
||||
import { RlnMessage } from "./message.js";
|
||||
import { epochBytesToInt } from "./utils/index.js";
|
||||
|
||||
const TestContentTopic = "/test/1/waku-message/utf8";
|
||||
const EMPTY_PUBSUB_TOPIC = "";
|
||||
|
||||
const EMPTY_PROTO_MESSAGE = {
|
||||
timestamp: undefined,
|
||||
contentTopic: "",
|
||||
ephemeral: undefined,
|
||||
meta: undefined,
|
||||
rateLimitProof: undefined,
|
||||
version: undefined
|
||||
};
|
||||
|
||||
describe("RLN codec with version 0", () => {
|
||||
it("toWire", async function () {
|
||||
const rlnInstance = await createRLN();
|
||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
||||
const index = 0;
|
||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
|
||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
||||
const { rlnInstance, credential, index, payload } =
|
||||
await createTestRLNCodecSetup();
|
||||
|
||||
const rlnEncoder = createRLNEncoder({
|
||||
encoder: createEncoder({ contentTopic: TestContentTopic }),
|
||||
encoder: createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic }),
|
||||
rlnInstance,
|
||||
index,
|
||||
credential
|
||||
});
|
||||
const rlnDecoder = createRLNDecoder({
|
||||
rlnInstance,
|
||||
decoder: createDecoder(TestContentTopic)
|
||||
decoder: createDecoder(TEST_CONSTANTS.contentTopic)
|
||||
});
|
||||
|
||||
const bytes = await rlnEncoder.toWire({ payload });
|
||||
@ -67,78 +56,49 @@ describe("RLN codec with version 0", () => {
|
||||
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
||||
expect(protoResult).to.not.be.undefined;
|
||||
const msg = (await rlnDecoder.fromProtoObj(
|
||||
EMPTY_PUBSUB_TOPIC,
|
||||
TEST_CONSTANTS.emptyPubsubTopic,
|
||||
protoResult!
|
||||
))!;
|
||||
|
||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
||||
expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true;
|
||||
expect(msg.verifyNoRoot()).to.be.true;
|
||||
expect(msg.epoch).to.not.be.undefined;
|
||||
expect(msg.epoch).to.be.gt(0);
|
||||
|
||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msg.msg.version).to.eq(0);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance);
|
||||
});
|
||||
|
||||
it("toProtoObj", async function () {
|
||||
const rlnInstance = await createRLN();
|
||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
||||
const index = 0;
|
||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
|
||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
||||
const { rlnInstance, credential, index, payload } =
|
||||
await createTestRLNCodecSetup();
|
||||
|
||||
const rlnEncoder = new RLNEncoder(
|
||||
createEncoder({ contentTopic: TestContentTopic }),
|
||||
createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic }),
|
||||
rlnInstance,
|
||||
index,
|
||||
credential
|
||||
);
|
||||
const rlnDecoder = new RLNDecoder(
|
||||
rlnInstance,
|
||||
createDecoder(TestContentTopic)
|
||||
createDecoder(TEST_CONSTANTS.contentTopic)
|
||||
);
|
||||
|
||||
const proto = await rlnEncoder.toProtoObj({ payload });
|
||||
|
||||
expect(proto).to.not.be.undefined;
|
||||
const msg = (await rlnDecoder.fromProtoObj(
|
||||
EMPTY_PUBSUB_TOPIC,
|
||||
TEST_CONSTANTS.emptyPubsubTopic,
|
||||
proto!
|
||||
)) as RlnMessage<DecodedMessage>;
|
||||
|
||||
expect(msg).to.not.be.undefined;
|
||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
||||
|
||||
expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true;
|
||||
expect(msg.verifyNoRoot()).to.be.true;
|
||||
expect(msg.epoch).to.not.be.undefined;
|
||||
expect(msg.epoch).to.be.gt(0);
|
||||
|
||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msg.msg.version).to.eq(0);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance);
|
||||
});
|
||||
});
|
||||
|
||||
describe("RLN codec with version 1", () => {
|
||||
it("Symmetric, toWire", async function () {
|
||||
const rlnInstance = await createRLN();
|
||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
||||
const index = 0;
|
||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
|
||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
||||
|
||||
const { rlnInstance, credential, index, payload } =
|
||||
await createTestRLNCodecSetup();
|
||||
const symKey = generateSymmetricKey();
|
||||
|
||||
const rlnEncoder = new RLNEncoder(
|
||||
createSymEncoder({
|
||||
contentTopic: TestContentTopic,
|
||||
contentTopic: TEST_CONSTANTS.contentTopic,
|
||||
symKey
|
||||
}),
|
||||
rlnInstance,
|
||||
@ -147,45 +107,30 @@ describe("RLN codec with version 1", () => {
|
||||
);
|
||||
const rlnDecoder = new RLNDecoder(
|
||||
rlnInstance,
|
||||
createSymDecoder(TestContentTopic, symKey)
|
||||
createSymDecoder(TEST_CONSTANTS.contentTopic, symKey)
|
||||
);
|
||||
|
||||
const bytes = await rlnEncoder.toWire({ payload });
|
||||
|
||||
expect(bytes).to.not.be.undefined;
|
||||
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
||||
|
||||
expect(protoResult).to.not.be.undefined;
|
||||
const msg = (await rlnDecoder.fromProtoObj(
|
||||
EMPTY_PUBSUB_TOPIC,
|
||||
TEST_CONSTANTS.emptyPubsubTopic,
|
||||
protoResult!
|
||||
))!;
|
||||
|
||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
||||
expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true;
|
||||
expect(msg.verifyNoRoot()).to.be.true;
|
||||
expect(msg.epoch).to.not.be.undefined;
|
||||
expect(msg.epoch).to.be.gt(0);
|
||||
|
||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msg.msg.version).to.eq(1);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance);
|
||||
});
|
||||
|
||||
it("Symmetric, toProtoObj", async function () {
|
||||
const rlnInstance = await createRLN();
|
||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
||||
const index = 0;
|
||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
|
||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
||||
|
||||
const { rlnInstance, credential, index, payload } =
|
||||
await createTestRLNCodecSetup();
|
||||
const symKey = generateSymmetricKey();
|
||||
|
||||
const rlnEncoder = new RLNEncoder(
|
||||
createSymEncoder({
|
||||
contentTopic: TestContentTopic,
|
||||
contentTopic: TEST_CONSTANTS.contentTopic,
|
||||
symKey
|
||||
}),
|
||||
rlnInstance,
|
||||
@ -194,45 +139,29 @@ describe("RLN codec with version 1", () => {
|
||||
);
|
||||
const rlnDecoder = new RLNDecoder(
|
||||
rlnInstance,
|
||||
createSymDecoder(TestContentTopic, symKey)
|
||||
createSymDecoder(TEST_CONSTANTS.contentTopic, symKey)
|
||||
);
|
||||
|
||||
const proto = await rlnEncoder.toProtoObj({ payload });
|
||||
|
||||
expect(proto).to.not.be.undefined;
|
||||
const msg = (await rlnDecoder.fromProtoObj(
|
||||
EMPTY_PUBSUB_TOPIC,
|
||||
TEST_CONSTANTS.emptyPubsubTopic,
|
||||
proto!
|
||||
)) as RlnMessage<DecodedMessage>;
|
||||
|
||||
expect(msg).to.not.be.undefined;
|
||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
||||
|
||||
expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true;
|
||||
expect(msg.verifyNoRoot()).to.be.true;
|
||||
expect(msg.epoch).to.not.be.undefined;
|
||||
expect(msg.epoch).to.be.gt(0);
|
||||
|
||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msg.msg.version).to.eq(1);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance);
|
||||
});
|
||||
|
||||
it("Asymmetric, toWire", async function () {
|
||||
const rlnInstance = await createRLN();
|
||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
||||
const index = 0;
|
||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
|
||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
||||
|
||||
const { rlnInstance, credential, index, payload } =
|
||||
await createTestRLNCodecSetup();
|
||||
const privateKey = generatePrivateKey();
|
||||
const publicKey = getPublicKey(privateKey);
|
||||
|
||||
const rlnEncoder = new RLNEncoder(
|
||||
createAsymEncoder({
|
||||
contentTopic: TestContentTopic,
|
||||
contentTopic: TEST_CONSTANTS.contentTopic,
|
||||
publicKey
|
||||
}),
|
||||
rlnInstance,
|
||||
@ -241,46 +170,31 @@ describe("RLN codec with version 1", () => {
|
||||
);
|
||||
const rlnDecoder = new RLNDecoder(
|
||||
rlnInstance,
|
||||
createAsymDecoder(TestContentTopic, privateKey)
|
||||
createAsymDecoder(TEST_CONSTANTS.contentTopic, privateKey)
|
||||
);
|
||||
|
||||
const bytes = await rlnEncoder.toWire({ payload });
|
||||
|
||||
expect(bytes).to.not.be.undefined;
|
||||
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
||||
|
||||
expect(protoResult).to.not.be.undefined;
|
||||
const msg = (await rlnDecoder.fromProtoObj(
|
||||
EMPTY_PUBSUB_TOPIC,
|
||||
TEST_CONSTANTS.emptyPubsubTopic,
|
||||
protoResult!
|
||||
))!;
|
||||
|
||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
||||
expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true;
|
||||
expect(msg.verifyNoRoot()).to.be.true;
|
||||
expect(msg.epoch).to.not.be.undefined;
|
||||
expect(msg.epoch).to.be.gt(0);
|
||||
|
||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msg.msg.version).to.eq(1);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance);
|
||||
});
|
||||
|
||||
it("Asymmetric, toProtoObj", async function () {
|
||||
const rlnInstance = await createRLN();
|
||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
||||
const index = 0;
|
||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
|
||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
||||
|
||||
const { rlnInstance, credential, index, payload } =
|
||||
await createTestRLNCodecSetup();
|
||||
const privateKey = generatePrivateKey();
|
||||
const publicKey = getPublicKey(privateKey);
|
||||
|
||||
const rlnEncoder = new RLNEncoder(
|
||||
createAsymEncoder({
|
||||
contentTopic: TestContentTopic,
|
||||
contentTopic: TEST_CONSTANTS.contentTopic,
|
||||
publicKey
|
||||
}),
|
||||
rlnInstance,
|
||||
@ -289,106 +203,73 @@ describe("RLN codec with version 1", () => {
|
||||
);
|
||||
const rlnDecoder = new RLNDecoder(
|
||||
rlnInstance,
|
||||
createAsymDecoder(TestContentTopic, privateKey)
|
||||
createAsymDecoder(TEST_CONSTANTS.contentTopic, privateKey)
|
||||
);
|
||||
|
||||
const proto = await rlnEncoder.toProtoObj({ payload });
|
||||
|
||||
expect(proto).to.not.be.undefined;
|
||||
const msg = (await rlnDecoder.fromProtoObj(
|
||||
EMPTY_PUBSUB_TOPIC,
|
||||
TEST_CONSTANTS.emptyPubsubTopic,
|
||||
proto!
|
||||
)) as RlnMessage<DecodedMessage>;
|
||||
|
||||
expect(msg).to.not.be.undefined;
|
||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
||||
|
||||
expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true;
|
||||
expect(msg.verifyNoRoot()).to.be.true;
|
||||
expect(msg.epoch).to.not.be.undefined;
|
||||
expect(msg.epoch).to.be.gt(0);
|
||||
|
||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msg.msg.version).to.eq(1);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance);
|
||||
});
|
||||
});
|
||||
|
||||
describe("RLN Codec - epoch", () => {
|
||||
it("toProtoObj", async function () {
|
||||
const rlnInstance = await createRLN();
|
||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
||||
const index = 0;
|
||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
|
||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
||||
const { rlnInstance, credential, index, payload } =
|
||||
await createTestRLNCodecSetup();
|
||||
|
||||
const rlnEncoder = new RLNEncoder(
|
||||
createEncoder({ contentTopic: TestContentTopic }),
|
||||
createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic }),
|
||||
rlnInstance,
|
||||
index,
|
||||
credential
|
||||
);
|
||||
const rlnDecoder = new RLNDecoder(
|
||||
rlnInstance,
|
||||
createDecoder(TestContentTopic)
|
||||
createDecoder(TEST_CONSTANTS.contentTopic)
|
||||
);
|
||||
|
||||
const proto = await rlnEncoder.toProtoObj({ payload });
|
||||
|
||||
expect(proto).to.not.be.undefined;
|
||||
const msg = (await rlnDecoder.fromProtoObj(
|
||||
EMPTY_PUBSUB_TOPIC,
|
||||
TEST_CONSTANTS.emptyPubsubTopic,
|
||||
proto!
|
||||
)) as RlnMessage<DecodedMessage>;
|
||||
|
||||
const epochBytes = proto!.rateLimitProof!.epoch;
|
||||
const epoch = epochBytesToInt(epochBytes);
|
||||
|
||||
expect(msg).to.not.be.undefined;
|
||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
||||
|
||||
expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true;
|
||||
expect(msg.verifyNoRoot()).to.be.true;
|
||||
expect(msg.epoch).to.not.be.undefined;
|
||||
expect(msg.epoch!.toString(10).length).to.eq(9);
|
||||
expect(msg.epoch).to.eq(epoch);
|
||||
|
||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msg.msg.version).to.eq(0);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance);
|
||||
});
|
||||
});
|
||||
|
||||
describe("RLN codec with version 0 and meta setter", () => {
|
||||
// Encode the length of the payload
|
||||
// Not a relevant real life example
|
||||
const metaSetter = (msg: IProtoMessage & { meta: undefined }): Uint8Array => {
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
view.setUint32(0, msg.payload.length, false);
|
||||
return new Uint8Array(buffer);
|
||||
};
|
||||
|
||||
it("toWire", async function () {
|
||||
const rlnInstance = await createRLN();
|
||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
||||
const index = 0;
|
||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
|
||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
||||
const { rlnInstance, credential, index, payload } =
|
||||
await createTestRLNCodecSetup();
|
||||
const metaSetter = createTestMetaSetter();
|
||||
|
||||
const rlnEncoder = createRLNEncoder({
|
||||
encoder: createEncoder({ contentTopic: TestContentTopic, metaSetter }),
|
||||
encoder: createEncoder({
|
||||
contentTopic: TEST_CONSTANTS.contentTopic,
|
||||
metaSetter
|
||||
}),
|
||||
rlnInstance,
|
||||
index,
|
||||
credential
|
||||
});
|
||||
const rlnDecoder = createRLNDecoder({
|
||||
rlnInstance,
|
||||
decoder: createDecoder(TestContentTopic)
|
||||
decoder: createDecoder(TEST_CONSTANTS.contentTopic)
|
||||
});
|
||||
|
||||
const bytes = await rlnEncoder.toWire({ payload });
|
||||
@ -397,7 +278,7 @@ describe("RLN codec with version 0 and meta setter", () => {
|
||||
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
||||
expect(protoResult).to.not.be.undefined;
|
||||
const msg = (await rlnDecoder.fromProtoObj(
|
||||
EMPTY_PUBSUB_TOPIC,
|
||||
TEST_CONSTANTS.emptyPubsubTopic,
|
||||
protoResult!
|
||||
))!;
|
||||
|
||||
@ -407,43 +288,30 @@ describe("RLN codec with version 0 and meta setter", () => {
|
||||
});
|
||||
|
||||
expect(msg!.meta).to.deep.eq(expectedMeta);
|
||||
|
||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
||||
expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true;
|
||||
expect(msg.verifyNoRoot()).to.be.true;
|
||||
expect(msg.epoch).to.not.be.undefined;
|
||||
expect(msg.epoch).to.be.gt(0);
|
||||
|
||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msg.msg.version).to.eq(0);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance);
|
||||
});
|
||||
|
||||
it("toProtoObj", async function () {
|
||||
const rlnInstance = await createRLN();
|
||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
||||
const index = 0;
|
||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
|
||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
||||
const { rlnInstance, credential, index, payload } =
|
||||
await createTestRLNCodecSetup();
|
||||
const metaSetter = createTestMetaSetter();
|
||||
|
||||
const rlnEncoder = new RLNEncoder(
|
||||
createEncoder({ contentTopic: TestContentTopic, metaSetter }),
|
||||
createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic, metaSetter }),
|
||||
rlnInstance,
|
||||
index,
|
||||
credential
|
||||
);
|
||||
const rlnDecoder = new RLNDecoder(
|
||||
rlnInstance,
|
||||
createDecoder(TestContentTopic)
|
||||
createDecoder(TEST_CONSTANTS.contentTopic)
|
||||
);
|
||||
|
||||
const proto = await rlnEncoder.toProtoObj({ payload });
|
||||
|
||||
expect(proto).to.not.be.undefined;
|
||||
const msg = (await rlnDecoder.fromProtoObj(
|
||||
EMPTY_PUBSUB_TOPIC,
|
||||
TEST_CONSTANTS.emptyPubsubTopic,
|
||||
proto!
|
||||
)) as RlnMessage<DecodedMessage>;
|
||||
|
||||
@ -453,18 +321,6 @@ describe("RLN codec with version 0 and meta setter", () => {
|
||||
});
|
||||
|
||||
expect(msg!.meta).to.deep.eq(expectedMeta);
|
||||
|
||||
expect(msg).to.not.be.undefined;
|
||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
||||
|
||||
expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true;
|
||||
expect(msg.verifyNoRoot()).to.be.true;
|
||||
expect(msg.epoch).to.not.be.undefined;
|
||||
expect(msg.epoch).to.be.gt(0);
|
||||
|
||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
||||
expect(msg.msg.version).to.eq(0);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance);
|
||||
});
|
||||
});
|
||||
|
||||
80
packages/rln/src/codec.test-utils.ts
Normal file
80
packages/rln/src/codec.test-utils.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import type { IProtoMessage } from "@waku/interfaces";
|
||||
import { expect } from "chai";
|
||||
|
||||
import { createRLN } from "./create.js";
|
||||
import type { IdentityCredential } from "./identity.js";
|
||||
|
||||
export interface TestRLNCodecSetup {
|
||||
rlnInstance: any;
|
||||
credential: IdentityCredential;
|
||||
index: number;
|
||||
payload: Uint8Array;
|
||||
}
|
||||
|
||||
export const TEST_CONSTANTS = {
|
||||
contentTopic: "/test/1/waku-message/utf8",
|
||||
emptyPubsubTopic: "",
|
||||
defaultIndex: 0,
|
||||
defaultPayload: new Uint8Array([1, 2, 3, 4, 5])
|
||||
} as const;
|
||||
|
||||
export const EMPTY_PROTO_MESSAGE = {
|
||||
timestamp: undefined,
|
||||
contentTopic: "",
|
||||
ephemeral: undefined,
|
||||
meta: undefined,
|
||||
rateLimitProof: undefined,
|
||||
version: undefined
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Creates a basic RLN setup for codec tests
|
||||
*/
|
||||
export async function createTestRLNCodecSetup(): Promise<TestRLNCodecSetup> {
|
||||
const rlnInstance = await createRLN();
|
||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
||||
|
||||
return {
|
||||
rlnInstance,
|
||||
credential,
|
||||
index: TEST_CONSTANTS.defaultIndex,
|
||||
payload: TEST_CONSTANTS.defaultPayload
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a meta setter function for testing
|
||||
*/
|
||||
export function createTestMetaSetter(): (
|
||||
msg: IProtoMessage & { meta: undefined }
|
||||
) => Uint8Array {
|
||||
return (msg: IProtoMessage & { meta: undefined }): Uint8Array => {
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const view = new DataView(buffer);
|
||||
view.setUint32(0, msg.payload.length, false);
|
||||
return new Uint8Array(buffer);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies common RLN message properties
|
||||
*/
|
||||
export function verifyRLNMessage(
|
||||
msg: any,
|
||||
payload: Uint8Array,
|
||||
contentTopic: string,
|
||||
version: number,
|
||||
rlnInstance: any
|
||||
): void {
|
||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
||||
expect(msg.verify([rlnInstance.zerokit.getMerkleRoot()])).to.be.true;
|
||||
expect(msg.verifyNoRoot()).to.be.true;
|
||||
expect(msg.epoch).to.not.be.undefined;
|
||||
expect(msg.epoch).to.be.gt(0);
|
||||
|
||||
expect(msg.contentTopic).to.eq(contentTopic);
|
||||
expect(msg.msg.version).to.eq(version);
|
||||
expect(msg.payload).to.deep.eq(payload);
|
||||
expect(msg.timestamp).to.not.be.undefined;
|
||||
}
|
||||
@ -4,316 +4,83 @@ import chaiAsPromised from "chai-as-promised";
|
||||
import * as ethers from "ethers";
|
||||
import sinon, { SinonSandbox } from "sinon";
|
||||
|
||||
import { createRLN } from "../create.js";
|
||||
import type { IdentityCredential } from "../identity.js";
|
||||
|
||||
import { DEFAULT_RATE_LIMIT, SEPOLIA_CONTRACT } from "./constants.js";
|
||||
import { RLNContract } from "./rln_contract.js";
|
||||
import { createTestRLNInstance, initializeRLNContract } from "./test-setup.js";
|
||||
import {
|
||||
createMockRegistryContract,
|
||||
createRegisterStub,
|
||||
mockRLNRegisteredEvent,
|
||||
verifyRegistration
|
||||
} from "./test-utils.js";
|
||||
|
||||
use(chaiAsPromised);
|
||||
|
||||
describe("RLN Contract abstraction - RLN", () => {
|
||||
let sandbox: SinonSandbox;
|
||||
let rlnInstance: any;
|
||||
let mockedRegistryContract: any;
|
||||
|
||||
const mockRateLimits = {
|
||||
minRate: 20,
|
||||
maxRate: 600,
|
||||
maxTotalRate: 1200,
|
||||
currentTotalRate: 500
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox = sinon.createSandbox();
|
||||
rlnInstance = await createRLN();
|
||||
rlnInstance.zerokit.insertMember = () => undefined;
|
||||
|
||||
mockedRegistryContract = {
|
||||
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: (filter: ethers.EventFilter | undefined) => {
|
||||
if (filter && filter.address === undefined) {
|
||||
return [];
|
||||
}
|
||||
return [mockRLNRegisteredEvent()];
|
||||
},
|
||||
provider: {
|
||||
getLogs: () => [],
|
||||
getBlockNumber: () => Promise.resolve(1000),
|
||||
getNetwork: () => Promise.resolve({ chainId: 11155111 })
|
||||
},
|
||||
filters: {
|
||||
MembershipRegistered: function () {
|
||||
return {};
|
||||
},
|
||||
MembershipErased: function () {
|
||||
return {};
|
||||
},
|
||||
MembershipExpired: function () {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
on: () => ({})
|
||||
};
|
||||
|
||||
const provider = new ethers.providers.JsonRpcProvider();
|
||||
const voidSigner = new ethers.VoidSigner(
|
||||
SEPOLIA_CONTRACT.address,
|
||||
provider
|
||||
);
|
||||
await RLNContract.init(rlnInstance, {
|
||||
address: SEPOLIA_CONTRACT.address,
|
||||
signer: voidSigner,
|
||||
rateLimit: DEFAULT_RATE_LIMIT,
|
||||
contract: mockedRegistryContract as unknown as ethers.Contract
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it("should fetch members from events and store them in the RLN instance", async () => {
|
||||
const rlnInstance = await createRLN();
|
||||
describe("Member Registration", () => {
|
||||
it("should fetch members from events and store them in the RLN instance", async () => {
|
||||
const { rlnInstance, insertMemberSpy } = await createTestRLNInstance();
|
||||
const membershipRegisteredEvent = mockRLNRegisteredEvent();
|
||||
const queryFilterStub = sinon.stub().returns([membershipRegisteredEvent]);
|
||||
|
||||
const insertMemberSpy = sinon.stub();
|
||||
rlnInstance.zerokit.insertMember = insertMemberSpy;
|
||||
|
||||
const membershipRegisteredEvent = mockRLNRegisteredEvent();
|
||||
|
||||
const queryFilterStub = sinon
|
||||
.stub()
|
||||
.callsFake((filter: ethers.EventFilter | undefined) => {
|
||||
if (filter && filter.topics && filter.topics.length > 0) {
|
||||
return [];
|
||||
}
|
||||
return [membershipRegisteredEvent];
|
||||
const mockedRegistryContract = createMockRegistryContract({
|
||||
queryFilter: queryFilterStub
|
||||
});
|
||||
const mockedRegistryContract = {
|
||||
queryFilter: queryFilterStub,
|
||||
provider: {
|
||||
getLogs: () => [],
|
||||
getBlockNumber: () => Promise.resolve(1000)
|
||||
},
|
||||
interface: {
|
||||
getEvent: (eventName: string) => ({
|
||||
name: eventName,
|
||||
format: () => {}
|
||||
})
|
||||
},
|
||||
filters: {
|
||||
MembershipRegistered: function () {
|
||||
return {};
|
||||
},
|
||||
MembershipErased: function () {
|
||||
return {};
|
||||
},
|
||||
MembershipExpired: function () {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
on: () => ({}),
|
||||
removeAllListeners: () => ({})
|
||||
};
|
||||
|
||||
const provider = new ethers.providers.JsonRpcProvider();
|
||||
const voidSigner = new ethers.VoidSigner(
|
||||
SEPOLIA_CONTRACT.address,
|
||||
provider
|
||||
);
|
||||
const rlnContract = await RLNContract.init(rlnInstance, {
|
||||
address: SEPOLIA_CONTRACT.address,
|
||||
signer: voidSigner,
|
||||
rateLimit: DEFAULT_RATE_LIMIT,
|
||||
contract: mockedRegistryContract as unknown as ethers.Contract
|
||||
});
|
||||
const rlnContract = await initializeRLNContract(
|
||||
rlnInstance,
|
||||
mockedRegistryContract
|
||||
);
|
||||
|
||||
await rlnContract.fetchMembers(rlnInstance, {
|
||||
fromBlock: 0,
|
||||
fetchRange: 1000,
|
||||
fetchChunks: 2
|
||||
});
|
||||
await rlnContract.fetchMembers(rlnInstance, {
|
||||
fromBlock: 0,
|
||||
fetchRange: 1000,
|
||||
fetchChunks: 2
|
||||
});
|
||||
|
||||
expect(
|
||||
insertMemberSpy.calledWith(
|
||||
ethers.utils.zeroPad(
|
||||
hexToBytes(membershipRegisteredEvent.args!.idCommitment),
|
||||
32
|
||||
expect(
|
||||
insertMemberSpy.calledWith(
|
||||
ethers.utils.zeroPad(
|
||||
hexToBytes(membershipRegisteredEvent.args!.idCommitment),
|
||||
32
|
||||
)
|
||||
)
|
||||
)
|
||||
).to.be.true;
|
||||
).to.be.true;
|
||||
expect(queryFilterStub.called).to.be.true;
|
||||
});
|
||||
|
||||
expect(queryFilterStub.called).to.be.true;
|
||||
});
|
||||
it("should register a member", async () => {
|
||||
const { rlnInstance, identity, insertMemberSpy } =
|
||||
await createTestRLNInstance();
|
||||
|
||||
it("should register a member", async () => {
|
||||
const mockSignature =
|
||||
"0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c";
|
||||
|
||||
const rlnInstance = await createRLN();
|
||||
const identity: IdentityCredential =
|
||||
rlnInstance.zerokit.generateSeededIdentityCredential(mockSignature);
|
||||
|
||||
const insertMemberSpy = sinon.stub();
|
||||
rlnInstance.zerokit.insertMember = insertMemberSpy;
|
||||
|
||||
const formatIdCommitment = (idCommitmentBigInt: bigint): string =>
|
||||
"0x" + idCommitmentBigInt.toString(16).padStart(64, "0");
|
||||
|
||||
const membershipRegisteredEvent = mockRLNRegisteredEvent(
|
||||
formatIdCommitment(identity.IDCommitmentBigInt)
|
||||
);
|
||||
|
||||
const registerStub = sinon
|
||||
.stub()
|
||||
.callsFake((idCommitment, rateLimit, idCommitmentsToErase, options) => {
|
||||
console.log(
|
||||
`Registering with ID commitment: ${idCommitment}, rate limit: ${rateLimit}`
|
||||
);
|
||||
console.log(
|
||||
`ID commitments to erase: ${idCommitmentsToErase}, options: ${JSON.stringify(options)}`
|
||||
);
|
||||
|
||||
return {
|
||||
wait: () =>
|
||||
Promise.resolve({
|
||||
events: [
|
||||
{
|
||||
event: "MembershipRegistered",
|
||||
args: {
|
||||
idCommitment: formatIdCommitment(
|
||||
identity.IDCommitmentBigInt
|
||||
),
|
||||
rateLimit: DEFAULT_RATE_LIMIT,
|
||||
index: ethers.BigNumber.from(1)
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
};
|
||||
const registerStub = createRegisterStub(identity);
|
||||
const mockedRegistryContract = createMockRegistryContract({
|
||||
register: registerStub,
|
||||
queryFilter: () => []
|
||||
});
|
||||
|
||||
const mockedRegistryContract = {
|
||||
register: registerStub,
|
||||
queryFilter: (filter: ethers.EventFilter | undefined) => {
|
||||
if (filter && filter.address === undefined) {
|
||||
return [];
|
||||
}
|
||||
return [membershipRegisteredEvent];
|
||||
},
|
||||
provider: {
|
||||
getLogs: () => [],
|
||||
getBlockNumber: () => Promise.resolve(1000),
|
||||
getNetwork: () => Promise.resolve({ chainId: 11155111 })
|
||||
},
|
||||
address: SEPOLIA_CONTRACT.address,
|
||||
interface: {
|
||||
getEvent: (eventName: string) => ({
|
||||
name: eventName,
|
||||
format: () => {}
|
||||
})
|
||||
},
|
||||
filters: {
|
||||
MembershipRegistered: function () {
|
||||
return {};
|
||||
},
|
||||
MembershipErased: function () {
|
||||
return {};
|
||||
},
|
||||
MembershipExpired: function () {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
on: () => ({}),
|
||||
removeAllListeners: () => ({}),
|
||||
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)),
|
||||
estimateGas: {
|
||||
register: () => Promise.resolve(ethers.BigNumber.from(200000))
|
||||
}
|
||||
};
|
||||
const rlnContract = await initializeRLNContract(
|
||||
rlnInstance,
|
||||
mockedRegistryContract
|
||||
);
|
||||
|
||||
const provider = new ethers.providers.JsonRpcProvider();
|
||||
const voidSigner = new ethers.VoidSigner(
|
||||
SEPOLIA_CONTRACT.address,
|
||||
provider
|
||||
);
|
||||
const rlnContract = await RLNContract.init(rlnInstance, {
|
||||
signer: voidSigner,
|
||||
address: SEPOLIA_CONTRACT.address,
|
||||
rateLimit: DEFAULT_RATE_LIMIT,
|
||||
contract: mockedRegistryContract as unknown as ethers.Contract
|
||||
const decryptedCredentials =
|
||||
await rlnContract.registerWithIdentity(identity);
|
||||
|
||||
verifyRegistration(
|
||||
decryptedCredentials,
|
||||
identity,
|
||||
registerStub,
|
||||
insertMemberSpy
|
||||
);
|
||||
});
|
||||
|
||||
const decryptedCredentials =
|
||||
await rlnContract.registerWithIdentity(identity);
|
||||
|
||||
expect(decryptedCredentials).to.not.be.undefined;
|
||||
if (!decryptedCredentials) {
|
||||
throw new Error("Decrypted credentials should not be undefined");
|
||||
}
|
||||
|
||||
expect(
|
||||
registerStub.calledWith(
|
||||
sinon.match.same(identity.IDCommitmentBigInt),
|
||||
sinon.match.same(DEFAULT_RATE_LIMIT),
|
||||
sinon.match.array,
|
||||
sinon.match.object
|
||||
)
|
||||
).to.be.true;
|
||||
|
||||
const processedEvent = {
|
||||
event: "MembershipRegistered",
|
||||
args: {
|
||||
idCommitment: formatIdCommitment(identity.IDCommitmentBigInt),
|
||||
membershipRateLimit: ethers.BigNumber.from(DEFAULT_RATE_LIMIT),
|
||||
index: ethers.BigNumber.from(1)
|
||||
},
|
||||
blockNumber: 1000
|
||||
} as unknown as ethers.Event;
|
||||
|
||||
rlnContract.processEvents(rlnInstance, [processedEvent]);
|
||||
|
||||
expect(decryptedCredentials).to.have.property("identity");
|
||||
expect(decryptedCredentials).to.have.property("membership");
|
||||
expect(decryptedCredentials.membership).to.include({
|
||||
address: SEPOLIA_CONTRACT.address,
|
||||
treeIndex: 1
|
||||
});
|
||||
|
||||
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
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function mockRLNRegisteredEvent(idCommitment?: string): ethers.Event {
|
||||
return {
|
||||
args: {
|
||||
idCommitment:
|
||||
idCommitment ||
|
||||
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
||||
membershipRateLimit: ethers.BigNumber.from(DEFAULT_RATE_LIMIT),
|
||||
index: ethers.BigNumber.from(1)
|
||||
},
|
||||
event: "MembershipRegistered"
|
||||
} as unknown as ethers.Event;
|
||||
}
|
||||
|
||||
@ -272,6 +272,12 @@ export class RLNContract {
|
||||
|
||||
if (typeof index === "number" || typeof index === "string") {
|
||||
index = ethers.BigNumber.from(index);
|
||||
} else {
|
||||
log.error("Index is not a number or string", {
|
||||
index,
|
||||
event: evt
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const toRemoveVal = toRemoveTable.get(evt.blockNumber);
|
||||
@ -581,7 +587,7 @@ export class RLNContract {
|
||||
|
||||
const network = await this.contract.provider.getNetwork();
|
||||
const address = this.contract.address;
|
||||
const membershipId = Number(decodedData.index);
|
||||
const membershipId = ethers.BigNumber.from(decodedData.index).toNumber();
|
||||
|
||||
return {
|
||||
identity,
|
||||
|
||||
86
packages/rln/src/contract/test-setup.ts
Normal file
86
packages/rln/src/contract/test-setup.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { hexToBytes } from "@waku/utils/bytes";
|
||||
import { ethers } from "ethers";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { createRLN } from "../create.js";
|
||||
import type { IdentityCredential } from "../identity.js";
|
||||
|
||||
import { DEFAULT_RATE_LIMIT, SEPOLIA_CONTRACT } from "./constants.js";
|
||||
import { RLNContract } from "./rln_contract.js";
|
||||
|
||||
export interface TestRLNInstance {
|
||||
rlnInstance: any;
|
||||
identity: IdentityCredential;
|
||||
insertMemberSpy: sinon.SinonStub;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test RLN instance with basic setup
|
||||
*/
|
||||
export async function createTestRLNInstance(): Promise<TestRLNInstance> {
|
||||
const rlnInstance = await createRLN();
|
||||
const insertMemberSpy = sinon.stub();
|
||||
rlnInstance.zerokit.insertMember = insertMemberSpy;
|
||||
|
||||
const mockSignature =
|
||||
"0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c";
|
||||
const identity =
|
||||
rlnInstance.zerokit.generateSeededIdentityCredential(mockSignature);
|
||||
|
||||
return {
|
||||
rlnInstance,
|
||||
identity,
|
||||
insertMemberSpy
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an RLN contract with the given registry contract
|
||||
*/
|
||||
export async function initializeRLNContract(
|
||||
rlnInstance: any,
|
||||
mockedRegistryContract: ethers.Contract
|
||||
): Promise<RLNContract> {
|
||||
const provider = new ethers.providers.JsonRpcProvider();
|
||||
const voidSigner = new ethers.VoidSigner(SEPOLIA_CONTRACT.address, provider);
|
||||
|
||||
const originalRegister = mockedRegistryContract.register;
|
||||
(mockedRegistryContract as any).register = function (...args: any[]) {
|
||||
const result = originalRegister.apply(this, args);
|
||||
|
||||
if (args[0] && rlnInstance.zerokit) {
|
||||
const idCommitmentBigInt = args[0];
|
||||
const idCommitmentHex =
|
||||
"0x" + idCommitmentBigInt.toString(16).padStart(64, "0");
|
||||
const idCommitment = ethers.utils.zeroPad(
|
||||
hexToBytes(idCommitmentHex),
|
||||
32
|
||||
);
|
||||
rlnInstance.zerokit.insertMember(idCommitment);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const contract = await RLNContract.init(rlnInstance, {
|
||||
address: SEPOLIA_CONTRACT.address,
|
||||
signer: voidSigner,
|
||||
rateLimit: DEFAULT_RATE_LIMIT,
|
||||
contract: mockedRegistryContract
|
||||
});
|
||||
|
||||
return contract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common test message data
|
||||
*/
|
||||
export const TEST_DATA = {
|
||||
contentTopic: "/test/1/waku-message/utf8",
|
||||
emptyPubsubTopic: "",
|
||||
testMessage: Uint8Array.from(
|
||||
"Hello World".split("").map((x) => x.charCodeAt(0))
|
||||
),
|
||||
mockSignature:
|
||||
"0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c"
|
||||
} as const;
|
||||
179
packages/rln/src/contract/test-utils.ts
Normal file
179
packages/rln/src/contract/test-utils.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import { hexToBytes } from "@waku/utils/bytes";
|
||||
import { expect } from "chai";
|
||||
import * as ethers from "ethers";
|
||||
import sinon from "sinon";
|
||||
|
||||
import type { IdentityCredential } from "../identity.js";
|
||||
|
||||
import { DEFAULT_RATE_LIMIT, SEPOLIA_CONTRACT } from "./constants.js";
|
||||
|
||||
export const mockRateLimits = {
|
||||
minRate: 20,
|
||||
maxRate: 600,
|
||||
maxTotalRate: 1200,
|
||||
currentTotalRate: 500
|
||||
};
|
||||
|
||||
type MockProvider = {
|
||||
getLogs: () => never[];
|
||||
getBlockNumber: () => Promise<number>;
|
||||
getNetwork: () => Promise<{ chainId: number }>;
|
||||
};
|
||||
|
||||
type MockFilters = {
|
||||
MembershipRegistered: () => { address: string };
|
||||
MembershipErased: () => { address: string };
|
||||
MembershipExpired: () => { address: string };
|
||||
};
|
||||
|
||||
export function createMockProvider(): MockProvider {
|
||||
return {
|
||||
getLogs: () => [],
|
||||
getBlockNumber: () => Promise.resolve(1000),
|
||||
getNetwork: () => Promise.resolve({ chainId: 11155111 })
|
||||
};
|
||||
}
|
||||
|
||||
export function createMockFilters(): MockFilters {
|
||||
return {
|
||||
MembershipRegistered: () => ({ address: SEPOLIA_CONTRACT.address }),
|
||||
MembershipErased: () => ({ address: SEPOLIA_CONTRACT.address }),
|
||||
MembershipExpired: () => ({ address: SEPOLIA_CONTRACT.address })
|
||||
};
|
||||
}
|
||||
|
||||
type ContractOverrides = Partial<{
|
||||
filters: Record<string, unknown>;
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
|
||||
export function createMockRegistryContract(
|
||||
overrides: ContractOverrides = {}
|
||||
): ethers.Contract {
|
||||
const filters = {
|
||||
MembershipRegistered: () => ({ address: SEPOLIA_CONTRACT.address }),
|
||||
MembershipErased: () => ({ address: SEPOLIA_CONTRACT.address }),
|
||||
MembershipExpired: () => ({ address: SEPOLIA_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: string) => ({
|
||||
name: eventName,
|
||||
format: () => {}
|
||||
})
|
||||
},
|
||||
address: SEPOLIA_CONTRACT.address
|
||||
};
|
||||
|
||||
// Merge overrides while preserving filters
|
||||
const merged = {
|
||||
...baseContract,
|
||||
...overrides,
|
||||
filters: { ...filters, ...(overrides.filters || {}) }
|
||||
};
|
||||
|
||||
return merged as unknown as ethers.Contract;
|
||||
}
|
||||
|
||||
export function mockRLNRegisteredEvent(idCommitment?: string): ethers.Event {
|
||||
return {
|
||||
args: {
|
||||
idCommitment:
|
||||
idCommitment ||
|
||||
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
||||
membershipRateLimit: ethers.BigNumber.from(DEFAULT_RATE_LIMIT),
|
||||
index: ethers.BigNumber.from(1)
|
||||
},
|
||||
event: "MembershipRegistered"
|
||||
} as unknown as ethers.Event;
|
||||
}
|
||||
|
||||
export function formatIdCommitment(idCommitmentBigInt: bigint): string {
|
||||
return "0x" + idCommitmentBigInt.toString(16).padStart(64, "0");
|
||||
}
|
||||
|
||||
export function createRegisterStub(
|
||||
identity: IdentityCredential
|
||||
): sinon.SinonStub {
|
||||
return sinon.stub().callsFake(() => ({
|
||||
wait: () =>
|
||||
Promise.resolve({
|
||||
events: [
|
||||
{
|
||||
event: "MembershipRegistered",
|
||||
args: {
|
||||
idCommitment: formatIdCommitment(identity.IDCommitmentBigInt),
|
||||
rateLimit: DEFAULT_RATE_LIMIT,
|
||||
index: ethers.BigNumber.from(1)
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
export function verifyRegistration(
|
||||
decryptedCredentials: any,
|
||||
identity: IdentityCredential,
|
||||
registerStub: sinon.SinonStub,
|
||||
insertMemberSpy: sinon.SinonStub
|
||||
): void {
|
||||
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: SEPOLIA_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
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user