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": [ { diff --git a/src/constants.ts b/src/constants.ts index 49407de..bcac425 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,14 +1,68 @@ -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)", +// 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_STORAGE_ABI = [ + "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: 3193048, - address: "0x9C09146844C1326c2dBC41c451766C7138F88155", - abi: RLN_ABI, + address: "0xF1935b338321013f11068abCafC548A7B0db732C", + abi: RLN_REGISTRY_ABI, }; 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, }; diff --git a/src/rln_contract.spec.ts b/src/rln_contract.spec.ts index e9fd1d6..fe650ca 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,9 +19,13 @@ describe("RLN Contract abstraction", () => { provider: voidSigner, }); - rlnContract["_contract"] = { + rlnContract["storageContract"] = { queryFilter: () => Promise.resolve([mockEvent()]), } as unknown as ethers.Contract; + rlnContract["_membersFilter"] = { + address: "", + topics: [], + } as unknown as ethers.EventFilter; await rlnContract.fetchMembers(rlnInstance); @@ -39,12 +43,19 @@ describe("RLN Contract abstraction", () => { provider: voidSigner, }); - rlnContract["_contract"] = { + rlnContract["storageIndex"] = 1; + rlnContract["_membersFilter"] = { + address: "", + topics: [], + } as unknown as ethers.EventFilter; + 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 8adb6b0..0f41ecc 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,6 +39,7 @@ export class RLNContract { ): Promise { const rlnContract = new RLNContract(rlnInstance, options); + await rlnContract.initStorageContract(options.provider); await rlnContract.fetchMembers(rlnInstance); rlnContract.subscribeToMembers(rlnInstance); @@ -45,24 +52,53 @@ export class RLNContract { ) { 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; + if (!this.storageContract) { + throw Error("Storage contract was not initialized."); + } + return this.storageContract as ethers.Contract; } public get members(): Member[] { return this._members; } + private get membersFilter(): ethers.EventFilter { + if (!this._membersFilter) { + throw Error("Members filter was not initialized."); + } + 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,12 +200,19 @@ export class RLNContract { public async registerWithKey( credential: IdentityCredential ): Promise { - const depositValue = await this.contract.MEMBERSHIP_DEPOSIT(); - + if (!this.storageIndex) { + throw Error( + "Cannot register credential, no storage contract index found." + ); + } const txRegisterResponse: ethers.ContractTransaction = - await this.contract.register(credential.IDCommitmentBigInt, { - value: depositValue, - }); + await this.registryContract.register( + this.storageIndex, + credential.IDCommitmentBigInt, + { + gasLimit: 100000, + } + ); const txRegisterReceipt = await txRegisterResponse.wait(); return txRegisterReceipt?.events?.[0];