From b9cde875f16f8e47866bcf1b9f4ba259acb25439 Mon Sep 17 00:00:00 2001 From: Sasha Date: Sat, 14 Oct 2023 22:50:46 +0200 Subject: [PATCH 01/12] feat: update contract --- src/constants.ts | 11 ++++------- src/rln_contract.ts | 6 ++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 49407de..37f4b7c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,14 +1,11 @@ export const RLN_ABI = [ - "function MEMBERSHIP_DEPOSIT() public view returns(uint256)", - "function register(uint256 pubkey) external payable", - "function withdraw(uint256 secret, uint256 _pubkeyIndex, address payable receiver) external", - "event MemberRegistered(uint256 pubkey, uint256 index)", - "event MemberWithdrawn(uint256 pubkey, uint256 index)", + "function register(uint256[] calldata commitments) external payable", + "event MemberRegistered(uint256 idCommitment, uint256 idCommitmentIndex)", ]; export const SEPOLIA_CONTRACT = { chainId: 11155111, - startBlock: 3193048, - address: "0x9C09146844C1326c2dBC41c451766C7138F88155", + startBlock: 4230713, + address: "0xF1935b338321013f11068abCafC548A7B0db732C", abi: RLN_ABI, }; diff --git a/src/rln_contract.ts b/src/rln_contract.ts index 8adb6b0..7913332 100644 --- a/src/rln_contract.ts +++ b/src/rln_contract.ts @@ -164,11 +164,9 @@ export class RLNContract { public async registerWithKey( credential: IdentityCredential ): Promise { - const depositValue = await this.contract.MEMBERSHIP_DEPOSIT(); - const txRegisterResponse: ethers.ContractTransaction = - await this.contract.register(credential.IDCommitmentBigInt, { - value: depositValue, + await this.contract.register([credential.IDCommitmentBigInt], { + gasLimit: 100000, }); const txRegisterReceipt = await txRegisterResponse.wait(); From 8e37010af48bcd43e593c8ef951646a4a08edd2e Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 02:16:09 +0200 Subject: [PATCH 02/12] update ABI --- src/constants.ts | 65 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 37f4b7c..50062f5 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,11 +1,68 @@ +// ref https://github.com/waku-org/waku-rln-contract/blob/19fded82bca07e7b535b429dc507cfb83f10dfcf/deployments/sepolia/WakuRlnRegistry_Implementation.json#L3 +export const RLN_REGISTRY_ABI = [ + "error IncompatibleStorage()", + "error IncompatibleStorageIndex()", + "error NoStorageContractAvailable()", + "error StorageAlreadyExists(address storageAddress)", + "event AdminChanged(address previousAdmin, address newAdmin)", + "event BeaconUpgraded(address indexed beacon)", + "event Initialized(uint8 version)", + "event NewStorageContract(uint16 index, address storageAddress)", + "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", + "event Upgraded(address indexed implementation)", + "function forceProgress()", + "function initialize(address _poseidonHasher)", + "function newStorage()", + "function nextStorageIndex() view returns (uint16)", + "function owner() view returns (address)", + "function poseidonHasher() view returns (address)", + "function proxiableUUID() view returns (bytes32)", + "function register(uint16 storageIndex, uint256 commitment)", + "function register(uint256[] commitments)", + "function register(uint16 storageIndex, uint256[] commitments)", + "function registerStorage(address storageAddress)", + "function renounceOwnership()", + "function storages(uint16) view returns (address)", + "function transferOwnership(address newOwner)", + "function upgradeTo(address newImplementation)", + "function upgradeToAndCall(address newImplementation, bytes data) payable", + "function usingStorageIndex() view returns (uint16)", +]; + +// ref https://github.com/waku-org/waku-rln-contract/blob/19fded82bca07e7b535b429dc507cfb83f10dfcf/deployments/sepolia/WakuRlnStorage_0.json#L3 export const RLN_ABI = [ - "function register(uint256[] calldata commitments) external payable", - "event MemberRegistered(uint256 idCommitment, uint256 idCommitmentIndex)", + "constructor(address _poseidonHasher, uint16 _contractIndex)", + "error DuplicateIdCommitment()", + "error FullTree()", + "error InvalidIdCommitment(uint256 idCommitment)", + "error NotImplemented()", + "event MemberRegistered(uint256 idCommitment, uint256 index)", + "event MemberWithdrawn(uint256 idCommitment, uint256 index)", + "event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)", + "function DEPTH() view returns (uint256)", + "function MEMBERSHIP_DEPOSIT() view returns (uint256)", + "function SET_SIZE() view returns (uint256)", + "function contractIndex() view returns (uint16)", + "function deployedBlockNumber() view returns (uint32)", + "function idCommitmentIndex() view returns (uint256)", + "function isValidCommitment(uint256 idCommitment) view returns (bool)", + "function memberExists(uint256) view returns (bool)", + "function members(uint256) view returns (uint256)", + "function owner() view returns (address)", + "function poseidonHasher() view returns (address)", + "function register(uint256[] idCommitments)", + "function register(uint256 idCommitment) payable", + "function renounceOwnership()", + "function slash(uint256 idCommitment, address receiver, uint256[8] proof) pure", + "function stakedAmounts(uint256) view returns (uint256)", + "function transferOwnership(address newOwner)", + "function verifier() view returns (address)", + "function withdraw() pure", + "function withdrawalBalance(address) view returns (uint256)", ]; export const SEPOLIA_CONTRACT = { chainId: 11155111, - startBlock: 4230713, address: "0xF1935b338321013f11068abCafC548A7B0db732C", - abi: RLN_ABI, + abi: RLN_REGISTRY_ABI, }; From bfb016cfc2e4ed41baf22aea5a2823489c2215ca Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 02:48:46 +0200 Subject: [PATCH 03/12] update contract and fix name of ABI --- src/constants.ts | 2 +- src/rln_contract.ts | 65 +++++++++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 50062f5..bcac425 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -30,7 +30,7 @@ export const RLN_REGISTRY_ABI = [ ]; // ref https://github.com/waku-org/waku-rln-contract/blob/19fded82bca07e7b535b429dc507cfb83f10dfcf/deployments/sepolia/WakuRlnStorage_0.json#L3 -export const RLN_ABI = [ +export const RLN_STORAGE_ABI = [ "constructor(address _poseidonHasher, uint16 _contractIndex)", "error DuplicateIdCommitment()", "error FullTree()", diff --git a/src/rln_contract.ts b/src/rln_contract.ts index 7913332..bc46e69 100644 --- a/src/rln_contract.ts +++ b/src/rln_contract.ts @@ -1,6 +1,6 @@ import { ethers } from "ethers"; -import { RLN_ABI } from "./constants.js"; +import { RLN_REGISTRY_ABI, RLN_STORAGE_ABI } from "./constants.js"; import { IdentityCredential, RLNInstance } from "./rln.js"; import { MerkleRootTracker } from "./root_tracker.js"; @@ -9,9 +9,11 @@ type Member = { index: number; }; +type Provider = ethers.Signer | ethers.providers.Provider; + type ContractOptions = { address: string; - provider: ethers.Signer | ethers.providers.Provider; + provider: Provider; }; type FetchMembersOptions = { @@ -21,10 +23,14 @@ type FetchMembersOptions = { }; export class RLNContract { - private _contract: ethers.Contract; - private membersFilter: ethers.EventFilter; + private registryContract: ethers.Contract; private merkleRootTracker: MerkleRootTracker; + private deployBlock: undefined | number; + private storageIndex: undefined | number; + private storageContract: undefined | ethers.Contract; + private _membersFilter: undefined | ethers.EventFilter; + private _members: Member[] = []; public static async init( @@ -33,36 +39,64 @@ export class RLNContract { ): Promise { const rlnContract = new RLNContract(rlnInstance, options); + await rlnContract.initStorageContract(options.provider); await rlnContract.fetchMembers(rlnInstance); rlnContract.subscribeToMembers(rlnInstance); return rlnContract; } - constructor( + private constructor( rlnInstance: RLNInstance, { address, provider }: ContractOptions ) { const initialRoot = rlnInstance.getMerkleRoot(); - this._contract = new ethers.Contract(address, RLN_ABI, provider); + this.registryContract = new ethers.Contract( + address, + RLN_REGISTRY_ABI, + provider + ); this.merkleRootTracker = new MerkleRootTracker(5, initialRoot); - this.membersFilter = this.contract.filters.MemberRegistered(); + } + + private async initStorageContract(provider: Provider): Promise { + const index = await this.registryContract.usingStorageIndex(); + const address = await this.registryContract.storages(index); + + this.storageIndex = index; + this.storageContract = new ethers.Contract( + address, + RLN_STORAGE_ABI, + provider + ); + this._membersFilter = this.storageContract.filters.MemberRegistered(); + + this.deployBlock = await this.storageContract.deployedBlockNumber(); } public get contract(): ethers.Contract { - return this._contract; + // since init is only way to create RLNContract class + // it is guaranteed that this.storageContract is populated + return this.storageContract as ethers.Contract; } public get members(): Member[] { return this._members; } + private get membersFilter(): ethers.EventFilter { + // since init is only way to create RLNContract class + // it is guaranteed that this.storageContract is populated + return this._membersFilter as ethers.EventFilter; + } + public async fetchMembers( rlnInstance: RLNInstance, options: FetchMembersOptions = {} ): Promise { const registeredMemberEvents = await queryFilter(this.contract, { + fromBlock: this.deployBlock, ...options, membersFilter: this.membersFilter, }); @@ -164,10 +198,19 @@ export class RLNContract { public async registerWithKey( credential: IdentityCredential ): Promise { + if (!this.storageIndex) { + throw Error( + "Cannot register credential, no storage contract index found." + ); + } const txRegisterResponse: ethers.ContractTransaction = - await this.contract.register([credential.IDCommitmentBigInt], { - gasLimit: 100000, - }); + await this.registryContract.register( + this.storageIndex, + credential.IDCommitmentBigInt, + { + gasLimit: 100000, + } + ); const txRegisterReceipt = await txRegisterResponse.wait(); return txRegisterReceipt?.events?.[0]; From 8347276861f7c193f4ae6f53a7801932b7f2094d Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 02:51:57 +0200 Subject: [PATCH 04/12] update exports --- src/index.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index eef2b81..0d27230 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,9 @@ import { RLNDecoder, RLNEncoder } from "./codec.js"; -import { RLN_ABI, SEPOLIA_CONTRACT } from "./constants.js"; +import { + RLN_REGISTRY_ABI, + RLN_STORAGE_ABI, + SEPOLIA_CONTRACT, +} from "./constants.js"; import { Keystore } from "./keystore/index.js"; import { IdentityCredential, @@ -29,6 +33,7 @@ export { RLNDecoder, MerkleRootTracker, RLNContract, - RLN_ABI, + RLN_STORAGE_ABI, + RLN_REGISTRY_ABI, SEPOLIA_CONTRACT, }; From b1b3d0d19fb38eabe3036e8f054cd4027e34fe5a Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 02:54:52 +0200 Subject: [PATCH 05/12] ignore constants --- .cspell.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.cspell.json b/.cspell.json index 0073fbe..0033611 100644 --- a/.cspell.json +++ b/.cspell.json @@ -33,7 +33,8 @@ "gen", "proto", "*.spec.ts", - "src/resources.ts" + "src/resources.ts", + "src/constants.ts" ], "patterns": [ { From 3e777b18770bc1d73a27e37925c0cf2ce17e2f62 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 10:49:08 +0200 Subject: [PATCH 06/12] fix tests --- src/rln_contract.spec.ts | 13 ++++++++----- src/rln_contract.ts | 12 +++++++----- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/rln_contract.spec.ts b/src/rln_contract.spec.ts index e9fd1d6..5a50220 100644 --- a/src/rln_contract.spec.ts +++ b/src/rln_contract.spec.ts @@ -7,7 +7,7 @@ import * as rln from "./index.js"; chai.use(spies); describe("RLN Contract abstraction", () => { - it("should be able to fetch members from events and store to rln instance", async () => { + it.only("should be able to fetch members from events and store to rln instance", async () => { const rlnInstance = await rln.create(); rlnInstance.insertMember = () => undefined; @@ -19,7 +19,7 @@ describe("RLN Contract abstraction", () => { provider: voidSigner, }); - rlnContract["_contract"] = { + rlnContract["storageContract"] = { queryFilter: () => Promise.resolve([mockEvent()]), } as unknown as ethers.Contract; @@ -39,12 +39,15 @@ describe("RLN Contract abstraction", () => { provider: voidSigner, }); - rlnContract["_contract"] = { + rlnContract["storageIndex"] = 1; + rlnContract["registryContract"] = { register: () => Promise.resolve({ wait: () => Promise.resolve(undefined) }), - MEMBERSHIP_DEPOSIT: () => Promise.resolve(1), } as unknown as ethers.Contract; - const contractSpy = chai.spy.on(rlnContract["_contract"], "register"); + const contractSpy = chai.spy.on( + rlnContract["registryContract"], + "register" + ); await rlnContract.registerWithSignature(rlnInstance, mockSignature); diff --git a/src/rln_contract.ts b/src/rln_contract.ts index bc46e69..0f41ecc 100644 --- a/src/rln_contract.ts +++ b/src/rln_contract.ts @@ -46,7 +46,7 @@ export class RLNContract { return rlnContract; } - private constructor( + constructor( rlnInstance: RLNInstance, { address, provider }: ContractOptions ) { @@ -76,8 +76,9 @@ export class RLNContract { } public get contract(): ethers.Contract { - // since init is only way to create RLNContract class - // it is guaranteed that this.storageContract is populated + if (!this.storageContract) { + throw Error("Storage contract was not initialized."); + } return this.storageContract as ethers.Contract; } @@ -86,8 +87,9 @@ export class RLNContract { } private get membersFilter(): ethers.EventFilter { - // since init is only way to create RLNContract class - // it is guaranteed that this.storageContract is populated + if (!this._membersFilter) { + throw Error("Members filter was not initialized."); + } return this._membersFilter as ethers.EventFilter; } From ce4581f6e4c9ff755b6a35efb580c88591936804 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 10:53:28 +0200 Subject: [PATCH 07/12] update mock --- src/rln_contract.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rln_contract.spec.ts b/src/rln_contract.spec.ts index 5a50220..073f920 100644 --- a/src/rln_contract.spec.ts +++ b/src/rln_contract.spec.ts @@ -40,6 +40,7 @@ describe("RLN Contract abstraction", () => { }); rlnContract["storageIndex"] = 1; + rlnContract["_membersFilter"] = {} as unknown as ethers.EventFilter; rlnContract["registryContract"] = { register: () => Promise.resolve({ wait: () => Promise.resolve(undefined) }), From 4dc30c4630641460867b44053b3518abba435213 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 11:14:31 +0200 Subject: [PATCH 08/12] up mock --- src/rln_contract.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rln_contract.spec.ts b/src/rln_contract.spec.ts index 073f920..32780a2 100644 --- a/src/rln_contract.spec.ts +++ b/src/rln_contract.spec.ts @@ -40,7 +40,10 @@ describe("RLN Contract abstraction", () => { }); rlnContract["storageIndex"] = 1; - rlnContract["_membersFilter"] = {} as unknown as ethers.EventFilter; + rlnContract["_membersFilter"] = { + address: "", + topics: [], + } as unknown as ethers.EventFilter; rlnContract["registryContract"] = { register: () => Promise.resolve({ wait: () => Promise.resolve(undefined) }), From 0d2fbb62f6f193543b9ea255f483af746b636943 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 11:18:08 +0200 Subject: [PATCH 09/12] add logs --- src/rln_contract.spec.ts | 1 + src/rln_contract.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/rln_contract.spec.ts b/src/rln_contract.spec.ts index 32780a2..80c5076 100644 --- a/src/rln_contract.spec.ts +++ b/src/rln_contract.spec.ts @@ -44,6 +44,7 @@ describe("RLN Contract abstraction", () => { address: "", topics: [], } as unknown as ethers.EventFilter; + console.log("test", rlnContract["_membersFilter"]); rlnContract["registryContract"] = { register: () => Promise.resolve({ wait: () => Promise.resolve(undefined) }), diff --git a/src/rln_contract.ts b/src/rln_contract.ts index 0f41ecc..3ee0aa3 100644 --- a/src/rln_contract.ts +++ b/src/rln_contract.ts @@ -87,6 +87,7 @@ export class RLNContract { } private get membersFilter(): ethers.EventFilter { + console.log("get", this._membersFilter); if (!this._membersFilter) { throw Error("Members filter was not initialized."); } From a1e435f259461020e3809fadc2da74805ffbb0a9 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 11:23:35 +0200 Subject: [PATCH 10/12] add mock --- src/rln_contract.spec.ts | 5 ++++- src/rln_contract.ts | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/rln_contract.spec.ts b/src/rln_contract.spec.ts index 80c5076..fe650ca 100644 --- a/src/rln_contract.spec.ts +++ b/src/rln_contract.spec.ts @@ -22,6 +22,10 @@ describe("RLN Contract abstraction", () => { rlnContract["storageContract"] = { queryFilter: () => Promise.resolve([mockEvent()]), } as unknown as ethers.Contract; + rlnContract["_membersFilter"] = { + address: "", + topics: [], + } as unknown as ethers.EventFilter; await rlnContract.fetchMembers(rlnInstance); @@ -44,7 +48,6 @@ describe("RLN Contract abstraction", () => { address: "", topics: [], } as unknown as ethers.EventFilter; - console.log("test", rlnContract["_membersFilter"]); rlnContract["registryContract"] = { register: () => Promise.resolve({ wait: () => Promise.resolve(undefined) }), diff --git a/src/rln_contract.ts b/src/rln_contract.ts index 3ee0aa3..0f41ecc 100644 --- a/src/rln_contract.ts +++ b/src/rln_contract.ts @@ -87,7 +87,6 @@ export class RLNContract { } private get membersFilter(): ethers.EventFilter { - console.log("get", this._membersFilter); if (!this._membersFilter) { throw Error("Members filter was not initialized."); } From 540f177d882b06d73424301d7e61991c6b716f41 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 11:27:00 +0200 Subject: [PATCH 11/12] remove only --- src/rln_contract.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rln_contract.spec.ts b/src/rln_contract.spec.ts index fe650ca..8f47652 100644 --- a/src/rln_contract.spec.ts +++ b/src/rln_contract.spec.ts @@ -7,7 +7,7 @@ import * as rln from "./index.js"; chai.use(spies); describe("RLN Contract abstraction", () => { - it.only("should be able to fetch members from events and store to rln instance", async () => { + it("should be able to fetch members from events and store to rln instance", async () => { const rlnInstance = await rln.create(); rlnInstance.insertMember = () => undefined; From 1a4f7850e9a6e5a8d026d709bae8a8ce89321e66 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 17 Oct 2023 11:28:42 +0200 Subject: [PATCH 12/12] up test --- src/rln_contract.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rln_contract.spec.ts b/src/rln_contract.spec.ts index fe650ca..8f47652 100644 --- a/src/rln_contract.spec.ts +++ b/src/rln_contract.spec.ts @@ -7,7 +7,7 @@ import * as rln from "./index.js"; chai.use(spies); describe("RLN Contract abstraction", () => { - it.only("should be able to fetch members from events and store to rln instance", async () => { + it("should be able to fetch members from events and store to rln instance", async () => { const rlnInstance = await rln.create(); rlnInstance.insertMember = () => undefined;