mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-02 13:53:12 +00:00
chore(rln): update ABI and contract address to Linea Sepolia, enhancements (#2294)
* chore: update ABI * chore: update contract address and chain ID to Linea Sepolia * chore: improve error handling * fix: bigint conversion * chore: update tests * chore: clean comments * chore: export keystore types * chore: update README with contract address * tests: add reusable mock functions * chore: LINEA_CONTRACT instead of SEPOLIA_CONTRACT * chore: add linea to cspell * chore: add rateLimit to MembershipInfo * fix(tests): rate limit additions * chore: update gitignore
This commit is contained in:
parent
ea6daae927
commit
a8ff776962
@ -8,8 +8,8 @@
|
||||
"ahadns",
|
||||
"Alives",
|
||||
"alphabeta",
|
||||
"Arraylike",
|
||||
"arrayify",
|
||||
"Arraylike",
|
||||
"asym",
|
||||
"autoshard",
|
||||
"autosharding",
|
||||
@ -70,6 +70,7 @@
|
||||
"libauth",
|
||||
"libp",
|
||||
"lightpush",
|
||||
"LINEA",
|
||||
"livechat",
|
||||
"Merkle",
|
||||
"mkdir",
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -12,4 +12,7 @@ docs
|
||||
test-results
|
||||
playwright-report
|
||||
example
|
||||
packages/discovery/mock_local_storage
|
||||
packages/discovery/mock_local_storage
|
||||
.cursorrules
|
||||
.giga
|
||||
.cursor
|
||||
1008
package-lock.json
generated
1008
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,11 @@ import { RLN } from '@waku/rln';
|
||||
// Usage examples coming soon
|
||||
```
|
||||
|
||||
## Constants
|
||||
|
||||
- Implementation contract: 0xde2260ca49300357d5af4153cda0d18f7b3ea9b3
|
||||
- Proxy contract: 0xb9cd878c90e49f797b4431fbf4fb333108cb90e6
|
||||
|
||||
## License
|
||||
|
||||
MIT OR Apache-2.0
|
||||
@ -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;
|
||||
}
|
||||
@ -1,392 +1,646 @@
|
||||
export const RLN_ABI = [
|
||||
{ inputs: [], stateMutability: "nonpayable", type: "constructor" },
|
||||
{
|
||||
type: "constructor",
|
||||
inputs: [],
|
||||
stateMutability: "nonpayable"
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "CannotEraseActiveMembership",
|
||||
type: "error"
|
||||
},
|
||||
{ inputs: [], name: "CannotExceedMaxTotalRateLimit", type: "error" },
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "CannotExtendNonGracePeriodMembership",
|
||||
type: "error"
|
||||
},
|
||||
{
|
||||
type: "error",
|
||||
name: "DuplicateIdCommitment",
|
||||
inputs: []
|
||||
},
|
||||
{
|
||||
type: "error",
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "InvalidIdCommitment",
|
||||
inputs: [
|
||||
{
|
||||
name: "idCommitment",
|
||||
type: "uint256"
|
||||
}
|
||||
]
|
||||
type: "error"
|
||||
},
|
||||
{ inputs: [], name: "InvalidMembershipRateLimit", type: "error" },
|
||||
{
|
||||
type: "error",
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "startIndex", type: "uint256" },
|
||||
{ internalType: "uint256", name: "endIndex", type: "uint256" }
|
||||
],
|
||||
name: "InvalidPaginationQuery",
|
||||
type: "error"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "MembershipDoesNotExist",
|
||||
type: "error"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "NonHolderCannotEraseGracePeriodMembership",
|
||||
type: "error"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "NonHolderCannotExtend",
|
||||
type: "error"
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
name: "startIndex",
|
||||
type: "uint256"
|
||||
},
|
||||
{
|
||||
name: "endIndex",
|
||||
type: "uint256"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "MAX_MEMBERSHIP_SET_SIZE",
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
name: "",
|
||||
type: "uint32"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "MERKLE_TREE_DEPTH",
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
name: "",
|
||||
type: "uint8"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "Q",
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
name: "",
|
||||
type: "uint256"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "activeDurationForNewMemberships",
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
name: "",
|
||||
type: "uint32"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "currentTotalRateLimit",
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
name: "",
|
||||
type: "uint256"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "deployedBlockNumber",
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
name: "",
|
||||
type: "uint32"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "depositsToWithdraw",
|
||||
inputs: [
|
||||
{
|
||||
name: "holder",
|
||||
indexed: false,
|
||||
internalType: "address",
|
||||
name: "previousAdmin",
|
||||
type: "address"
|
||||
},
|
||||
{
|
||||
name: "token",
|
||||
indexed: false,
|
||||
internalType: "address",
|
||||
name: "newAdmin",
|
||||
type: "address"
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
name: "balance",
|
||||
type: "uint256"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
name: "AdminChanged",
|
||||
type: "event"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "eraseMemberships",
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
name: "idCommitments",
|
||||
type: "uint256[]"
|
||||
indexed: true,
|
||||
internalType: "address",
|
||||
name: "beacon",
|
||||
type: "address"
|
||||
}
|
||||
],
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable"
|
||||
name: "BeaconUpgraded",
|
||||
type: "event"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "eraseMemberships",
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
name: "idCommitments",
|
||||
type: "uint256[]"
|
||||
},
|
||||
{
|
||||
name: "eraseFromMembershipSet",
|
||||
type: "bool"
|
||||
}
|
||||
{ indexed: false, internalType: "uint8", name: "version", type: "uint8" }
|
||||
],
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable"
|
||||
name: "Initialized",
|
||||
type: "event"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "extendMemberships",
|
||||
inputs: [
|
||||
{
|
||||
name: "idCommitments",
|
||||
type: "uint256[]"
|
||||
}
|
||||
],
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "getMembershipInfo",
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "uint256",
|
||||
name: "idCommitment",
|
||||
type: "uint256"
|
||||
}
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "uint32",
|
||||
name: "membershipRateLimit",
|
||||
type: "uint32"
|
||||
},
|
||||
{ indexed: false, internalType: "uint32", name: "index", type: "uint32" }
|
||||
],
|
||||
outputs: [
|
||||
name: "MembershipErased",
|
||||
type: "event"
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
name: "",
|
||||
type: "uint32"
|
||||
indexed: false,
|
||||
internalType: "uint256",
|
||||
name: "idCommitment",
|
||||
type: "uint256"
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
indexed: false,
|
||||
internalType: "uint32",
|
||||
name: "membershipRateLimit",
|
||||
type: "uint32"
|
||||
},
|
||||
{ indexed: false, internalType: "uint32", name: "index", type: "uint32" }
|
||||
],
|
||||
name: "MembershipExpired",
|
||||
type: "event"
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
name: "",
|
||||
indexed: false,
|
||||
internalType: "uint256",
|
||||
name: "idCommitment",
|
||||
type: "uint256"
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "uint32",
|
||||
name: "membershipRateLimit",
|
||||
type: "uint32"
|
||||
},
|
||||
{ indexed: false, internalType: "uint32", name: "index", type: "uint32" },
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "uint256",
|
||||
name: "newGracePeriodStartTimestamp",
|
||||
type: "uint256"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
name: "MembershipExtended",
|
||||
type: "event"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "getMerkleProof",
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
name: "index",
|
||||
type: "uint40"
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
name: "",
|
||||
type: "uint256[20]"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "getRateCommitmentsInRangeBoundsInclusive",
|
||||
inputs: [
|
||||
{
|
||||
name: "startIndex",
|
||||
type: "uint32"
|
||||
indexed: false,
|
||||
internalType: "uint256",
|
||||
name: "idCommitment",
|
||||
type: "uint256"
|
||||
},
|
||||
{
|
||||
name: "endIndex",
|
||||
type: "uint32"
|
||||
}
|
||||
indexed: false,
|
||||
internalType: "uint256",
|
||||
name: "membershipRateLimit",
|
||||
type: "uint256"
|
||||
},
|
||||
{ indexed: false, internalType: "uint32", name: "index", type: "uint32" }
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
name: "",
|
||||
type: "uint256[]"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
name: "MembershipRegistered",
|
||||
type: "event"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "gracePeriodDurationForNewMemberships",
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
name: "",
|
||||
type: "uint32"
|
||||
}
|
||||
],
|
||||
stateMutability: "view"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "initialize",
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
name: "_priceCalculator",
|
||||
indexed: true,
|
||||
internalType: "address",
|
||||
name: "previousOwner",
|
||||
type: "address"
|
||||
},
|
||||
{
|
||||
name: "_maxTotalRateLimit",
|
||||
type: "uint32"
|
||||
},
|
||||
indexed: true,
|
||||
internalType: "address",
|
||||
name: "newOwner",
|
||||
type: "address"
|
||||
}
|
||||
],
|
||||
name: "OwnershipTransferred",
|
||||
type: "event"
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: "address",
|
||||
name: "implementation",
|
||||
type: "address"
|
||||
}
|
||||
],
|
||||
name: "Upgraded",
|
||||
type: "event"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "MAX_MEMBERSHIP_SET_SIZE",
|
||||
outputs: [{ internalType: "uint32", name: "", type: "uint32" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "MERKLE_TREE_DEPTH",
|
||||
outputs: [{ internalType: "uint8", name: "", type: "uint8" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "Q",
|
||||
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "activeDurationForNewMemberships",
|
||||
outputs: [{ internalType: "uint32", name: "", type: "uint32" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "currentTotalRateLimit",
|
||||
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "deployedBlockNumber",
|
||||
outputs: [{ internalType: "uint32", name: "", type: "uint32" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "address", name: "holder", type: "address" },
|
||||
{ internalType: "address", name: "token", type: "address" }
|
||||
],
|
||||
name: "depositsToWithdraw",
|
||||
outputs: [{ internalType: "uint256", name: "balance", type: "uint256" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256[]", name: "idCommitments", type: "uint256[]" }
|
||||
],
|
||||
name: "eraseMemberships",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256[]", name: "idCommitments", type: "uint256[]" },
|
||||
{ internalType: "bool", name: "eraseFromMembershipSet", type: "bool" }
|
||||
],
|
||||
name: "eraseMemberships",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256[]", name: "idCommitments", type: "uint256[]" }
|
||||
],
|
||||
name: "extendMemberships",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "getMembershipInfo",
|
||||
outputs: [
|
||||
{ internalType: "uint32", name: "", type: "uint32" },
|
||||
{ internalType: "uint32", name: "", type: "uint32" },
|
||||
{ internalType: "uint256", name: "", type: "uint256" }
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [{ internalType: "uint40", name: "index", type: "uint40" }],
|
||||
name: "getMerkleProof",
|
||||
outputs: [{ internalType: "uint256[20]", name: "", type: "uint256[20]" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint32", name: "startIndex", type: "uint32" },
|
||||
{ internalType: "uint32", name: "endIndex", type: "uint32" }
|
||||
],
|
||||
name: "getRateCommitmentsInRangeBoundsInclusive",
|
||||
outputs: [{ internalType: "uint256[]", name: "", type: "uint256[]" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "gracePeriodDurationForNewMemberships",
|
||||
outputs: [{ internalType: "uint32", name: "", type: "uint32" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
||||
name: "indicesOfLazilyErasedMemberships",
|
||||
outputs: [{ internalType: "uint32", name: "", type: "uint32" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "address", name: "_priceCalculator", type: "address" },
|
||||
{ internalType: "uint32", name: "_maxTotalRateLimit", type: "uint32" },
|
||||
{
|
||||
internalType: "uint32",
|
||||
name: "_minMembershipRateLimit",
|
||||
type: "uint32"
|
||||
},
|
||||
{
|
||||
internalType: "uint32",
|
||||
name: "_maxMembershipRateLimit",
|
||||
type: "uint32"
|
||||
},
|
||||
{
|
||||
name: "_activeDuration",
|
||||
type: "uint32"
|
||||
},
|
||||
{
|
||||
name: "_gracePeriod",
|
||||
type: "uint32"
|
||||
}
|
||||
{ internalType: "uint32", name: "_activeDuration", type: "uint32" },
|
||||
{ internalType: "uint32", name: "_gracePeriod", type: "uint32" }
|
||||
],
|
||||
name: "initialize",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable"
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "_idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "isExpired",
|
||||
outputs: [{ internalType: "bool", name: "", type: "bool" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
name: "_idCommitment",
|
||||
type: "uint256"
|
||||
}
|
||||
{ internalType: "uint256", name: "_idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "isInGracePeriod",
|
||||
outputs: [{ internalType: "bool", name: "", type: "bool" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "isInMembershipSet",
|
||||
outputs: [{ internalType: "bool", name: "", type: "bool" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "isValidIdCommitment",
|
||||
outputs: [{ internalType: "bool", name: "", type: "bool" }],
|
||||
stateMutability: "pure",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [{ internalType: "uint32", name: "rateLimit", type: "uint32" }],
|
||||
name: "isValidMembershipRateLimit",
|
||||
outputs: [{ internalType: "bool", name: "", type: "bool" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "maxMembershipRateLimit",
|
||||
outputs: [{ internalType: "uint32", name: "", type: "uint32" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "maxTotalRateLimit",
|
||||
outputs: [{ internalType: "uint32", name: "", type: "uint32" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "_idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "membershipExpirationTimestamp",
|
||||
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||
],
|
||||
name: "memberships",
|
||||
outputs: [
|
||||
{ internalType: "uint256", name: "depositAmount", type: "uint256" },
|
||||
{ internalType: "uint32", name: "activeDuration", type: "uint32" },
|
||||
{
|
||||
name: "",
|
||||
type: "bool"
|
||||
}
|
||||
internalType: "uint256",
|
||||
name: "gracePeriodStartTimestamp",
|
||||
type: "uint256"
|
||||
},
|
||||
{ internalType: "uint32", name: "gracePeriodDuration", type: "uint32" },
|
||||
{ internalType: "uint32", name: "rateLimit", type: "uint32" },
|
||||
{ internalType: "uint32", name: "index", type: "uint32" },
|
||||
{ internalType: "address", name: "holder", type: "address" },
|
||||
{ internalType: "address", name: "token", type: "address" }
|
||||
],
|
||||
stateMutability: "view"
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
inputs: [],
|
||||
name: "merkleTree",
|
||||
outputs: [
|
||||
{ internalType: "uint40", name: "maxIndex", type: "uint40" },
|
||||
{ internalType: "uint40", name: "numberOfLeaves", type: "uint40" }
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "minMembershipRateLimit",
|
||||
outputs: [{ internalType: "uint32", name: "", type: "uint32" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "nextFreeIndex",
|
||||
outputs: [{ internalType: "uint32", name: "", type: "uint32" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "owner",
|
||||
outputs: [{ internalType: "address", name: "", type: "address" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "priceCalculator",
|
||||
outputs: [
|
||||
{ internalType: "contract IPriceCalculator", name: "", type: "address" }
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "proxiableUUID",
|
||||
outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" },
|
||||
{ internalType: "uint32", name: "rateLimit", type: "uint32" },
|
||||
{
|
||||
internalType: "uint256[]",
|
||||
name: "idCommitmentsToErase",
|
||||
type: "uint256[]"
|
||||
}
|
||||
],
|
||||
name: "register",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "address", name: "owner", type: "address" },
|
||||
{ internalType: "uint256", name: "deadline", type: "uint256" },
|
||||
{ internalType: "uint8", name: "v", type: "uint8" },
|
||||
{ internalType: "bytes32", name: "r", type: "bytes32" },
|
||||
{ internalType: "bytes32", name: "s", type: "bytes32" },
|
||||
{ internalType: "uint256", name: "idCommitment", type: "uint256" },
|
||||
{ internalType: "uint32", name: "rateLimit", type: "uint32" },
|
||||
{
|
||||
name: "idCommitment",
|
||||
type: "uint256"
|
||||
},
|
||||
{
|
||||
name: "rateLimit",
|
||||
type: "uint32"
|
||||
},
|
||||
{
|
||||
internalType: "uint256[]",
|
||||
name: "idCommitmentsToErase",
|
||||
type: "uint256[]"
|
||||
}
|
||||
],
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable"
|
||||
},
|
||||
{
|
||||
type: "function",
|
||||
name: "registerWithPermit",
|
||||
inputs: [
|
||||
{
|
||||
name: "owner",
|
||||
type: "address"
|
||||
},
|
||||
{
|
||||
name: "deadline",
|
||||
type: "uint256"
|
||||
},
|
||||
{
|
||||
name: "v",
|
||||
type: "uint8"
|
||||
},
|
||||
{
|
||||
name: "r",
|
||||
type: "bytes32"
|
||||
},
|
||||
{
|
||||
name: "s",
|
||||
type: "bytes32"
|
||||
},
|
||||
{
|
||||
name: "idCommitment",
|
||||
type: "uint256"
|
||||
},
|
||||
{
|
||||
name: "rateLimit",
|
||||
type: "uint32"
|
||||
},
|
||||
{
|
||||
name: "idCommitmentsToErase",
|
||||
type: "uint256[]"
|
||||
}
|
||||
],
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable"
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
type: "event",
|
||||
name: "MembershipRegistered",
|
||||
inputs: [
|
||||
{
|
||||
name: "idCommitment",
|
||||
type: "uint256",
|
||||
indexed: false
|
||||
},
|
||||
{
|
||||
name: "rateLimit",
|
||||
type: "uint32",
|
||||
indexed: false
|
||||
},
|
||||
{
|
||||
name: "index",
|
||||
type: "uint256",
|
||||
indexed: false
|
||||
}
|
||||
],
|
||||
anonymous: false
|
||||
inputs: [],
|
||||
name: "renounceOwnership",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "root",
|
||||
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
||||
stateMutability: "view",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
type: "event",
|
||||
name: "MembershipRemoved",
|
||||
inputs: [
|
||||
{
|
||||
name: "idCommitment",
|
||||
type: "uint256",
|
||||
indexed: false
|
||||
},
|
||||
{
|
||||
name: "index",
|
||||
type: "uint256",
|
||||
indexed: false
|
||||
internalType: "uint32",
|
||||
name: "_activeDurationForNewMembership",
|
||||
type: "uint32"
|
||||
}
|
||||
],
|
||||
anonymous: false
|
||||
name: "setActiveDuration",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "uint32",
|
||||
name: "_gracePeriodDurationForNewMembership",
|
||||
type: "uint32"
|
||||
}
|
||||
],
|
||||
name: "setGracePeriodDuration",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "uint32",
|
||||
name: "_maxMembershipRateLimit",
|
||||
type: "uint32"
|
||||
}
|
||||
],
|
||||
name: "setMaxMembershipRateLimit",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "uint32", name: "_maxTotalRateLimit", type: "uint32" }
|
||||
],
|
||||
name: "setMaxTotalRateLimit",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "uint32",
|
||||
name: "_minMembershipRateLimit",
|
||||
type: "uint32"
|
||||
}
|
||||
],
|
||||
name: "setMinMembershipRateLimit",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "address", name: "_priceCalculator", type: "address" }
|
||||
],
|
||||
name: "setPriceCalculator",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [{ internalType: "address", name: "newOwner", type: "address" }],
|
||||
name: "transferOwnership",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "address", name: "newImplementation", type: "address" }
|
||||
],
|
||||
name: "upgradeTo",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{ internalType: "address", name: "newImplementation", type: "address" },
|
||||
{ internalType: "bytes", name: "data", type: "bytes" }
|
||||
],
|
||||
name: "upgradeToAndCall",
|
||||
outputs: [],
|
||||
stateMutability: "payable",
|
||||
type: "function"
|
||||
},
|
||||
{
|
||||
inputs: [{ internalType: "address", name: "token", type: "address" }],
|
||||
name: "withdraw",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function"
|
||||
}
|
||||
];
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { RLN_ABI } from "./abi.js";
|
||||
|
||||
export const SEPOLIA_CONTRACT = {
|
||||
chainId: 11155111,
|
||||
address: "0xCB33Aa5B38d79E3D9Fa8B10afF38AA201399a7e3",
|
||||
export const LINEA_CONTRACT = {
|
||||
chainId: 59141,
|
||||
address: "0xb9cd878c90e49f797b4431fbf4fb333108cb90e6",
|
||||
abi: RLN_ABI
|
||||
};
|
||||
|
||||
|
||||
@ -4,244 +4,87 @@ 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: 1000,
|
||||
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: () => [mockRLNRegisteredEvent()],
|
||||
provider: {
|
||||
getLogs: () => [],
|
||||
getBlockNumber: () => Promise.resolve(1000),
|
||||
getNetwork: () => Promise.resolve({ chainId: 11155111 })
|
||||
},
|
||||
filters: {
|
||||
MembershipRegistered: () => ({}),
|
||||
MembershipRemoved: () => ({})
|
||||
},
|
||||
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 mockedRegistryContract = createMockRegistryContract({
|
||||
queryFilter: queryFilterStub
|
||||
});
|
||||
|
||||
const membershipRegisteredEvent = mockRLNRegisteredEvent();
|
||||
const rlnContract = await initializeRLNContract(
|
||||
rlnInstance,
|
||||
mockedRegistryContract
|
||||
);
|
||||
|
||||
const queryFilterStub = sinon.stub().returns([membershipRegisteredEvent]);
|
||||
const mockedRegistryContract = {
|
||||
queryFilter: queryFilterStub,
|
||||
provider: {
|
||||
getLogs: () => [],
|
||||
getBlockNumber: () => Promise.resolve(1000)
|
||||
},
|
||||
interface: {
|
||||
getEvent: (eventName: string) => ({
|
||||
name: eventName,
|
||||
format: () => {}
|
||||
})
|
||||
},
|
||||
filters: {
|
||||
MembershipRegistered: () => ({}),
|
||||
MembershipRemoved: () => ({})
|
||||
},
|
||||
on: () => ({}),
|
||||
removeAllListeners: () => ({})
|
||||
};
|
||||
await rlnContract.fetchMembers(rlnInstance, {
|
||||
fromBlock: 0,
|
||||
fetchRange: 1000,
|
||||
fetchChunks: 2
|
||||
});
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
expect(queryFilterStub.called).to.be.true;
|
||||
});
|
||||
|
||||
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().returns({
|
||||
wait: () =>
|
||||
Promise.resolve({
|
||||
events: [
|
||||
{
|
||||
event: "MembershipRegistered",
|
||||
args: {
|
||||
idCommitment: formatIdCommitment(identity.IDCommitmentBigInt),
|
||||
rateLimit: DEFAULT_RATE_LIMIT,
|
||||
index: ethers.BigNumber.from(1)
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
).to.be.true;
|
||||
expect(queryFilterStub.called).to.be.true;
|
||||
});
|
||||
|
||||
const mockedRegistryContract = {
|
||||
register: registerStub,
|
||||
queryFilter: () => [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: () => ({}),
|
||||
MembershipRemoved: () => ({})
|
||||
},
|
||||
on: () => ({}),
|
||||
removeAllListeners: () => ({})
|
||||
};
|
||||
it("should register a member", async () => {
|
||||
const { rlnInstance, identity, insertMemberSpy } =
|
||||
await createTestRLNInstance();
|
||||
|
||||
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 registerStub = createRegisterStub(identity);
|
||||
const mockedRegistryContract = createMockRegistryContract({
|
||||
register: registerStub,
|
||||
queryFilter: () => []
|
||||
});
|
||||
|
||||
const rlnContract = await initializeRLNContract(
|
||||
rlnInstance,
|
||||
mockedRegistryContract
|
||||
);
|
||||
|
||||
const decryptedCredentials =
|
||||
await rlnContract.registerWithIdentity(identity);
|
||||
|
||||
if (!decryptedCredentials) {
|
||||
throw new Error("Failed to retrieve credentials");
|
||||
}
|
||||
|
||||
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(
|
||||
identity.IDCommitmentBigInt,
|
||||
DEFAULT_RATE_LIMIT,
|
||||
[],
|
||||
{
|
||||
gasLimit: 300000
|
||||
}
|
||||
)
|
||||
).to.be.true;
|
||||
|
||||
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(2);
|
||||
expect(insertMemberSpy.getCall(1).args[0]).to.deep.equal(
|
||||
expectedIdCommitment
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function mockRLNRegisteredEvent(idCommitment?: string): ethers.Event {
|
||||
return {
|
||||
args: {
|
||||
idCommitment:
|
||||
idCommitment ||
|
||||
"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
||||
rateLimit: DEFAULT_RATE_LIMIT,
|
||||
index: ethers.BigNumber.from(1)
|
||||
},
|
||||
event: "MembershipRegistered"
|
||||
} as unknown as ethers.Event;
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ interface RLNContractInitOptions extends RLNContractOptions {
|
||||
|
||||
export interface MembershipRegisteredEvent {
|
||||
idCommitment: string;
|
||||
rateLimit: number;
|
||||
membershipRateLimit: ethers.BigNumber;
|
||||
index: ethers.BigNumber;
|
||||
}
|
||||
|
||||
@ -65,7 +65,8 @@ export class RLNContract {
|
||||
|
||||
private _members: Map<number, Member> = new Map();
|
||||
private _membersFilter: ethers.EventFilter;
|
||||
private _membersRemovedFilter: ethers.EventFilter;
|
||||
private _membershipErasedFilter: ethers.EventFilter;
|
||||
private _membersExpiredFilter: ethers.EventFilter;
|
||||
|
||||
/**
|
||||
* Asynchronous initializer for RLNContract.
|
||||
@ -94,15 +95,7 @@ export class RLNContract {
|
||||
contract
|
||||
} = options;
|
||||
|
||||
if (
|
||||
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
||||
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
||||
) {
|
||||
throw new Error(
|
||||
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE} messages per epoch`
|
||||
);
|
||||
}
|
||||
|
||||
this.validateRateLimit(rateLimit);
|
||||
this.rateLimit = rateLimit;
|
||||
|
||||
const initialRoot = rlnInstance.zerokit.getMerkleRoot();
|
||||
@ -111,9 +104,25 @@ export class RLNContract {
|
||||
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
|
||||
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
|
||||
|
||||
// Initialize event filters for MembershipRegistered and MembershipRemoved
|
||||
// Initialize event filters
|
||||
this._membersFilter = this.contract.filters.MembershipRegistered();
|
||||
this._membersRemovedFilter = this.contract.filters.MembershipRemoved();
|
||||
this._membershipErasedFilter = this.contract.filters.MembershipErased();
|
||||
this._membersExpiredFilter = this.contract.filters.MembershipExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the rate limit is within the allowed range
|
||||
* @throws Error if the rate limit is outside the allowed range
|
||||
*/
|
||||
private validateRateLimit(rateLimit: number): void {
|
||||
if (
|
||||
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
||||
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
||||
) {
|
||||
throw new Error(
|
||||
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE} messages per epoch`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,7 +191,7 @@ export class RLNContract {
|
||||
this.contract.maxTotalRateLimit(),
|
||||
this.contract.currentTotalRateLimit()
|
||||
]);
|
||||
return maxTotal.sub(currentTotal).toNumber();
|
||||
return Number(maxTotal) - Number(currentTotal);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,6 +199,7 @@ export class RLNContract {
|
||||
* @param newRateLimit The new rate limit to use
|
||||
*/
|
||||
public async setRateLimit(newRateLimit: number): Promise<void> {
|
||||
this.validateRateLimit(newRateLimit);
|
||||
this.rateLimit = newRateLimit;
|
||||
}
|
||||
|
||||
@ -207,11 +217,18 @@ export class RLNContract {
|
||||
return this._membersFilter;
|
||||
}
|
||||
|
||||
private get membersRemovedFilter(): ethers.EventFilter {
|
||||
if (!this._membersRemovedFilter) {
|
||||
throw Error("MembersRemoved filter was not initialized.");
|
||||
private get membershipErasedFilter(): ethers.EventFilter {
|
||||
if (!this._membershipErasedFilter) {
|
||||
throw Error("MembershipErased filter was not initialized.");
|
||||
}
|
||||
return this._membersRemovedFilter;
|
||||
return this._membershipErasedFilter;
|
||||
}
|
||||
|
||||
private get membersExpiredFilter(): ethers.EventFilter {
|
||||
if (!this._membersExpiredFilter) {
|
||||
throw Error("MembersExpired filter was not initialized.");
|
||||
}
|
||||
return this._membersExpiredFilter;
|
||||
}
|
||||
|
||||
public async fetchMembers(
|
||||
@ -226,10 +243,19 @@ export class RLNContract {
|
||||
const removedMemberEvents = await queryFilter(this.contract, {
|
||||
fromBlock: this.deployBlock,
|
||||
...options,
|
||||
membersFilter: this.membersRemovedFilter
|
||||
membersFilter: this.membershipErasedFilter
|
||||
});
|
||||
const expiredMemberEvents = await queryFilter(this.contract, {
|
||||
fromBlock: this.deployBlock,
|
||||
...options,
|
||||
membersFilter: this.membersExpiredFilter
|
||||
});
|
||||
|
||||
const events = [...registeredMemberEvents, ...removedMemberEvents];
|
||||
const events = [
|
||||
...registeredMemberEvents,
|
||||
...removedMemberEvents,
|
||||
...expiredMemberEvents
|
||||
];
|
||||
this.processEvents(rlnInstance, events);
|
||||
}
|
||||
|
||||
@ -242,8 +268,26 @@ export class RLNContract {
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.event === "MembershipRemoved") {
|
||||
const index = evt.args.index as ethers.BigNumber;
|
||||
if (
|
||||
evt.event === "MembershipErased" ||
|
||||
evt.event === "MembershipExpired"
|
||||
) {
|
||||
let index = evt.args.index;
|
||||
|
||||
if (!index) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
if (toRemoveVal != undefined) {
|
||||
toRemoveVal.push(index.toNumber());
|
||||
@ -275,15 +319,21 @@ export class RLNContract {
|
||||
if (!evt.args) return;
|
||||
|
||||
const _idCommitment = evt.args.idCommitment as string;
|
||||
const index = evt.args.index as ethers.BigNumber;
|
||||
let index = evt.args.index;
|
||||
|
||||
if (!_idCommitment || !index) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof index === "number" || typeof index === "string") {
|
||||
index = ethers.BigNumber.from(index);
|
||||
}
|
||||
|
||||
const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
|
||||
rlnInstance.zerokit.insertMember(idCommitment);
|
||||
this._members.set(index.toNumber(), {
|
||||
|
||||
const numericIndex = index.toNumber();
|
||||
this._members.set(numericIndex, {
|
||||
index,
|
||||
idCommitment: _idCommitment
|
||||
});
|
||||
@ -316,7 +366,7 @@ export class RLNContract {
|
||||
this.membersFilter,
|
||||
(
|
||||
_idCommitment: string,
|
||||
_rateLimit: number,
|
||||
_membershipRateLimit: ethers.BigNumber,
|
||||
_index: ethers.BigNumber,
|
||||
event: ethers.Event
|
||||
) => {
|
||||
@ -325,9 +375,22 @@ export class RLNContract {
|
||||
);
|
||||
|
||||
this.contract.on(
|
||||
this.membersRemovedFilter,
|
||||
this.membershipErasedFilter,
|
||||
(
|
||||
_idCommitment: string,
|
||||
_membershipRateLimit: ethers.BigNumber,
|
||||
_index: ethers.BigNumber,
|
||||
event: ethers.Event
|
||||
) => {
|
||||
this.processEvents(rlnInstance, [event]);
|
||||
}
|
||||
);
|
||||
|
||||
this.contract.on(
|
||||
this.membersExpiredFilter,
|
||||
(
|
||||
_idCommitment: string,
|
||||
_membershipRateLimit: ethers.BigNumber,
|
||||
_index: ethers.BigNumber,
|
||||
event: ethers.Event
|
||||
) => {
|
||||
@ -344,15 +407,45 @@ export class RLNContract {
|
||||
`Registering identity with rate limit: ${this.rateLimit} messages/epoch`
|
||||
);
|
||||
|
||||
// Check if the ID commitment is already registered
|
||||
const existingIndex = await this.getMemberIndex(
|
||||
identity.IDCommitmentBigInt.toString()
|
||||
);
|
||||
if (existingIndex) {
|
||||
throw new Error(
|
||||
`ID commitment is already registered with index ${existingIndex}`
|
||||
);
|
||||
}
|
||||
|
||||
// Check if there's enough remaining rate limit
|
||||
const remainingRateLimit = await this.getRemainingTotalRateLimit();
|
||||
if (remainingRateLimit < this.rateLimit) {
|
||||
throw new Error(
|
||||
`Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`
|
||||
);
|
||||
}
|
||||
|
||||
const estimatedGas = await this.contract.estimateGas.register(
|
||||
identity.IDCommitmentBigInt,
|
||||
this.rateLimit,
|
||||
[]
|
||||
);
|
||||
const gasLimit = estimatedGas.add(10000);
|
||||
|
||||
const txRegisterResponse: ethers.ContractTransaction =
|
||||
await this.contract.register(
|
||||
identity.IDCommitmentBigInt,
|
||||
this.rateLimit,
|
||||
[],
|
||||
{ gasLimit: 300000 }
|
||||
{ gasLimit }
|
||||
);
|
||||
|
||||
const txRegisterReceipt = await txRegisterResponse.wait();
|
||||
|
||||
if (txRegisterReceipt.status === 0) {
|
||||
throw new Error("Transaction failed on-chain");
|
||||
}
|
||||
|
||||
const memberRegistered = txRegisterReceipt.events?.find(
|
||||
(event) => event.event === "MembershipRegistered"
|
||||
);
|
||||
@ -366,30 +459,55 @@ export class RLNContract {
|
||||
|
||||
const decodedData: MembershipRegisteredEvent = {
|
||||
idCommitment: memberRegistered.args.idCommitment,
|
||||
rateLimit: memberRegistered.args.rateLimit,
|
||||
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
||||
index: memberRegistered.args.index
|
||||
};
|
||||
|
||||
log.info(
|
||||
`Successfully registered membership with index ${decodedData.index} ` +
|
||||
`and rate limit ${decodedData.rateLimit}`
|
||||
`and rate limit ${decodedData.membershipRateLimit}`
|
||||
);
|
||||
|
||||
const network = await this.contract.provider.getNetwork();
|
||||
const address = this.contract.address;
|
||||
const membershipId = decodedData.index.toNumber();
|
||||
const membershipId = Number(decodedData.index);
|
||||
|
||||
return {
|
||||
identity,
|
||||
membership: {
|
||||
address,
|
||||
treeIndex: membershipId,
|
||||
chainId: network.chainId
|
||||
chainId: network.chainId,
|
||||
rateLimit: decodedData.membershipRateLimit.toNumber()
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(`Error in registerWithIdentity: ${(error as Error).message}`);
|
||||
return undefined;
|
||||
if (error instanceof Error) {
|
||||
const errorMessage = error.message;
|
||||
log.error("registerWithIdentity - error message:", errorMessage);
|
||||
log.error("registerWithIdentity - error stack:", error.stack);
|
||||
|
||||
// Try to extract more specific error information
|
||||
if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
|
||||
throw new Error(
|
||||
"Registration failed: Cannot exceed maximum total rate limit"
|
||||
);
|
||||
} else if (errorMessage.includes("InvalidIdCommitment")) {
|
||||
throw new Error("Registration failed: Invalid ID commitment");
|
||||
} else if (errorMessage.includes("InvalidMembershipRateLimit")) {
|
||||
throw new Error("Registration failed: Invalid membership rate limit");
|
||||
} else if (errorMessage.includes("execution reverted")) {
|
||||
throw new Error(
|
||||
"Contract execution reverted. Check contract requirements."
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Error in registerWithIdentity: ${errorMessage}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error("Unknown error in registerWithIdentity", {
|
||||
cause: error
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,25 +585,26 @@ export class RLNContract {
|
||||
|
||||
const decodedData: MembershipRegisteredEvent = {
|
||||
idCommitment: memberRegistered.args.idCommitment,
|
||||
rateLimit: memberRegistered.args.rateLimit,
|
||||
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
||||
index: memberRegistered.args.index
|
||||
};
|
||||
|
||||
log.info(
|
||||
`Successfully registered membership with permit. Index: ${decodedData.index}, ` +
|
||||
`Rate limit: ${decodedData.rateLimit}, Erased ${idCommitmentsToErase.length} commitments`
|
||||
`Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`
|
||||
);
|
||||
|
||||
const network = await this.contract.provider.getNetwork();
|
||||
const address = this.contract.address;
|
||||
const membershipId = decodedData.index.toNumber();
|
||||
const membershipId = ethers.BigNumber.from(decodedData.index).toNumber();
|
||||
|
||||
return {
|
||||
identity,
|
||||
membership: {
|
||||
address,
|
||||
treeIndex: membershipId,
|
||||
chainId: network.chainId
|
||||
chainId: network.chainId,
|
||||
rateLimit: decodedData.membershipRateLimit.toNumber()
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
@ -560,16 +679,9 @@ export class RLNContract {
|
||||
|
||||
public async registerMembership(
|
||||
idCommitment: string,
|
||||
rateLimit: number = DEFAULT_RATE_LIMIT
|
||||
rateLimit: number = this.rateLimit
|
||||
): Promise<ethers.ContractTransaction> {
|
||||
if (
|
||||
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
||||
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
||||
) {
|
||||
throw new Error(
|
||||
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
|
||||
);
|
||||
}
|
||||
this.validateRateLimit(rateLimit);
|
||||
return this.contract.register(idCommitment, rateLimit, []);
|
||||
}
|
||||
|
||||
|
||||
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, LINEA_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(LINEA_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: LINEA_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, LINEA_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: LINEA_CONTRACT.address }),
|
||||
MembershipErased: () => ({ address: LINEA_CONTRACT.address }),
|
||||
MembershipExpired: () => ({ address: LINEA_CONTRACT.address })
|
||||
};
|
||||
}
|
||||
|
||||
type ContractOverrides = Partial<{
|
||||
filters: Record<string, unknown>;
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
|
||||
export function createMockRegistryContract(
|
||||
overrides: ContractOverrides = {}
|
||||
): ethers.Contract {
|
||||
const filters = {
|
||||
MembershipRegistered: () => ({ address: LINEA_CONTRACT.address }),
|
||||
MembershipErased: () => ({ address: LINEA_CONTRACT.address }),
|
||||
MembershipExpired: () => ({ address: LINEA_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: LINEA_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),
|
||||
membershipRateLimit: ethers.BigNumber.from(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: LINEA_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
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { RLNDecoder, RLNEncoder } from "./codec.js";
|
||||
import { RLN_ABI } from "./contract/abi.js";
|
||||
import { RLNContract, SEPOLIA_CONTRACT } from "./contract/index.js";
|
||||
import { LINEA_CONTRACT, RLNContract } from "./contract/index.js";
|
||||
import { createRLN } from "./create.js";
|
||||
import { IdentityCredential } from "./identity.js";
|
||||
import { Keystore } from "./keystore/index.js";
|
||||
@ -19,7 +19,18 @@ export {
|
||||
RLNDecoder,
|
||||
MerkleRootTracker,
|
||||
RLNContract,
|
||||
SEPOLIA_CONTRACT,
|
||||
LINEA_CONTRACT,
|
||||
extractMetaMaskSigner,
|
||||
RLN_ABI
|
||||
};
|
||||
|
||||
export type {
|
||||
DecryptedCredentials,
|
||||
EncryptedCredentials,
|
||||
Keccak256Hash,
|
||||
KeystoreEntity,
|
||||
MembershipHash,
|
||||
MembershipInfo,
|
||||
Password,
|
||||
Sha256Hash
|
||||
} from "./keystore/types.js";
|
||||
|
||||
@ -231,7 +231,8 @@ describe("Keystore", () => {
|
||||
const membership = {
|
||||
chainId: "0xAA36A7",
|
||||
treeIndex: 8,
|
||||
address: "0x8e1F3742B987d8BA376c0CBbD7357fE1F003ED71"
|
||||
address: "0x8e1F3742B987d8BA376c0CBbD7357fE1F003ED71",
|
||||
rateLimit: undefined
|
||||
} as unknown as MembershipInfo;
|
||||
|
||||
const store = Keystore.create();
|
||||
@ -246,6 +247,11 @@ describe("Keystore", () => {
|
||||
expectedHash,
|
||||
DEFAULT_PASSWORD
|
||||
);
|
||||
|
||||
if (!actualCredentials) {
|
||||
throw new Error("Failed to retrieve credentials");
|
||||
}
|
||||
|
||||
expect(actualCredentials).to.deep.equalInAnyOrder({
|
||||
identity,
|
||||
membership
|
||||
@ -276,7 +282,8 @@ describe("Keystore", () => {
|
||||
const membership = {
|
||||
chainId: "0xAA36A7",
|
||||
treeIndex: 8,
|
||||
address: "0x8e1F3742B987d8BA376c0CBbD7357fE1F003ED71"
|
||||
address: "0x8e1F3742B987d8BA376c0CBbD7357fE1F003ED71",
|
||||
rateLimit: undefined
|
||||
} as unknown as MembershipInfo;
|
||||
|
||||
const store = Keystore.fromObject(NWAKU_KEYSTORE as any);
|
||||
|
||||
@ -274,7 +274,8 @@ export class Keystore {
|
||||
membership: {
|
||||
treeIndex: _.get(obj, "treeIndex"),
|
||||
chainId: _.get(obj, "membershipContract.chainId"),
|
||||
address: _.get(obj, "membershipContract.address")
|
||||
address: _.get(obj, "membershipContract.address"),
|
||||
rateLimit: _.get(obj, "membershipContract.rateLimit")
|
||||
}
|
||||
};
|
||||
} catch (err) {
|
||||
|
||||
@ -11,6 +11,7 @@ export type MembershipInfo = {
|
||||
chainId: number;
|
||||
address: string;
|
||||
treeIndex: number;
|
||||
rateLimit: number;
|
||||
};
|
||||
|
||||
export type KeystoreEntity = {
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
type RLNEncoder
|
||||
} from "./codec.js";
|
||||
import { DEFAULT_RATE_LIMIT } from "./contract/constants.js";
|
||||
import { RLNContract, SEPOLIA_CONTRACT } from "./contract/index.js";
|
||||
import { LINEA_CONTRACT, RLNContract } from "./contract/index.js";
|
||||
import { IdentityCredential } from "./identity.js";
|
||||
import { Keystore } from "./keystore/index.js";
|
||||
import type {
|
||||
@ -108,7 +108,7 @@ type StartRLNOptions = {
|
||||
*/
|
||||
signer?: ethers.Signer;
|
||||
/**
|
||||
* If not set - will use default SEPOLIA_CONTRACT address.
|
||||
* If not set - will use default LINEA_CONTRACT address.
|
||||
*/
|
||||
address?: string;
|
||||
/**
|
||||
@ -190,10 +190,10 @@ export class RLNInstance {
|
||||
const address =
|
||||
credentials?.membership.address ||
|
||||
options.address ||
|
||||
SEPOLIA_CONTRACT.address;
|
||||
LINEA_CONTRACT.address;
|
||||
|
||||
if (address === SEPOLIA_CONTRACT.address) {
|
||||
chainId = SEPOLIA_CONTRACT.chainId;
|
||||
if (address === LINEA_CONTRACT.address) {
|
||||
chainId = LINEA_CONTRACT.chainId;
|
||||
}
|
||||
|
||||
const signer = options.signer || (await extractMetaMaskSigner());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user