mirror of
https://github.com/waku-org/js-waku.git
synced 2025-02-23 09:38:24 +00:00
feat: upgrading adapter to basic rlnv2
This commit is contained in:
parent
fe5bbd9854
commit
8f5df7817f
@ -56,6 +56,7 @@
|
||||
"@types/chai-spies": "^1.0.6",
|
||||
"@types/deep-equal-in-any-order": "^1.0.4",
|
||||
"@types/lodash": "^4.17.15",
|
||||
"@types/sinon": "^17.0.3",
|
||||
"@waku/build-utils": "^1.0.0",
|
||||
"@waku/message-encryption": "^0.0.31",
|
||||
"chai": "^5.1.2",
|
||||
@ -64,7 +65,8 @@
|
||||
"chai-subset": "^1.6.0",
|
||||
"deep-equal-in-any-order": "^2.0.6",
|
||||
"fast-check": "^3.23.2",
|
||||
"rollup-plugin-copy": "^3.5.0"
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"sinon": "^19.0.2"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
@ -1,43 +1,100 @@
|
||||
import { expect } from "chai";
|
||||
import { hexToBytes } from "@waku/utils/bytes";
|
||||
import chai from "chai";
|
||||
import spies from "chai-spies";
|
||||
import * as ethers from "ethers";
|
||||
import sinon, { SinonSandbox } from "sinon";
|
||||
|
||||
import { createRLN } from "../create.js";
|
||||
import type { IdentityCredential } from "../identity.js";
|
||||
|
||||
import { SEPOLIA_CONTRACT } from "./constants.js";
|
||||
import { RLNContract } from "./rln_contract.js";
|
||||
|
||||
describe("RLN Contract abstraction", () => {
|
||||
it("should be able to fetch members from events and store to rln instance", async () => {
|
||||
const rlnInstance = await createRLN();
|
||||
let insertMemberCalled = false;
|
||||
chai.use(spies);
|
||||
|
||||
// Track if insertMember was called
|
||||
const originalInsertMember = rlnInstance.zerokit.insertMember;
|
||||
rlnInstance.zerokit.insertMember = function (
|
||||
this: any,
|
||||
...args: Parameters<typeof originalInsertMember>
|
||||
) {
|
||||
insertMemberCalled = true;
|
||||
return originalInsertMember.apply(this, args);
|
||||
const DEFAULT_RATE_LIMIT = 10;
|
||||
|
||||
function mockRLNv2RegisteredEvent(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;
|
||||
}
|
||||
|
||||
describe("RLN Contract abstraction - RLN v2", () => {
|
||||
let sandbox: SinonSandbox;
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it("should fetch members from events and store them in the RLN instance", async () => {
|
||||
const rlnInstance = await createRLN();
|
||||
|
||||
rlnInstance.zerokit.insertMember = () => undefined;
|
||||
const insertMemberSpy = chai.spy.on(rlnInstance.zerokit, "insertMember");
|
||||
|
||||
const membershipRegisteredEvent = mockRLNv2RegisteredEvent();
|
||||
const mockedRegistryContract = {
|
||||
queryFilter: () => [membershipRegisteredEvent],
|
||||
provider: {
|
||||
getLogs: () => [],
|
||||
getBlockNumber: () => Promise.resolve(1000)
|
||||
},
|
||||
interface: {
|
||||
getEvent: (eventName: string) => ({
|
||||
name: eventName,
|
||||
format: () => {}
|
||||
})
|
||||
},
|
||||
filters: {
|
||||
MembershipRegistered: () => ({}),
|
||||
MembershipRemoved: () => ({})
|
||||
},
|
||||
on: () => ({}),
|
||||
removeAllListeners: () => ({})
|
||||
};
|
||||
|
||||
const voidSigner = new ethers.VoidSigner(SEPOLIA_CONTRACT.address);
|
||||
const rlnContract = new RLNContract(rlnInstance, {
|
||||
registryAddress: SEPOLIA_CONTRACT.address,
|
||||
signer: voidSigner
|
||||
const queryFilterSpy = chai.spy.on(mockedRegistryContract, "queryFilter");
|
||||
|
||||
const provider = new ethers.providers.JsonRpcProvider();
|
||||
const voidSigner = new ethers.VoidSigner(
|
||||
SEPOLIA_CONTRACT.address,
|
||||
provider
|
||||
);
|
||||
const rlnContract = await RLNContract.init(rlnInstance, {
|
||||
address: SEPOLIA_CONTRACT.address,
|
||||
signer: voidSigner,
|
||||
rateLimit: 0,
|
||||
contract: mockedRegistryContract as unknown as ethers.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, {
|
||||
fromBlock: 0,
|
||||
fetchRange: 1000,
|
||||
fetchChunks: 2
|
||||
});
|
||||
|
||||
await rlnContract.fetchMembers(rlnInstance);
|
||||
chai
|
||||
.expect(insertMemberSpy)
|
||||
.to.have.been.called.with(
|
||||
ethers.utils.zeroPad(
|
||||
hexToBytes(membershipRegisteredEvent.args!.idCommitment),
|
||||
32
|
||||
)
|
||||
);
|
||||
|
||||
expect(insertMemberCalled).to.be.true;
|
||||
chai.expect(queryFilterSpy).to.have.been.called;
|
||||
});
|
||||
|
||||
it("should register a member", async () => {
|
||||
@ -45,38 +102,96 @@ describe("RLN Contract abstraction", () => {
|
||||
"0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c";
|
||||
|
||||
const rlnInstance = await createRLN();
|
||||
const voidSigner = new ethers.VoidSigner(SEPOLIA_CONTRACT.address);
|
||||
const rlnContract = new RLNContract(rlnInstance, {
|
||||
registryAddress: SEPOLIA_CONTRACT.address,
|
||||
signer: voidSigner
|
||||
const identity: IdentityCredential =
|
||||
rlnInstance.zerokit.generateSeededIdentityCredential(mockSignature);
|
||||
|
||||
rlnInstance.zerokit.insertMember = () => undefined;
|
||||
const insertMemberSpy = chai.spy.on(rlnInstance.zerokit, "insertMember");
|
||||
|
||||
const formatIdCommitment = (idCommitmentBigInt: bigint): string =>
|
||||
"0x" + idCommitmentBigInt.toString(16).padStart(64, "0");
|
||||
|
||||
const membershipRegisteredEvent = mockRLNv2RegisteredEvent(
|
||||
formatIdCommitment(identity.IDCommitmentBigInt)
|
||||
);
|
||||
|
||||
const mockedRegistryContract = {
|
||||
register: () => ({
|
||||
wait: () =>
|
||||
Promise.resolve({
|
||||
events: [
|
||||
{
|
||||
event: "MembershipRegistered",
|
||||
args: {
|
||||
idCommitment: formatIdCommitment(identity.IDCommitmentBigInt),
|
||||
rateLimit: 0,
|
||||
index: ethers.BigNumber.from(1)
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}),
|
||||
queryFilter: () => [membershipRegisteredEvent],
|
||||
provider: {
|
||||
getLogs: () => [],
|
||||
getBlockNumber: () => Promise.resolve(1000),
|
||||
getNetwork: () => Promise.resolve({ chainId: 11155111 })
|
||||
},
|
||||
address: SEPOLIA_CONTRACT.address,
|
||||
interface: {
|
||||
getEvent: (eventName: string) => ({
|
||||
name: eventName,
|
||||
format: () => {}
|
||||
})
|
||||
},
|
||||
filters: {
|
||||
MembershipRegistered: () => ({}),
|
||||
MembershipRemoved: () => ({})
|
||||
},
|
||||
on: () => ({}),
|
||||
removeAllListeners: () => ({})
|
||||
};
|
||||
|
||||
const provider = new ethers.providers.JsonRpcProvider();
|
||||
const voidSigner = new ethers.VoidSigner(
|
||||
SEPOLIA_CONTRACT.address,
|
||||
provider
|
||||
);
|
||||
const rlnContract = await RLNContract.init(rlnInstance, {
|
||||
signer: voidSigner,
|
||||
address: SEPOLIA_CONTRACT.address,
|
||||
rateLimit: 0,
|
||||
contract: mockedRegistryContract as unknown as ethers.Contract
|
||||
});
|
||||
|
||||
let registerCalled = false;
|
||||
rlnContract["storageIndex"] = 1;
|
||||
rlnContract["_membersFilter"] = {
|
||||
address: "",
|
||||
topics: []
|
||||
} as unknown as ethers.EventFilter;
|
||||
rlnContract["registryContract"] = {
|
||||
"register(uint16,uint256)": () => {
|
||||
registerCalled = true;
|
||||
return Promise.resolve({ wait: () => Promise.resolve(undefined) });
|
||||
}
|
||||
} as unknown as ethers.Contract;
|
||||
const registerSpy = chai.spy.on(mockedRegistryContract, "register");
|
||||
|
||||
const identity =
|
||||
rlnInstance.zerokit.generateSeededIdentityCredential(mockSignature);
|
||||
await rlnContract.registerWithIdentity(identity);
|
||||
const decryptedCredentials =
|
||||
await rlnContract.registerWithIdentity(identity);
|
||||
|
||||
expect(registerCalled).to.be.true;
|
||||
chai.expect(decryptedCredentials).to.not.be.undefined;
|
||||
if (!decryptedCredentials)
|
||||
throw new Error("Decrypted credentials should not be undefined");
|
||||
|
||||
chai
|
||||
.expect(registerSpy)
|
||||
.to.have.been.called.with(identity.IDCommitmentBigInt, 0, [], {
|
||||
gasLimit: 300000
|
||||
});
|
||||
|
||||
chai.expect(decryptedCredentials).to.have.property("identity");
|
||||
chai.expect(decryptedCredentials).to.have.property("membership");
|
||||
chai.expect(decryptedCredentials.membership).to.include({
|
||||
address: SEPOLIA_CONTRACT.address,
|
||||
treeIndex: 1
|
||||
});
|
||||
|
||||
const expectedIdCommitment = ethers.utils.zeroPad(
|
||||
hexToBytes(formatIdCommitment(identity.IDCommitmentBigInt)),
|
||||
32
|
||||
);
|
||||
const insertMemberCalls = (insertMemberSpy as any).__spy.calls;
|
||||
chai.expect(insertMemberCalls).to.have.length(2);
|
||||
chai.expect(insertMemberCalls[1][0]).to.deep.equal(expectedIdCommitment);
|
||||
});
|
||||
});
|
||||
|
||||
function mockEvent(): ethers.Event {
|
||||
return {
|
||||
args: {
|
||||
idCommitment: { _hex: "0xb3df1c4e5600ef2b" },
|
||||
index: ethers.BigNumber.from(1)
|
||||
}
|
||||
} as unknown as ethers.Event;
|
||||
}
|
||||
|
@ -9,44 +9,30 @@ import { MerkleRootTracker } from "../root_tracker.js";
|
||||
import { zeroPadLE } from "../utils/index.js";
|
||||
|
||||
import { RLN_V2_ABI } from "./abi/rlnv2.js";
|
||||
import {
|
||||
FetchMembersOptions,
|
||||
MembershipRegisteredEvent,
|
||||
RLNContractInitOptions
|
||||
} from "./types.js";
|
||||
import { Member } from "./types.js";
|
||||
|
||||
const log = new Logger("waku:rln:contract");
|
||||
|
||||
type Member = {
|
||||
idCommitment: string;
|
||||
index: ethers.BigNumber;
|
||||
};
|
||||
|
||||
type Signer = ethers.Signer;
|
||||
|
||||
type RLNContractOptions = {
|
||||
signer: Signer;
|
||||
registryAddress: string;
|
||||
};
|
||||
|
||||
type RLNStorageOptions = {
|
||||
storageIndex?: number;
|
||||
};
|
||||
|
||||
type RLNContractInitOptions = RLNContractOptions & RLNStorageOptions;
|
||||
|
||||
type FetchMembersOptions = {
|
||||
fromBlock?: number;
|
||||
fetchRange?: number;
|
||||
fetchChunks?: number;
|
||||
};
|
||||
|
||||
export class RLNContract {
|
||||
private registryContract: ethers.Contract;
|
||||
private contract: ethers.Contract;
|
||||
private merkleRootTracker: MerkleRootTracker;
|
||||
|
||||
private deployBlock: undefined | number;
|
||||
private storageIndex: undefined | number;
|
||||
private storageContract: undefined | ethers.Contract;
|
||||
private _membersFilter: undefined | ethers.EventFilter;
|
||||
private rateLimit: number;
|
||||
private _membersFilter: ethers.EventFilter;
|
||||
private _membersRemovedFilter: ethers.EventFilter;
|
||||
|
||||
private _members: Map<number, Member> = new Map();
|
||||
|
||||
/**
|
||||
* Asynchronous initializer for RLNContract.
|
||||
* Allows injecting a mocked registryContract for testing purposes.
|
||||
*/
|
||||
public static async init(
|
||||
rlnInstance: RLNInstance,
|
||||
options: RLNContractInitOptions
|
||||
@ -58,33 +44,35 @@ export class RLNContract {
|
||||
|
||||
return rlnContract;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
/**
|
||||
* Private constructor to enforce the use of the async init method.
|
||||
*/
|
||||
private constructor(
|
||||
rlnInstance: RLNInstance,
|
||||
{ registryAddress, signer }: RLNContractOptions
|
||||
options: RLNContractInitOptions
|
||||
) {
|
||||
const { address, signer, rateLimit, contract } = options;
|
||||
|
||||
if (rateLimit === undefined) {
|
||||
throw new Error("rateLimit must be provided in RLNContractOptions.");
|
||||
}
|
||||
|
||||
this.rateLimit = rateLimit;
|
||||
|
||||
const initialRoot = rlnInstance.zerokit.getMerkleRoot();
|
||||
|
||||
this.registryContract = new ethers.Contract(
|
||||
registryAddress,
|
||||
RLN_V2_ABI,
|
||||
signer
|
||||
);
|
||||
// Use the injected registryContract if provided; otherwise, instantiate a new one.
|
||||
this.contract =
|
||||
contract || new ethers.Contract(address, RLN_V2_ABI, signer);
|
||||
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
|
||||
|
||||
// Initialize event filters for MembershipRegistered and MembershipRemoved
|
||||
this._membersFilter = this.contract.filters.MembershipRegistered();
|
||||
this._membersRemovedFilter = this.contract.filters.MembershipRemoved();
|
||||
}
|
||||
|
||||
public get registry(): ethers.Contract {
|
||||
if (!this.registryContract) {
|
||||
throw Error("Registry contract was not initialized");
|
||||
}
|
||||
return this.registryContract as ethers.Contract;
|
||||
}
|
||||
|
||||
public get contract(): ethers.Contract {
|
||||
if (!this.storageContract) {
|
||||
throw Error("Storage contract was not initialized");
|
||||
}
|
||||
return this.storageContract as ethers.Contract;
|
||||
return this.contract;
|
||||
}
|
||||
|
||||
public get members(): Member[] {
|
||||
@ -98,7 +86,14 @@ export class RLNContract {
|
||||
if (!this._membersFilter) {
|
||||
throw Error("Members filter was not initialized.");
|
||||
}
|
||||
return this._membersFilter as ethers.EventFilter;
|
||||
return this._membersFilter;
|
||||
}
|
||||
|
||||
private get membersRemovedFilter(): ethers.EventFilter {
|
||||
if (!this._membersRemovedFilter) {
|
||||
throw Error("MembersRemoved filter was not initialized.");
|
||||
}
|
||||
return this._membersRemovedFilter;
|
||||
}
|
||||
|
||||
public async fetchMembers(
|
||||
@ -110,7 +105,14 @@ export class RLNContract {
|
||||
...options,
|
||||
membersFilter: this.membersFilter
|
||||
});
|
||||
this.processEvents(rlnInstance, registeredMemberEvents);
|
||||
const removedMemberEvents = await queryFilter(this.contract, {
|
||||
fromBlock: this.deployBlock,
|
||||
...options,
|
||||
membersFilter: this.membersRemovedFilter
|
||||
});
|
||||
|
||||
const events = [...registeredMemberEvents, ...removedMemberEvents];
|
||||
this.processEvents(rlnInstance, events);
|
||||
}
|
||||
|
||||
public processEvents(rlnInstance: RLNInstance, events: ethers.Event[]): void {
|
||||
@ -122,8 +124,8 @@ export class RLNContract {
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.removed) {
|
||||
const index: ethers.BigNumber = evt.args.index;
|
||||
if (evt.event === "MembershipRemoved") {
|
||||
const index = evt.args.index as ethers.BigNumber;
|
||||
const toRemoveVal = toRemoveTable.get(evt.blockNumber);
|
||||
if (toRemoveVal != undefined) {
|
||||
toRemoveVal.push(index.toNumber());
|
||||
@ -131,7 +133,7 @@ export class RLNContract {
|
||||
} else {
|
||||
toRemoveTable.set(evt.blockNumber, [index.toNumber()]);
|
||||
}
|
||||
} else {
|
||||
} else if (evt.event === "MembershipRegistered") {
|
||||
let eventsPerBlock = toInsertTable.get(evt.blockNumber);
|
||||
if (eventsPerBlock == undefined) {
|
||||
eventsPerBlock = [];
|
||||
@ -152,18 +154,20 @@ export class RLNContract {
|
||||
): void {
|
||||
toInsert.forEach((events: ethers.Event[], blockNumber: number) => {
|
||||
events.forEach((evt) => {
|
||||
const _idCommitment = evt?.args?.idCommitment;
|
||||
const index: ethers.BigNumber = evt?.args?.index;
|
||||
if (!evt.args) return;
|
||||
|
||||
const _idCommitment = evt.args.idCommitment as string;
|
||||
const index = evt.args.index as ethers.BigNumber;
|
||||
|
||||
if (!_idCommitment || !index) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idCommitment = zeroPadLE(hexToBytes(_idCommitment?._hex), 32);
|
||||
const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
|
||||
rlnInstance.zerokit.insertMember(idCommitment);
|
||||
this._members.set(index.toNumber(), {
|
||||
index,
|
||||
idCommitment: _idCommitment?._hex
|
||||
idCommitment: _idCommitment
|
||||
});
|
||||
});
|
||||
|
||||
@ -176,7 +180,7 @@ export class RLNContract {
|
||||
rlnInstance: RLNInstance,
|
||||
toRemove: Map<number, number[]>
|
||||
): void {
|
||||
const removeDescending = new Map([...toRemove].sort().reverse());
|
||||
const removeDescending = new Map([...toRemove].sort((a, b) => b[0] - a[0]));
|
||||
removeDescending.forEach((indexes: number[], blockNumber: number) => {
|
||||
indexes.forEach((index) => {
|
||||
if (this._members.has(index)) {
|
||||
@ -190,63 +194,153 @@ export class RLNContract {
|
||||
}
|
||||
|
||||
public subscribeToMembers(rlnInstance: RLNInstance): void {
|
||||
this.contract.on(this.membersFilter, (_pubkey, _index, event) =>
|
||||
this.processEvents(rlnInstance, [event])
|
||||
this.contract.on(
|
||||
this.membersFilter,
|
||||
(
|
||||
_idCommitment: string,
|
||||
_rateLimit: number,
|
||||
_index: ethers.BigNumber,
|
||||
event: ethers.Event
|
||||
) => {
|
||||
this.processEvents(rlnInstance, [event]);
|
||||
}
|
||||
);
|
||||
|
||||
this.contract.on(
|
||||
this.membersRemovedFilter,
|
||||
(
|
||||
_idCommitment: string,
|
||||
_index: ethers.BigNumber,
|
||||
event: ethers.Event
|
||||
) => {
|
||||
this.processEvents(rlnInstance, [event]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public async registerWithIdentity(
|
||||
identity: IdentityCredential
|
||||
): Promise<DecryptedCredentials | undefined> {
|
||||
if (this.storageIndex === undefined) {
|
||||
throw Error(
|
||||
"Cannot register credential, no storage contract index found."
|
||||
);
|
||||
}
|
||||
const txRegisterResponse: ethers.ContractTransaction =
|
||||
await this.registryContract["register(uint16,uint256)"](
|
||||
this.storageIndex,
|
||||
identity.IDCommitmentBigInt,
|
||||
{ gasLimit: 100000 }
|
||||
);
|
||||
const txRegisterReceipt = await txRegisterResponse.wait();
|
||||
try {
|
||||
const txRegisterResponse: ethers.ContractTransaction =
|
||||
await this.contract.register(
|
||||
identity.IDCommitmentBigInt,
|
||||
this.rateLimit,
|
||||
[],
|
||||
{ gasLimit: 300000 }
|
||||
);
|
||||
const txRegisterReceipt = await txRegisterResponse.wait();
|
||||
|
||||
// assumption: register(uint16,uint256) emits one event
|
||||
const memberRegistered = txRegisterReceipt?.events?.[0];
|
||||
const memberRegistered = txRegisterReceipt.events?.find(
|
||||
(event) => event.event === "MembershipRegistered"
|
||||
);
|
||||
|
||||
if (!memberRegistered) {
|
||||
if (!memberRegistered || !memberRegistered.args) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const decodedData: MembershipRegisteredEvent = {
|
||||
idCommitment: memberRegistered.args.idCommitment,
|
||||
rateLimit: memberRegistered.args.rateLimit,
|
||||
index: memberRegistered.args.index
|
||||
};
|
||||
|
||||
const network = await this.contract.provider.getNetwork();
|
||||
const address = this.contract.address;
|
||||
const membershipId = decodedData.index.toNumber();
|
||||
|
||||
return {
|
||||
identity,
|
||||
membership: {
|
||||
address,
|
||||
treeIndex: membershipId,
|
||||
chainId: network.chainId
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(`Error in registerWithIdentity: ${(error as Error).message}`);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const decodedData = this.contract.interface.decodeEventLog(
|
||||
"MemberRegistered",
|
||||
memberRegistered.data
|
||||
);
|
||||
public async registerWithPermitAndErase(
|
||||
identity: IdentityCredential,
|
||||
permit: {
|
||||
owner: string;
|
||||
deadline: number;
|
||||
v: number;
|
||||
r: string;
|
||||
s: string;
|
||||
},
|
||||
idCommitmentsToErase: string[]
|
||||
): Promise<DecryptedCredentials | undefined> {
|
||||
try {
|
||||
const txRegisterResponse: ethers.ContractTransaction =
|
||||
await this.contract.registerWithPermit(
|
||||
permit.owner,
|
||||
permit.deadline,
|
||||
permit.v,
|
||||
permit.r,
|
||||
permit.s,
|
||||
identity.IDCommitmentBigInt,
|
||||
this.rateLimit,
|
||||
idCommitmentsToErase.map((id) => ethers.BigNumber.from(id))
|
||||
);
|
||||
const txRegisterReceipt = await txRegisterResponse.wait();
|
||||
|
||||
const network = await this.registryContract.provider.getNetwork();
|
||||
const address = this.registryContract.address;
|
||||
const membershipId = decodedData.index.toNumber();
|
||||
const memberRegistered = txRegisterReceipt.events?.find(
|
||||
(event) => event.event === "MembershipRegistered"
|
||||
);
|
||||
|
||||
return {
|
||||
identity,
|
||||
membership: {
|
||||
address,
|
||||
treeIndex: membershipId,
|
||||
chainId: network.chainId
|
||||
if (!memberRegistered || !memberRegistered.args) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const decodedData: MembershipRegisteredEvent = {
|
||||
idCommitment: memberRegistered.args.idCommitment,
|
||||
rateLimit: memberRegistered.args.rateLimit,
|
||||
index: memberRegistered.args.index
|
||||
};
|
||||
|
||||
const network = await this.contract.provider.getNetwork();
|
||||
const address = this.contract.address;
|
||||
const membershipId = decodedData.index.toNumber();
|
||||
|
||||
return {
|
||||
identity,
|
||||
membership: {
|
||||
address,
|
||||
treeIndex: membershipId,
|
||||
chainId: network.chainId
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`Error in registerWithPermitAndErase: ${(error as Error).message}`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public roots(): Uint8Array[] {
|
||||
return this.merkleRootTracker.roots();
|
||||
}
|
||||
|
||||
public async withdraw(token: string, holder: string): Promise<void> {
|
||||
try {
|
||||
const tx = await this.contract.withdraw(token, { from: holder });
|
||||
await tx.wait();
|
||||
} catch (error) {
|
||||
log.error(`Error in withdraw: ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CustomQueryOptions = FetchMembersOptions & {
|
||||
interface CustomQueryOptions extends FetchMembersOptions {
|
||||
membersFilter: ethers.EventFilter;
|
||||
};
|
||||
}
|
||||
|
||||
// these value should be tested on other networks
|
||||
// These values should be tested on other networks
|
||||
const FETCH_CHUNK = 5;
|
||||
const BLOCK_RANGE = 3000;
|
||||
|
||||
@ -261,18 +355,18 @@ async function queryFilter(
|
||||
fetchChunks = FETCH_CHUNK
|
||||
} = options;
|
||||
|
||||
if (!fromBlock) {
|
||||
if (fromBlock === undefined) {
|
||||
return contract.queryFilter(membersFilter);
|
||||
}
|
||||
|
||||
if (!contract.signer.provider) {
|
||||
throw Error("No provider found on the contract's signer.");
|
||||
if (!contract.provider) {
|
||||
throw Error("No provider found on the contract.");
|
||||
}
|
||||
|
||||
const toBlock = await contract.signer.provider.getBlockNumber();
|
||||
const toBlock = await contract.provider.getBlockNumber();
|
||||
|
||||
if (toBlock - fromBlock < fetchRange) {
|
||||
return contract.queryFilter(membersFilter);
|
||||
return contract.queryFilter(membersFilter, fromBlock, toBlock);
|
||||
}
|
||||
|
||||
const events: ethers.Event[][] = [];
|
||||
@ -294,7 +388,7 @@ function splitToChunks(
|
||||
to: number,
|
||||
step: number
|
||||
): Array<[number, number]> {
|
||||
const chunks = [];
|
||||
const chunks: Array<[number, number]> = [];
|
||||
|
||||
let left = from;
|
||||
while (left < to) {
|
||||
|
28
packages/rln/src/contract/types.ts
Normal file
28
packages/rln/src/contract/types.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { ethers } from "ethers";
|
||||
|
||||
export interface MembershipRegisteredEvent {
|
||||
idCommitment: string;
|
||||
rateLimit: number;
|
||||
index: ethers.BigNumber;
|
||||
}
|
||||
|
||||
export interface Member {
|
||||
idCommitment: string;
|
||||
index: ethers.BigNumber;
|
||||
}
|
||||
|
||||
interface RLNContractOptions {
|
||||
signer: ethers.Signer;
|
||||
address: string;
|
||||
rateLimit?: number;
|
||||
}
|
||||
|
||||
export interface FetchMembersOptions {
|
||||
fromBlock?: number;
|
||||
fetchRange?: number;
|
||||
fetchChunks?: number;
|
||||
}
|
||||
|
||||
export interface RLNContractInitOptions extends RLNContractOptions {
|
||||
contract?: ethers.Contract;
|
||||
}
|
@ -140,8 +140,9 @@ export class RLNInstance {
|
||||
this._credentials = credentials;
|
||||
this._signer = signer!;
|
||||
this._contract = await RLNContract.init(this, {
|
||||
registryAddress: registryAddress!,
|
||||
signer: signer!
|
||||
address: registryAddress!,
|
||||
signer: signer!,
|
||||
rateLimit: 0
|
||||
});
|
||||
this.started = true;
|
||||
} finally {
|
||||
|
Loading…
x
Reference in New Issue
Block a user