mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-07 16:23:09 +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",
|
"ahadns",
|
||||||
"Alives",
|
"Alives",
|
||||||
"alphabeta",
|
"alphabeta",
|
||||||
"Arraylike",
|
|
||||||
"arrayify",
|
"arrayify",
|
||||||
|
"Arraylike",
|
||||||
"asym",
|
"asym",
|
||||||
"autoshard",
|
"autoshard",
|
||||||
"autosharding",
|
"autosharding",
|
||||||
@ -70,6 +70,7 @@
|
|||||||
"libauth",
|
"libauth",
|
||||||
"libp",
|
"libp",
|
||||||
"lightpush",
|
"lightpush",
|
||||||
|
"LINEA",
|
||||||
"livechat",
|
"livechat",
|
||||||
"Merkle",
|
"Merkle",
|
||||||
"mkdir",
|
"mkdir",
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -12,4 +12,7 @@ docs
|
|||||||
test-results
|
test-results
|
||||||
playwright-report
|
playwright-report
|
||||||
example
|
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
|
// Usage examples coming soon
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Constants
|
||||||
|
|
||||||
|
- Implementation contract: 0xde2260ca49300357d5af4153cda0d18f7b3ea9b3
|
||||||
|
- Proxy contract: 0xb9cd878c90e49f797b4431fbf4fb333108cb90e6
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT OR Apache-2.0
|
MIT OR Apache-2.0
|
||||||
@ -59,14 +59,9 @@
|
|||||||
"@types/sinon": "^17.0.3",
|
"@types/sinon": "^17.0.3",
|
||||||
"@waku/build-utils": "^1.0.0",
|
"@waku/build-utils": "^1.0.0",
|
||||||
"@waku/message-encryption": "^0.0.32",
|
"@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",
|
"deep-equal-in-any-order": "^2.0.6",
|
||||||
"fast-check": "^3.23.2",
|
"fast-check": "^3.23.2",
|
||||||
"rollup-plugin-copy": "^3.5.0",
|
"rollup-plugin-copy": "^3.5.0"
|
||||||
"sinon": "^19.0.2"
|
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@ -86,6 +81,11 @@
|
|||||||
"ethereum-cryptography": "^3.1.0",
|
"ethereum-cryptography": "^3.1.0",
|
||||||
"ethers": "^5.7.2",
|
"ethers": "^5.7.2",
|
||||||
"lodash": "^4.17.21",
|
"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,
|
createEncoder,
|
||||||
DecodedMessage
|
DecodedMessage
|
||||||
} from "@waku/core/lib/message/version_0";
|
} from "@waku/core/lib/message/version_0";
|
||||||
import type { IProtoMessage } from "@waku/interfaces";
|
|
||||||
import {
|
import {
|
||||||
generatePrivateKey,
|
generatePrivateKey,
|
||||||
generateSymmetricKey,
|
generateSymmetricKey,
|
||||||
@ -25,40 +24,30 @@ import {
|
|||||||
RLNDecoder,
|
RLNDecoder,
|
||||||
RLNEncoder
|
RLNEncoder
|
||||||
} from "./codec.js";
|
} 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 { RlnMessage } from "./message.js";
|
||||||
import { epochBytesToInt } from "./utils/index.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", () => {
|
describe("RLN codec with version 0", () => {
|
||||||
it("toWire", async function () {
|
it("toWire", async function () {
|
||||||
const rlnInstance = await createRLN();
|
const { rlnInstance, credential, index, payload } =
|
||||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
await createTestRLNCodecSetup();
|
||||||
const index = 0;
|
|
||||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
|
||||||
|
|
||||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
|
||||||
|
|
||||||
const rlnEncoder = createRLNEncoder({
|
const rlnEncoder = createRLNEncoder({
|
||||||
encoder: createEncoder({ contentTopic: TestContentTopic }),
|
encoder: createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic }),
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
index,
|
index,
|
||||||
credential
|
credential
|
||||||
});
|
});
|
||||||
const rlnDecoder = createRLNDecoder({
|
const rlnDecoder = createRLNDecoder({
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
decoder: createDecoder(TestContentTopic)
|
decoder: createDecoder(TEST_CONSTANTS.contentTopic)
|
||||||
});
|
});
|
||||||
|
|
||||||
const bytes = await rlnEncoder.toWire({ payload });
|
const bytes = await rlnEncoder.toWire({ payload });
|
||||||
@ -67,78 +56,49 @@ describe("RLN codec with version 0", () => {
|
|||||||
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
||||||
expect(protoResult).to.not.be.undefined;
|
expect(protoResult).to.not.be.undefined;
|
||||||
const msg = (await rlnDecoder.fromProtoObj(
|
const msg = (await rlnDecoder.fromProtoObj(
|
||||||
EMPTY_PUBSUB_TOPIC,
|
TEST_CONSTANTS.emptyPubsubTopic,
|
||||||
protoResult!
|
protoResult!
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("toProtoObj", async function () {
|
it("toProtoObj", async function () {
|
||||||
const rlnInstance = await createRLN();
|
const { rlnInstance, credential, index, payload } =
|
||||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
await createTestRLNCodecSetup();
|
||||||
const index = 0;
|
|
||||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
|
||||||
|
|
||||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
|
||||||
|
|
||||||
const rlnEncoder = new RLNEncoder(
|
const rlnEncoder = new RLNEncoder(
|
||||||
createEncoder({ contentTopic: TestContentTopic }),
|
createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic }),
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
index,
|
index,
|
||||||
credential
|
credential
|
||||||
);
|
);
|
||||||
const rlnDecoder = new RLNDecoder(
|
const rlnDecoder = new RLNDecoder(
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
createDecoder(TestContentTopic)
|
createDecoder(TEST_CONSTANTS.contentTopic)
|
||||||
);
|
);
|
||||||
|
|
||||||
const proto = await rlnEncoder.toProtoObj({ payload });
|
const proto = await rlnEncoder.toProtoObj({ payload });
|
||||||
|
|
||||||
expect(proto).to.not.be.undefined;
|
expect(proto).to.not.be.undefined;
|
||||||
const msg = (await rlnDecoder.fromProtoObj(
|
const msg = (await rlnDecoder.fromProtoObj(
|
||||||
EMPTY_PUBSUB_TOPIC,
|
TEST_CONSTANTS.emptyPubsubTopic,
|
||||||
proto!
|
proto!
|
||||||
)) as RlnMessage<DecodedMessage>;
|
)) as RlnMessage<DecodedMessage>;
|
||||||
|
|
||||||
expect(msg).to.not.be.undefined;
|
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("RLN codec with version 1", () => {
|
describe("RLN codec with version 1", () => {
|
||||||
it("Symmetric, toWire", async function () {
|
it("Symmetric, toWire", async function () {
|
||||||
const rlnInstance = await createRLN();
|
const { rlnInstance, credential, index, payload } =
|
||||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
await createTestRLNCodecSetup();
|
||||||
const index = 0;
|
|
||||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
|
||||||
|
|
||||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
|
||||||
|
|
||||||
const symKey = generateSymmetricKey();
|
const symKey = generateSymmetricKey();
|
||||||
|
|
||||||
const rlnEncoder = new RLNEncoder(
|
const rlnEncoder = new RLNEncoder(
|
||||||
createSymEncoder({
|
createSymEncoder({
|
||||||
contentTopic: TestContentTopic,
|
contentTopic: TEST_CONSTANTS.contentTopic,
|
||||||
symKey
|
symKey
|
||||||
}),
|
}),
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
@ -147,45 +107,30 @@ describe("RLN codec with version 1", () => {
|
|||||||
);
|
);
|
||||||
const rlnDecoder = new RLNDecoder(
|
const rlnDecoder = new RLNDecoder(
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
createSymDecoder(TestContentTopic, symKey)
|
createSymDecoder(TEST_CONSTANTS.contentTopic, symKey)
|
||||||
);
|
);
|
||||||
|
|
||||||
const bytes = await rlnEncoder.toWire({ payload });
|
const bytes = await rlnEncoder.toWire({ payload });
|
||||||
|
|
||||||
expect(bytes).to.not.be.undefined;
|
expect(bytes).to.not.be.undefined;
|
||||||
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
||||||
|
|
||||||
expect(protoResult).to.not.be.undefined;
|
expect(protoResult).to.not.be.undefined;
|
||||||
const msg = (await rlnDecoder.fromProtoObj(
|
const msg = (await rlnDecoder.fromProtoObj(
|
||||||
EMPTY_PUBSUB_TOPIC,
|
TEST_CONSTANTS.emptyPubsubTopic,
|
||||||
protoResult!
|
protoResult!
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Symmetric, toProtoObj", async function () {
|
it("Symmetric, toProtoObj", async function () {
|
||||||
const rlnInstance = await createRLN();
|
const { rlnInstance, credential, index, payload } =
|
||||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
await createTestRLNCodecSetup();
|
||||||
const index = 0;
|
|
||||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
|
||||||
|
|
||||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
|
||||||
|
|
||||||
const symKey = generateSymmetricKey();
|
const symKey = generateSymmetricKey();
|
||||||
|
|
||||||
const rlnEncoder = new RLNEncoder(
|
const rlnEncoder = new RLNEncoder(
|
||||||
createSymEncoder({
|
createSymEncoder({
|
||||||
contentTopic: TestContentTopic,
|
contentTopic: TEST_CONSTANTS.contentTopic,
|
||||||
symKey
|
symKey
|
||||||
}),
|
}),
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
@ -194,45 +139,29 @@ describe("RLN codec with version 1", () => {
|
|||||||
);
|
);
|
||||||
const rlnDecoder = new RLNDecoder(
|
const rlnDecoder = new RLNDecoder(
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
createSymDecoder(TestContentTopic, symKey)
|
createSymDecoder(TEST_CONSTANTS.contentTopic, symKey)
|
||||||
);
|
);
|
||||||
|
|
||||||
const proto = await rlnEncoder.toProtoObj({ payload });
|
const proto = await rlnEncoder.toProtoObj({ payload });
|
||||||
|
|
||||||
expect(proto).to.not.be.undefined;
|
expect(proto).to.not.be.undefined;
|
||||||
const msg = (await rlnDecoder.fromProtoObj(
|
const msg = (await rlnDecoder.fromProtoObj(
|
||||||
EMPTY_PUBSUB_TOPIC,
|
TEST_CONSTANTS.emptyPubsubTopic,
|
||||||
proto!
|
proto!
|
||||||
)) as RlnMessage<DecodedMessage>;
|
)) as RlnMessage<DecodedMessage>;
|
||||||
|
|
||||||
expect(msg).to.not.be.undefined;
|
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Asymmetric, toWire", async function () {
|
it("Asymmetric, toWire", async function () {
|
||||||
const rlnInstance = await createRLN();
|
const { rlnInstance, credential, index, payload } =
|
||||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
await createTestRLNCodecSetup();
|
||||||
const index = 0;
|
|
||||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
|
||||||
|
|
||||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
|
||||||
|
|
||||||
const privateKey = generatePrivateKey();
|
const privateKey = generatePrivateKey();
|
||||||
const publicKey = getPublicKey(privateKey);
|
const publicKey = getPublicKey(privateKey);
|
||||||
|
|
||||||
const rlnEncoder = new RLNEncoder(
|
const rlnEncoder = new RLNEncoder(
|
||||||
createAsymEncoder({
|
createAsymEncoder({
|
||||||
contentTopic: TestContentTopic,
|
contentTopic: TEST_CONSTANTS.contentTopic,
|
||||||
publicKey
|
publicKey
|
||||||
}),
|
}),
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
@ -241,46 +170,31 @@ describe("RLN codec with version 1", () => {
|
|||||||
);
|
);
|
||||||
const rlnDecoder = new RLNDecoder(
|
const rlnDecoder = new RLNDecoder(
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
createAsymDecoder(TestContentTopic, privateKey)
|
createAsymDecoder(TEST_CONSTANTS.contentTopic, privateKey)
|
||||||
);
|
);
|
||||||
|
|
||||||
const bytes = await rlnEncoder.toWire({ payload });
|
const bytes = await rlnEncoder.toWire({ payload });
|
||||||
|
|
||||||
expect(bytes).to.not.be.undefined;
|
expect(bytes).to.not.be.undefined;
|
||||||
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
||||||
|
|
||||||
expect(protoResult).to.not.be.undefined;
|
expect(protoResult).to.not.be.undefined;
|
||||||
const msg = (await rlnDecoder.fromProtoObj(
|
const msg = (await rlnDecoder.fromProtoObj(
|
||||||
EMPTY_PUBSUB_TOPIC,
|
TEST_CONSTANTS.emptyPubsubTopic,
|
||||||
protoResult!
|
protoResult!
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
expect(msg.rateLimitProof).to.not.be.undefined;
|
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Asymmetric, toProtoObj", async function () {
|
it("Asymmetric, toProtoObj", async function () {
|
||||||
const rlnInstance = await createRLN();
|
const { rlnInstance, credential, index, payload } =
|
||||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
await createTestRLNCodecSetup();
|
||||||
const index = 0;
|
|
||||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
|
||||||
|
|
||||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
|
||||||
|
|
||||||
const privateKey = generatePrivateKey();
|
const privateKey = generatePrivateKey();
|
||||||
const publicKey = getPublicKey(privateKey);
|
const publicKey = getPublicKey(privateKey);
|
||||||
|
|
||||||
const rlnEncoder = new RLNEncoder(
|
const rlnEncoder = new RLNEncoder(
|
||||||
createAsymEncoder({
|
createAsymEncoder({
|
||||||
contentTopic: TestContentTopic,
|
contentTopic: TEST_CONSTANTS.contentTopic,
|
||||||
publicKey
|
publicKey
|
||||||
}),
|
}),
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
@ -289,106 +203,73 @@ describe("RLN codec with version 1", () => {
|
|||||||
);
|
);
|
||||||
const rlnDecoder = new RLNDecoder(
|
const rlnDecoder = new RLNDecoder(
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
createAsymDecoder(TestContentTopic, privateKey)
|
createAsymDecoder(TEST_CONSTANTS.contentTopic, privateKey)
|
||||||
);
|
);
|
||||||
|
|
||||||
const proto = await rlnEncoder.toProtoObj({ payload });
|
const proto = await rlnEncoder.toProtoObj({ payload });
|
||||||
|
|
||||||
expect(proto).to.not.be.undefined;
|
expect(proto).to.not.be.undefined;
|
||||||
const msg = (await rlnDecoder.fromProtoObj(
|
const msg = (await rlnDecoder.fromProtoObj(
|
||||||
EMPTY_PUBSUB_TOPIC,
|
TEST_CONSTANTS.emptyPubsubTopic,
|
||||||
proto!
|
proto!
|
||||||
)) as RlnMessage<DecodedMessage>;
|
)) as RlnMessage<DecodedMessage>;
|
||||||
|
|
||||||
expect(msg).to.not.be.undefined;
|
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 1, rlnInstance);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("RLN Codec - epoch", () => {
|
describe("RLN Codec - epoch", () => {
|
||||||
it("toProtoObj", async function () {
|
it("toProtoObj", async function () {
|
||||||
const rlnInstance = await createRLN();
|
const { rlnInstance, credential, index, payload } =
|
||||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
await createTestRLNCodecSetup();
|
||||||
const index = 0;
|
|
||||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
|
||||||
|
|
||||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
|
||||||
|
|
||||||
const rlnEncoder = new RLNEncoder(
|
const rlnEncoder = new RLNEncoder(
|
||||||
createEncoder({ contentTopic: TestContentTopic }),
|
createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic }),
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
index,
|
index,
|
||||||
credential
|
credential
|
||||||
);
|
);
|
||||||
const rlnDecoder = new RLNDecoder(
|
const rlnDecoder = new RLNDecoder(
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
createDecoder(TestContentTopic)
|
createDecoder(TEST_CONSTANTS.contentTopic)
|
||||||
);
|
);
|
||||||
|
|
||||||
const proto = await rlnEncoder.toProtoObj({ payload });
|
const proto = await rlnEncoder.toProtoObj({ payload });
|
||||||
|
|
||||||
expect(proto).to.not.be.undefined;
|
expect(proto).to.not.be.undefined;
|
||||||
const msg = (await rlnDecoder.fromProtoObj(
|
const msg = (await rlnDecoder.fromProtoObj(
|
||||||
EMPTY_PUBSUB_TOPIC,
|
TEST_CONSTANTS.emptyPubsubTopic,
|
||||||
proto!
|
proto!
|
||||||
)) as RlnMessage<DecodedMessage>;
|
)) as RlnMessage<DecodedMessage>;
|
||||||
|
|
||||||
const epochBytes = proto!.rateLimitProof!.epoch;
|
const epochBytes = proto!.rateLimitProof!.epoch;
|
||||||
const epoch = epochBytesToInt(epochBytes);
|
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!.toString(10).length).to.eq(9);
|
||||||
expect(msg.epoch).to.eq(epoch);
|
expect(msg.epoch).to.eq(epoch);
|
||||||
|
|
||||||
expect(msg.contentTopic).to.eq(TestContentTopic);
|
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance);
|
||||||
expect(msg.msg.version).to.eq(0);
|
|
||||||
expect(msg.payload).to.deep.eq(payload);
|
|
||||||
expect(msg.timestamp).to.not.be.undefined;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("RLN codec with version 0 and meta setter", () => {
|
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 () {
|
it("toWire", async function () {
|
||||||
const rlnInstance = await createRLN();
|
const { rlnInstance, credential, index, payload } =
|
||||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
await createTestRLNCodecSetup();
|
||||||
const index = 0;
|
const metaSetter = createTestMetaSetter();
|
||||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
|
||||||
|
|
||||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
|
||||||
|
|
||||||
const rlnEncoder = createRLNEncoder({
|
const rlnEncoder = createRLNEncoder({
|
||||||
encoder: createEncoder({ contentTopic: TestContentTopic, metaSetter }),
|
encoder: createEncoder({
|
||||||
|
contentTopic: TEST_CONSTANTS.contentTopic,
|
||||||
|
metaSetter
|
||||||
|
}),
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
index,
|
index,
|
||||||
credential
|
credential
|
||||||
});
|
});
|
||||||
const rlnDecoder = createRLNDecoder({
|
const rlnDecoder = createRLNDecoder({
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
decoder: createDecoder(TestContentTopic)
|
decoder: createDecoder(TEST_CONSTANTS.contentTopic)
|
||||||
});
|
});
|
||||||
|
|
||||||
const bytes = await rlnEncoder.toWire({ payload });
|
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!);
|
const protoResult = await rlnDecoder.fromWireToProtoObj(bytes!);
|
||||||
expect(protoResult).to.not.be.undefined;
|
expect(protoResult).to.not.be.undefined;
|
||||||
const msg = (await rlnDecoder.fromProtoObj(
|
const msg = (await rlnDecoder.fromProtoObj(
|
||||||
EMPTY_PUBSUB_TOPIC,
|
TEST_CONSTANTS.emptyPubsubTopic,
|
||||||
protoResult!
|
protoResult!
|
||||||
))!;
|
))!;
|
||||||
|
|
||||||
@ -407,43 +288,30 @@ describe("RLN codec with version 0 and meta setter", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(msg!.meta).to.deep.eq(expectedMeta);
|
expect(msg!.meta).to.deep.eq(expectedMeta);
|
||||||
|
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("toProtoObj", async function () {
|
it("toProtoObj", async function () {
|
||||||
const rlnInstance = await createRLN();
|
const { rlnInstance, credential, index, payload } =
|
||||||
const credential = rlnInstance.zerokit.generateIdentityCredentials();
|
await createTestRLNCodecSetup();
|
||||||
const index = 0;
|
const metaSetter = createTestMetaSetter();
|
||||||
const payload = new Uint8Array([1, 2, 3, 4, 5]);
|
|
||||||
|
|
||||||
rlnInstance.zerokit.insertMember(credential.IDCommitment);
|
|
||||||
|
|
||||||
const rlnEncoder = new RLNEncoder(
|
const rlnEncoder = new RLNEncoder(
|
||||||
createEncoder({ contentTopic: TestContentTopic, metaSetter }),
|
createEncoder({ contentTopic: TEST_CONSTANTS.contentTopic, metaSetter }),
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
index,
|
index,
|
||||||
credential
|
credential
|
||||||
);
|
);
|
||||||
const rlnDecoder = new RLNDecoder(
|
const rlnDecoder = new RLNDecoder(
|
||||||
rlnInstance,
|
rlnInstance,
|
||||||
createDecoder(TestContentTopic)
|
createDecoder(TEST_CONSTANTS.contentTopic)
|
||||||
);
|
);
|
||||||
|
|
||||||
const proto = await rlnEncoder.toProtoObj({ payload });
|
const proto = await rlnEncoder.toProtoObj({ payload });
|
||||||
|
|
||||||
expect(proto).to.not.be.undefined;
|
expect(proto).to.not.be.undefined;
|
||||||
const msg = (await rlnDecoder.fromProtoObj(
|
const msg = (await rlnDecoder.fromProtoObj(
|
||||||
EMPTY_PUBSUB_TOPIC,
|
TEST_CONSTANTS.emptyPubsubTopic,
|
||||||
proto!
|
proto!
|
||||||
)) as RlnMessage<DecodedMessage>;
|
)) 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!.meta).to.deep.eq(expectedMeta);
|
||||||
|
verifyRLNMessage(msg, payload, TEST_CONSTANTS.contentTopic, 0, rlnInstance);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
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 = [
|
export const RLN_ABI = [
|
||||||
|
{ inputs: [], stateMutability: "nonpayable", type: "constructor" },
|
||||||
{
|
{
|
||||||
type: "constructor",
|
inputs: [
|
||||||
inputs: [],
|
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||||
stateMutability: "nonpayable"
|
],
|
||||||
|
name: "CannotEraseActiveMembership",
|
||||||
|
type: "error"
|
||||||
|
},
|
||||||
|
{ inputs: [], name: "CannotExceedMaxTotalRateLimit", type: "error" },
|
||||||
|
{
|
||||||
|
inputs: [
|
||||||
|
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||||
|
],
|
||||||
|
name: "CannotExtendNonGracePeriodMembership",
|
||||||
|
type: "error"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "error",
|
inputs: [
|
||||||
name: "DuplicateIdCommitment",
|
{ internalType: "uint256", name: "idCommitment", type: "uint256" }
|
||||||
inputs: []
|
],
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "error",
|
|
||||||
name: "InvalidIdCommitment",
|
name: "InvalidIdCommitment",
|
||||||
inputs: [
|
type: "error"
|
||||||
{
|
|
||||||
name: "idCommitment",
|
|
||||||
type: "uint256"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
|
{ inputs: [], name: "InvalidMembershipRateLimit", type: "error" },
|
||||||
{
|
{
|
||||||
type: "error",
|
inputs: [
|
||||||
|
{ internalType: "uint256", name: "startIndex", type: "uint256" },
|
||||||
|
{ internalType: "uint256", name: "endIndex", type: "uint256" }
|
||||||
|
],
|
||||||
name: "InvalidPaginationQuery",
|
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: [
|
inputs: [
|
||||||
{
|
{
|
||||||
name: "startIndex",
|
indexed: false,
|
||||||
type: "uint256"
|
internalType: "address",
|
||||||
},
|
name: "previousAdmin",
|
||||||
{
|
|
||||||
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",
|
|
||||||
type: "address"
|
type: "address"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "token",
|
indexed: false,
|
||||||
|
internalType: "address",
|
||||||
|
name: "newAdmin",
|
||||||
type: "address"
|
type: "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
outputs: [
|
name: "AdminChanged",
|
||||||
{
|
type: "event"
|
||||||
name: "balance",
|
|
||||||
type: "uint256"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
stateMutability: "view"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "function",
|
anonymous: false,
|
||||||
name: "eraseMemberships",
|
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
name: "idCommitments",
|
indexed: true,
|
||||||
type: "uint256[]"
|
internalType: "address",
|
||||||
|
name: "beacon",
|
||||||
|
type: "address"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
outputs: [],
|
name: "BeaconUpgraded",
|
||||||
stateMutability: "nonpayable"
|
type: "event"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "function",
|
anonymous: false,
|
||||||
name: "eraseMemberships",
|
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{ indexed: false, internalType: "uint8", name: "version", type: "uint8" }
|
||||||
name: "idCommitments",
|
|
||||||
type: "uint256[]"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "eraseFromMembershipSet",
|
|
||||||
type: "bool"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
outputs: [],
|
name: "Initialized",
|
||||||
stateMutability: "nonpayable"
|
type: "event"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "function",
|
anonymous: false,
|
||||||
name: "extendMemberships",
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: "idCommitments",
|
|
||||||
type: "uint256[]"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
outputs: [],
|
|
||||||
stateMutability: "nonpayable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
name: "getMembershipInfo",
|
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
|
indexed: false,
|
||||||
|
internalType: "uint256",
|
||||||
name: "idCommitment",
|
name: "idCommitment",
|
||||||
type: "uint256"
|
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: "",
|
indexed: false,
|
||||||
type: "uint32"
|
internalType: "uint256",
|
||||||
|
name: "idCommitment",
|
||||||
|
type: "uint256"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "",
|
indexed: false,
|
||||||
|
internalType: "uint32",
|
||||||
|
name: "membershipRateLimit",
|
||||||
type: "uint32"
|
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"
|
type: "uint256"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
stateMutability: "view"
|
name: "MembershipExtended",
|
||||||
|
type: "event"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "function",
|
anonymous: false,
|
||||||
name: "getMerkleProof",
|
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
name: "index",
|
indexed: false,
|
||||||
type: "uint40"
|
internalType: "uint256",
|
||||||
}
|
name: "idCommitment",
|
||||||
],
|
type: "uint256"
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: "",
|
|
||||||
type: "uint256[20]"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
stateMutability: "view"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
name: "getRateCommitmentsInRangeBoundsInclusive",
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
name: "startIndex",
|
|
||||||
type: "uint32"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "endIndex",
|
indexed: false,
|
||||||
type: "uint32"
|
internalType: "uint256",
|
||||||
}
|
name: "membershipRateLimit",
|
||||||
|
type: "uint256"
|
||||||
|
},
|
||||||
|
{ indexed: false, internalType: "uint32", name: "index", type: "uint32" }
|
||||||
],
|
],
|
||||||
outputs: [
|
name: "MembershipRegistered",
|
||||||
{
|
type: "event"
|
||||||
name: "",
|
|
||||||
type: "uint256[]"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
stateMutability: "view"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "function",
|
anonymous: false,
|
||||||
name: "gracePeriodDurationForNewMemberships",
|
|
||||||
inputs: [],
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
name: "",
|
|
||||||
type: "uint32"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
stateMutability: "view"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
name: "initialize",
|
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
name: "_priceCalculator",
|
indexed: true,
|
||||||
|
internalType: "address",
|
||||||
|
name: "previousOwner",
|
||||||
type: "address"
|
type: "address"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "_maxTotalRateLimit",
|
indexed: true,
|
||||||
type: "uint32"
|
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",
|
name: "_minMembershipRateLimit",
|
||||||
type: "uint32"
|
type: "uint32"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
internalType: "uint32",
|
||||||
name: "_maxMembershipRateLimit",
|
name: "_maxMembershipRateLimit",
|
||||||
type: "uint32"
|
type: "uint32"
|
||||||
},
|
},
|
||||||
{
|
{ internalType: "uint32", name: "_activeDuration", type: "uint32" },
|
||||||
name: "_activeDuration",
|
{ internalType: "uint32", name: "_gracePeriod", type: "uint32" }
|
||||||
type: "uint32"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "_gracePeriod",
|
|
||||||
type: "uint32"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
|
name: "initialize",
|
||||||
outputs: [],
|
outputs: [],
|
||||||
stateMutability: "nonpayable"
|
stateMutability: "nonpayable",
|
||||||
|
type: "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "function",
|
inputs: [
|
||||||
|
{ internalType: "uint256", name: "_idCommitment", type: "uint256" }
|
||||||
|
],
|
||||||
name: "isExpired",
|
name: "isExpired",
|
||||||
|
outputs: [{ internalType: "bool", name: "", type: "bool" }],
|
||||||
|
stateMutability: "view",
|
||||||
|
type: "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{ internalType: "uint256", name: "_idCommitment", type: "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: [
|
outputs: [
|
||||||
|
{ internalType: "uint256", name: "depositAmount", type: "uint256" },
|
||||||
|
{ internalType: "uint32", name: "activeDuration", type: "uint32" },
|
||||||
{
|
{
|
||||||
name: "",
|
internalType: "uint256",
|
||||||
type: "bool"
|
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",
|
name: "register",
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
type: "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
inputs: [
|
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",
|
internalType: "uint256[]",
|
||||||
type: "uint256"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "rateLimit",
|
|
||||||
type: "uint32"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "idCommitmentsToErase",
|
name: "idCommitmentsToErase",
|
||||||
type: "uint256[]"
|
type: "uint256[]"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
outputs: [],
|
|
||||||
stateMutability: "nonpayable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
name: "registerWithPermit",
|
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: [],
|
outputs: [],
|
||||||
stateMutability: "nonpayable"
|
stateMutability: "nonpayable",
|
||||||
|
type: "function"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "event",
|
inputs: [],
|
||||||
name: "MembershipRegistered",
|
name: "renounceOwnership",
|
||||||
inputs: [
|
outputs: [],
|
||||||
{
|
stateMutability: "nonpayable",
|
||||||
name: "idCommitment",
|
type: "function"
|
||||||
type: "uint256",
|
},
|
||||||
indexed: false
|
{
|
||||||
},
|
inputs: [],
|
||||||
{
|
name: "root",
|
||||||
name: "rateLimit",
|
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
||||||
type: "uint32",
|
stateMutability: "view",
|
||||||
indexed: false
|
type: "function"
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "index",
|
|
||||||
type: "uint256",
|
|
||||||
indexed: false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
anonymous: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "event",
|
|
||||||
name: "MembershipRemoved",
|
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
name: "idCommitment",
|
internalType: "uint32",
|
||||||
type: "uint256",
|
name: "_activeDurationForNewMembership",
|
||||||
indexed: false
|
type: "uint32"
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "index",
|
|
||||||
type: "uint256",
|
|
||||||
indexed: false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
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";
|
import { RLN_ABI } from "./abi.js";
|
||||||
|
|
||||||
export const SEPOLIA_CONTRACT = {
|
export const LINEA_CONTRACT = {
|
||||||
chainId: 11155111,
|
chainId: 59141,
|
||||||
address: "0xCB33Aa5B38d79E3D9Fa8B10afF38AA201399a7e3",
|
address: "0xb9cd878c90e49f797b4431fbf4fb333108cb90e6",
|
||||||
abi: RLN_ABI
|
abi: RLN_ABI
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,244 +4,87 @@ import chaiAsPromised from "chai-as-promised";
|
|||||||
import * as ethers from "ethers";
|
import * as ethers from "ethers";
|
||||||
import sinon, { SinonSandbox } from "sinon";
|
import sinon, { SinonSandbox } from "sinon";
|
||||||
|
|
||||||
import { createRLN } from "../create.js";
|
import { createTestRLNInstance, initializeRLNContract } from "./test-setup.js";
|
||||||
import type { IdentityCredential } from "../identity.js";
|
import {
|
||||||
|
createMockRegistryContract,
|
||||||
import { DEFAULT_RATE_LIMIT, SEPOLIA_CONTRACT } from "./constants.js";
|
createRegisterStub,
|
||||||
import { RLNContract } from "./rln_contract.js";
|
mockRLNRegisteredEvent,
|
||||||
|
verifyRegistration
|
||||||
|
} from "./test-utils.js";
|
||||||
|
|
||||||
use(chaiAsPromised);
|
use(chaiAsPromised);
|
||||||
|
|
||||||
describe("RLN Contract abstraction - RLN", () => {
|
describe("RLN Contract abstraction - RLN", () => {
|
||||||
let sandbox: SinonSandbox;
|
let sandbox: SinonSandbox;
|
||||||
let rlnInstance: any;
|
|
||||||
let mockedRegistryContract: any;
|
|
||||||
|
|
||||||
const mockRateLimits = {
|
|
||||||
minRate: 20,
|
|
||||||
maxRate: 600,
|
|
||||||
maxTotalRate: 1000,
|
|
||||||
currentTotalRate: 500
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
sandbox = sinon.createSandbox();
|
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(() => {
|
afterEach(() => {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should fetch members from events and store them in the RLN instance", async () => {
|
describe("Member Registration", () => {
|
||||||
const rlnInstance = await createRLN();
|
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();
|
const mockedRegistryContract = createMockRegistryContract({
|
||||||
rlnInstance.zerokit.insertMember = insertMemberSpy;
|
queryFilter: queryFilterStub
|
||||||
|
});
|
||||||
|
|
||||||
const membershipRegisteredEvent = mockRLNRegisteredEvent();
|
const rlnContract = await initializeRLNContract(
|
||||||
|
rlnInstance,
|
||||||
|
mockedRegistryContract
|
||||||
|
);
|
||||||
|
|
||||||
const queryFilterStub = sinon.stub().returns([membershipRegisteredEvent]);
|
await rlnContract.fetchMembers(rlnInstance, {
|
||||||
const mockedRegistryContract = {
|
fromBlock: 0,
|
||||||
queryFilter: queryFilterStub,
|
fetchRange: 1000,
|
||||||
provider: {
|
fetchChunks: 2
|
||||||
getLogs: () => [],
|
});
|
||||||
getBlockNumber: () => Promise.resolve(1000)
|
|
||||||
},
|
|
||||||
interface: {
|
|
||||||
getEvent: (eventName: string) => ({
|
|
||||||
name: eventName,
|
|
||||||
format: () => {}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
filters: {
|
|
||||||
MembershipRegistered: () => ({}),
|
|
||||||
MembershipRemoved: () => ({})
|
|
||||||
},
|
|
||||||
on: () => ({}),
|
|
||||||
removeAllListeners: () => ({})
|
|
||||||
};
|
|
||||||
|
|
||||||
const provider = new ethers.providers.JsonRpcProvider();
|
expect(
|
||||||
const voidSigner = new ethers.VoidSigner(
|
insertMemberSpy.calledWith(
|
||||||
SEPOLIA_CONTRACT.address,
|
ethers.utils.zeroPad(
|
||||||
provider
|
hexToBytes(membershipRegisteredEvent.args!.idCommitment),
|
||||||
);
|
32
|
||||||
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
|
|
||||||
)
|
)
|
||||||
)
|
).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 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockedRegistryContract = {
|
it("should register a member", async () => {
|
||||||
register: registerStub,
|
const { rlnInstance, identity, insertMemberSpy } =
|
||||||
queryFilter: () => [membershipRegisteredEvent],
|
await createTestRLNInstance();
|
||||||
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: () => ({})
|
|
||||||
};
|
|
||||||
|
|
||||||
const provider = new ethers.providers.JsonRpcProvider();
|
const registerStub = createRegisterStub(identity);
|
||||||
const voidSigner = new ethers.VoidSigner(
|
const mockedRegistryContract = createMockRegistryContract({
|
||||||
SEPOLIA_CONTRACT.address,
|
register: registerStub,
|
||||||
provider
|
queryFilter: () => []
|
||||||
);
|
});
|
||||||
const rlnContract = await RLNContract.init(rlnInstance, {
|
|
||||||
signer: voidSigner,
|
const rlnContract = await initializeRLNContract(
|
||||||
address: SEPOLIA_CONTRACT.address,
|
rlnInstance,
|
||||||
rateLimit: DEFAULT_RATE_LIMIT,
|
mockedRegistryContract
|
||||||
contract: mockedRegistryContract as unknown as ethers.Contract
|
);
|
||||||
|
|
||||||
|
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 {
|
export interface MembershipRegisteredEvent {
|
||||||
idCommitment: string;
|
idCommitment: string;
|
||||||
rateLimit: number;
|
membershipRateLimit: ethers.BigNumber;
|
||||||
index: ethers.BigNumber;
|
index: ethers.BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,8 @@ export class RLNContract {
|
|||||||
|
|
||||||
private _members: Map<number, Member> = new Map();
|
private _members: Map<number, Member> = new Map();
|
||||||
private _membersFilter: ethers.EventFilter;
|
private _membersFilter: ethers.EventFilter;
|
||||||
private _membersRemovedFilter: ethers.EventFilter;
|
private _membershipErasedFilter: ethers.EventFilter;
|
||||||
|
private _membersExpiredFilter: ethers.EventFilter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronous initializer for RLNContract.
|
* Asynchronous initializer for RLNContract.
|
||||||
@ -94,15 +95,7 @@ export class RLNContract {
|
|||||||
contract
|
contract
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
if (
|
this.validateRateLimit(rateLimit);
|
||||||
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.rateLimit = rateLimit;
|
this.rateLimit = rateLimit;
|
||||||
|
|
||||||
const initialRoot = rlnInstance.zerokit.getMerkleRoot();
|
const initialRoot = rlnInstance.zerokit.getMerkleRoot();
|
||||||
@ -111,9 +104,25 @@ export class RLNContract {
|
|||||||
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
|
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
|
||||||
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
|
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
|
||||||
|
|
||||||
// Initialize event filters for MembershipRegistered and MembershipRemoved
|
// Initialize event filters
|
||||||
this._membersFilter = this.contract.filters.MembershipRegistered();
|
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.maxTotalRateLimit(),
|
||||||
this.contract.currentTotalRateLimit()
|
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
|
* @param newRateLimit The new rate limit to use
|
||||||
*/
|
*/
|
||||||
public async setRateLimit(newRateLimit: number): Promise<void> {
|
public async setRateLimit(newRateLimit: number): Promise<void> {
|
||||||
|
this.validateRateLimit(newRateLimit);
|
||||||
this.rateLimit = newRateLimit;
|
this.rateLimit = newRateLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,11 +217,18 @@ export class RLNContract {
|
|||||||
return this._membersFilter;
|
return this._membersFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get membersRemovedFilter(): ethers.EventFilter {
|
private get membershipErasedFilter(): ethers.EventFilter {
|
||||||
if (!this._membersRemovedFilter) {
|
if (!this._membershipErasedFilter) {
|
||||||
throw Error("MembersRemoved filter was not initialized.");
|
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(
|
public async fetchMembers(
|
||||||
@ -226,10 +243,19 @@ export class RLNContract {
|
|||||||
const removedMemberEvents = await queryFilter(this.contract, {
|
const removedMemberEvents = await queryFilter(this.contract, {
|
||||||
fromBlock: this.deployBlock,
|
fromBlock: this.deployBlock,
|
||||||
...options,
|
...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);
|
this.processEvents(rlnInstance, events);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,8 +268,26 @@ export class RLNContract {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt.event === "MembershipRemoved") {
|
if (
|
||||||
const index = evt.args.index as ethers.BigNumber;
|
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);
|
const toRemoveVal = toRemoveTable.get(evt.blockNumber);
|
||||||
if (toRemoveVal != undefined) {
|
if (toRemoveVal != undefined) {
|
||||||
toRemoveVal.push(index.toNumber());
|
toRemoveVal.push(index.toNumber());
|
||||||
@ -275,15 +319,21 @@ export class RLNContract {
|
|||||||
if (!evt.args) return;
|
if (!evt.args) return;
|
||||||
|
|
||||||
const _idCommitment = evt.args.idCommitment as string;
|
const _idCommitment = evt.args.idCommitment as string;
|
||||||
const index = evt.args.index as ethers.BigNumber;
|
let index = evt.args.index;
|
||||||
|
|
||||||
if (!_idCommitment || !index) {
|
if (!_idCommitment || !index) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof index === "number" || typeof index === "string") {
|
||||||
|
index = ethers.BigNumber.from(index);
|
||||||
|
}
|
||||||
|
|
||||||
const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
|
const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
|
||||||
rlnInstance.zerokit.insertMember(idCommitment);
|
rlnInstance.zerokit.insertMember(idCommitment);
|
||||||
this._members.set(index.toNumber(), {
|
|
||||||
|
const numericIndex = index.toNumber();
|
||||||
|
this._members.set(numericIndex, {
|
||||||
index,
|
index,
|
||||||
idCommitment: _idCommitment
|
idCommitment: _idCommitment
|
||||||
});
|
});
|
||||||
@ -316,7 +366,7 @@ export class RLNContract {
|
|||||||
this.membersFilter,
|
this.membersFilter,
|
||||||
(
|
(
|
||||||
_idCommitment: string,
|
_idCommitment: string,
|
||||||
_rateLimit: number,
|
_membershipRateLimit: ethers.BigNumber,
|
||||||
_index: ethers.BigNumber,
|
_index: ethers.BigNumber,
|
||||||
event: ethers.Event
|
event: ethers.Event
|
||||||
) => {
|
) => {
|
||||||
@ -325,9 +375,22 @@ export class RLNContract {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.contract.on(
|
this.contract.on(
|
||||||
this.membersRemovedFilter,
|
this.membershipErasedFilter,
|
||||||
(
|
(
|
||||||
_idCommitment: string,
|
_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,
|
_index: ethers.BigNumber,
|
||||||
event: ethers.Event
|
event: ethers.Event
|
||||||
) => {
|
) => {
|
||||||
@ -344,15 +407,45 @@ export class RLNContract {
|
|||||||
`Registering identity with rate limit: ${this.rateLimit} messages/epoch`
|
`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 =
|
const txRegisterResponse: ethers.ContractTransaction =
|
||||||
await this.contract.register(
|
await this.contract.register(
|
||||||
identity.IDCommitmentBigInt,
|
identity.IDCommitmentBigInt,
|
||||||
this.rateLimit,
|
this.rateLimit,
|
||||||
[],
|
[],
|
||||||
{ gasLimit: 300000 }
|
{ gasLimit }
|
||||||
);
|
);
|
||||||
|
|
||||||
const txRegisterReceipt = await txRegisterResponse.wait();
|
const txRegisterReceipt = await txRegisterResponse.wait();
|
||||||
|
|
||||||
|
if (txRegisterReceipt.status === 0) {
|
||||||
|
throw new Error("Transaction failed on-chain");
|
||||||
|
}
|
||||||
|
|
||||||
const memberRegistered = txRegisterReceipt.events?.find(
|
const memberRegistered = txRegisterReceipt.events?.find(
|
||||||
(event) => event.event === "MembershipRegistered"
|
(event) => event.event === "MembershipRegistered"
|
||||||
);
|
);
|
||||||
@ -366,30 +459,55 @@ export class RLNContract {
|
|||||||
|
|
||||||
const decodedData: MembershipRegisteredEvent = {
|
const decodedData: MembershipRegisteredEvent = {
|
||||||
idCommitment: memberRegistered.args.idCommitment,
|
idCommitment: memberRegistered.args.idCommitment,
|
||||||
rateLimit: memberRegistered.args.rateLimit,
|
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
||||||
index: memberRegistered.args.index
|
index: memberRegistered.args.index
|
||||||
};
|
};
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
`Successfully registered membership with index ${decodedData.index} ` +
|
`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 network = await this.contract.provider.getNetwork();
|
||||||
const address = this.contract.address;
|
const address = this.contract.address;
|
||||||
const membershipId = decodedData.index.toNumber();
|
const membershipId = Number(decodedData.index);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
identity,
|
identity,
|
||||||
membership: {
|
membership: {
|
||||||
address,
|
address,
|
||||||
treeIndex: membershipId,
|
treeIndex: membershipId,
|
||||||
chainId: network.chainId
|
chainId: network.chainId,
|
||||||
|
rateLimit: decodedData.membershipRateLimit.toNumber()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(`Error in registerWithIdentity: ${(error as Error).message}`);
|
if (error instanceof Error) {
|
||||||
return undefined;
|
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 = {
|
const decodedData: MembershipRegisteredEvent = {
|
||||||
idCommitment: memberRegistered.args.idCommitment,
|
idCommitment: memberRegistered.args.idCommitment,
|
||||||
rateLimit: memberRegistered.args.rateLimit,
|
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
||||||
index: memberRegistered.args.index
|
index: memberRegistered.args.index
|
||||||
};
|
};
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
`Successfully registered membership with permit. Index: ${decodedData.index}, ` +
|
`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 network = await this.contract.provider.getNetwork();
|
||||||
const address = this.contract.address;
|
const address = this.contract.address;
|
||||||
const membershipId = decodedData.index.toNumber();
|
const membershipId = ethers.BigNumber.from(decodedData.index).toNumber();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
identity,
|
identity,
|
||||||
membership: {
|
membership: {
|
||||||
address,
|
address,
|
||||||
treeIndex: membershipId,
|
treeIndex: membershipId,
|
||||||
chainId: network.chainId
|
chainId: network.chainId,
|
||||||
|
rateLimit: decodedData.membershipRateLimit.toNumber()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -560,16 +679,9 @@ export class RLNContract {
|
|||||||
|
|
||||||
public async registerMembership(
|
public async registerMembership(
|
||||||
idCommitment: string,
|
idCommitment: string,
|
||||||
rateLimit: number = DEFAULT_RATE_LIMIT
|
rateLimit: number = this.rateLimit
|
||||||
): Promise<ethers.ContractTransaction> {
|
): Promise<ethers.ContractTransaction> {
|
||||||
if (
|
this.validateRateLimit(rateLimit);
|
||||||
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}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return this.contract.register(idCommitment, 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 { RLNDecoder, RLNEncoder } from "./codec.js";
|
||||||
import { RLN_ABI } from "./contract/abi.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 { createRLN } from "./create.js";
|
||||||
import { IdentityCredential } from "./identity.js";
|
import { IdentityCredential } from "./identity.js";
|
||||||
import { Keystore } from "./keystore/index.js";
|
import { Keystore } from "./keystore/index.js";
|
||||||
@ -19,7 +19,18 @@ export {
|
|||||||
RLNDecoder,
|
RLNDecoder,
|
||||||
MerkleRootTracker,
|
MerkleRootTracker,
|
||||||
RLNContract,
|
RLNContract,
|
||||||
SEPOLIA_CONTRACT,
|
LINEA_CONTRACT,
|
||||||
extractMetaMaskSigner,
|
extractMetaMaskSigner,
|
||||||
RLN_ABI
|
RLN_ABI
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type {
|
||||||
|
DecryptedCredentials,
|
||||||
|
EncryptedCredentials,
|
||||||
|
Keccak256Hash,
|
||||||
|
KeystoreEntity,
|
||||||
|
MembershipHash,
|
||||||
|
MembershipInfo,
|
||||||
|
Password,
|
||||||
|
Sha256Hash
|
||||||
|
} from "./keystore/types.js";
|
||||||
|
|||||||
@ -231,7 +231,8 @@ describe("Keystore", () => {
|
|||||||
const membership = {
|
const membership = {
|
||||||
chainId: "0xAA36A7",
|
chainId: "0xAA36A7",
|
||||||
treeIndex: 8,
|
treeIndex: 8,
|
||||||
address: "0x8e1F3742B987d8BA376c0CBbD7357fE1F003ED71"
|
address: "0x8e1F3742B987d8BA376c0CBbD7357fE1F003ED71",
|
||||||
|
rateLimit: undefined
|
||||||
} as unknown as MembershipInfo;
|
} as unknown as MembershipInfo;
|
||||||
|
|
||||||
const store = Keystore.create();
|
const store = Keystore.create();
|
||||||
@ -246,6 +247,11 @@ describe("Keystore", () => {
|
|||||||
expectedHash,
|
expectedHash,
|
||||||
DEFAULT_PASSWORD
|
DEFAULT_PASSWORD
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!actualCredentials) {
|
||||||
|
throw new Error("Failed to retrieve credentials");
|
||||||
|
}
|
||||||
|
|
||||||
expect(actualCredentials).to.deep.equalInAnyOrder({
|
expect(actualCredentials).to.deep.equalInAnyOrder({
|
||||||
identity,
|
identity,
|
||||||
membership
|
membership
|
||||||
@ -276,7 +282,8 @@ describe("Keystore", () => {
|
|||||||
const membership = {
|
const membership = {
|
||||||
chainId: "0xAA36A7",
|
chainId: "0xAA36A7",
|
||||||
treeIndex: 8,
|
treeIndex: 8,
|
||||||
address: "0x8e1F3742B987d8BA376c0CBbD7357fE1F003ED71"
|
address: "0x8e1F3742B987d8BA376c0CBbD7357fE1F003ED71",
|
||||||
|
rateLimit: undefined
|
||||||
} as unknown as MembershipInfo;
|
} as unknown as MembershipInfo;
|
||||||
|
|
||||||
const store = Keystore.fromObject(NWAKU_KEYSTORE as any);
|
const store = Keystore.fromObject(NWAKU_KEYSTORE as any);
|
||||||
|
|||||||
@ -274,7 +274,8 @@ export class Keystore {
|
|||||||
membership: {
|
membership: {
|
||||||
treeIndex: _.get(obj, "treeIndex"),
|
treeIndex: _.get(obj, "treeIndex"),
|
||||||
chainId: _.get(obj, "membershipContract.chainId"),
|
chainId: _.get(obj, "membershipContract.chainId"),
|
||||||
address: _.get(obj, "membershipContract.address")
|
address: _.get(obj, "membershipContract.address"),
|
||||||
|
rateLimit: _.get(obj, "membershipContract.rateLimit")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export type MembershipInfo = {
|
|||||||
chainId: number;
|
chainId: number;
|
||||||
address: string;
|
address: string;
|
||||||
treeIndex: number;
|
treeIndex: number;
|
||||||
|
rateLimit: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type KeystoreEntity = {
|
export type KeystoreEntity = {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import {
|
|||||||
type RLNEncoder
|
type RLNEncoder
|
||||||
} from "./codec.js";
|
} from "./codec.js";
|
||||||
import { DEFAULT_RATE_LIMIT } from "./contract/constants.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 { IdentityCredential } from "./identity.js";
|
||||||
import { Keystore } from "./keystore/index.js";
|
import { Keystore } from "./keystore/index.js";
|
||||||
import type {
|
import type {
|
||||||
@ -108,7 +108,7 @@ type StartRLNOptions = {
|
|||||||
*/
|
*/
|
||||||
signer?: ethers.Signer;
|
signer?: ethers.Signer;
|
||||||
/**
|
/**
|
||||||
* If not set - will use default SEPOLIA_CONTRACT address.
|
* If not set - will use default LINEA_CONTRACT address.
|
||||||
*/
|
*/
|
||||||
address?: string;
|
address?: string;
|
||||||
/**
|
/**
|
||||||
@ -190,10 +190,10 @@ export class RLNInstance {
|
|||||||
const address =
|
const address =
|
||||||
credentials?.membership.address ||
|
credentials?.membership.address ||
|
||||||
options.address ||
|
options.address ||
|
||||||
SEPOLIA_CONTRACT.address;
|
LINEA_CONTRACT.address;
|
||||||
|
|
||||||
if (address === SEPOLIA_CONTRACT.address) {
|
if (address === LINEA_CONTRACT.address) {
|
||||||
chainId = SEPOLIA_CONTRACT.chainId;
|
chainId = LINEA_CONTRACT.chainId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const signer = options.signer || (await extractMetaMaskSigner());
|
const signer = options.signer || (await extractMetaMaskSigner());
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user