@waku/rln 0.0.2-8a6571f.0 → 0.0.2-950aefb.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,7 @@ import { RATE_LIMIT_PARAMS, DEFAULT_RATE_LIMIT } from './constants.js';
10
10
  import { Contract } from '../../../../node_modules/@ethersproject/contracts/lib.esm/index.js';
11
11
  import { BigNumber } from '../../../../node_modules/@ethersproject/bignumber/lib.esm/bignumber.js';
12
12
 
13
+ /* eslint-disable no-console */
13
14
  const log = new Logger("waku:rln:contract");
14
15
  var MembershipState;
15
16
  (function (MembershipState) {
@@ -26,6 +27,7 @@ class RLNContract {
26
27
  _members = new Map();
27
28
  _membersFilter;
28
29
  _membersRemovedFilter;
30
+ _membersExpiredFilter;
29
31
  /**
30
32
  * Asynchronous initializer for RLNContract.
31
33
  * Allows injecting a mocked contract for testing purposes.
@@ -47,9 +49,10 @@ class RLNContract {
47
49
  // Use the injected contract if provided; otherwise, instantiate a new one.
48
50
  this.contract = contract || new Contract(address, RLN_ABI, signer);
49
51
  this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
50
- // Initialize event filters for MembershipRegistered and MembershipErased
52
+ // Initialize event filters
51
53
  this._membersFilter = this.contract.filters.MembershipRegistered();
52
54
  this._membersRemovedFilter = this.contract.filters.MembershipErased();
55
+ this._membersExpiredFilter = this.contract.filters.MembershipExpired();
53
56
  }
54
57
  /**
55
58
  * Gets the current rate limit for this contract instance
@@ -106,11 +109,12 @@ class RLNContract {
106
109
  * @returns Promise<number> The remaining rate limit that can be allocated
107
110
  */
108
111
  async getRemainingTotalRateLimit() {
109
- const [maxTotal, currentTotal] = await Promise.all([
110
- this.contract.maxTotalRateLimit(),
111
- this.contract.currentTotalRateLimit()
112
- ]);
113
- return maxTotal.sub(currentTotal).toNumber();
112
+ // const [maxTotal, currentTotal] = await Promise.all([
113
+ // this.contract.maxTotalRateLimit(),
114
+ // this.contract.currentTotalRateLimit()
115
+ // ]);
116
+ // return maxTotal.sub(currentTotal).toNumber();
117
+ return 10_000;
114
118
  }
115
119
  /**
116
120
  * Updates the rate limit for future registrations
@@ -135,6 +139,12 @@ class RLNContract {
135
139
  }
136
140
  return this._membersRemovedFilter;
137
141
  }
142
+ get membersExpiredFilter() {
143
+ if (!this._membersExpiredFilter) {
144
+ throw Error("MembersExpired filter was not initialized.");
145
+ }
146
+ return this._membersExpiredFilter;
147
+ }
138
148
  async fetchMembers(rlnInstance, options = {}) {
139
149
  const registeredMemberEvents = await queryFilter(this.contract, {
140
150
  fromBlock: this.deployBlock,
@@ -146,7 +156,16 @@ class RLNContract {
146
156
  ...options,
147
157
  membersFilter: this.membersRemovedFilter
148
158
  });
149
- const events = [...registeredMemberEvents, ...removedMemberEvents];
159
+ const expiredMemberEvents = await queryFilter(this.contract, {
160
+ fromBlock: this.deployBlock,
161
+ ...options,
162
+ membersFilter: this.membersExpiredFilter
163
+ });
164
+ const events = [
165
+ ...registeredMemberEvents,
166
+ ...removedMemberEvents,
167
+ ...expiredMemberEvents
168
+ ];
150
169
  this.processEvents(rlnInstance, events);
151
170
  }
152
171
  processEvents(rlnInstance, events) {
@@ -156,8 +175,17 @@ class RLNContract {
156
175
  if (!evt.args) {
157
176
  return;
158
177
  }
159
- if (evt.event === "MembershipErased") {
160
- const index = evt.args.index;
178
+ if (evt.event === "MembershipErased" ||
179
+ evt.event === "MembershipExpired") {
180
+ // Both MembershipErased and MembershipExpired events should remove members
181
+ let index = evt.args.index;
182
+ if (!index) {
183
+ return;
184
+ }
185
+ // Convert index to ethers.BigNumber if it's not already
186
+ if (typeof index === "number" || typeof index === "string") {
187
+ index = BigNumber.from(index);
188
+ }
161
189
  const toRemoveVal = toRemoveTable.get(evt.blockNumber);
162
190
  if (toRemoveVal != undefined) {
163
191
  toRemoveVal.push(index.toNumber());
@@ -185,14 +213,21 @@ class RLNContract {
185
213
  if (!evt.args)
186
214
  return;
187
215
  const _idCommitment = evt.args.idCommitment;
188
- const index = evt.args.index;
216
+ let index = evt.args.index;
217
+ // Ensure index is an ethers.BigNumber
189
218
  if (!_idCommitment || !index) {
190
219
  return;
191
220
  }
221
+ // Convert index to ethers.BigNumber if it's not already
222
+ if (typeof index === "number" || typeof index === "string") {
223
+ index = BigNumber.from(index);
224
+ }
192
225
  const idCommitment = zeroPadLE(hexToBytes(_idCommitment), 32);
193
226
  rlnInstance.zerokit.insertMember(idCommitment);
194
- this._members.set(index.toNumber(), {
195
- index,
227
+ // Always store the numeric index as the key, but the BigNumber as the value
228
+ const numericIndex = index.toNumber();
229
+ this._members.set(numericIndex, {
230
+ index, // This is always a BigNumber
196
231
  idCommitment: _idCommitment
197
232
  });
198
233
  });
@@ -213,34 +248,81 @@ class RLNContract {
213
248
  });
214
249
  }
215
250
  subscribeToMembers(rlnInstance) {
216
- this.contract.on(this.membersFilter, (_idCommitment, _rateLimit, _index, event) => {
251
+ this.contract.on(this.membersFilter, (_idCommitment, _membershipRateLimit, _index, event) => {
252
+ this.processEvents(rlnInstance, [event]);
253
+ });
254
+ this.contract.on(this.membersRemovedFilter, (_idCommitment, _membershipRateLimit, _index, event) => {
217
255
  this.processEvents(rlnInstance, [event]);
218
256
  });
219
- this.contract.on(this.membersRemovedFilter, (_idCommitment, _rateLimit, _index, event) => {
257
+ this.contract.on(this.membersExpiredFilter, (_idCommitment, _membershipRateLimit, _index, event) => {
220
258
  this.processEvents(rlnInstance, [event]);
221
259
  });
222
260
  }
223
261
  async registerWithIdentity(identity) {
224
262
  try {
263
+ console.log("registerWithIdentity - starting registration process");
264
+ console.log("registerWithIdentity - identity:", identity);
265
+ console.log("registerWithIdentity - IDCommitmentBigInt:", identity.IDCommitmentBigInt.toString());
266
+ console.log("registerWithIdentity - rate limit:", this.rateLimit);
225
267
  log.info(`Registering identity with rate limit: ${this.rateLimit} messages/epoch`);
226
- const txRegisterResponse = await this.contract.register(identity.IDCommitmentBigInt, this.rateLimit, [], { gasLimit: 300000 });
268
+ // Check if the ID commitment is already registered
269
+ const existingIndex = await this.getMemberIndex(identity.IDCommitmentBigInt.toString());
270
+ if (existingIndex) {
271
+ console.error(`ID commitment is already registered with index ${existingIndex}`);
272
+ throw new Error(`ID commitment is already registered with index ${existingIndex}`);
273
+ }
274
+ // Check if there's enough remaining rate limit
275
+ const remainingRateLimit = await this.getRemainingTotalRateLimit();
276
+ if (remainingRateLimit < this.rateLimit) {
277
+ console.error(`Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`);
278
+ throw new Error(`Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`);
279
+ }
280
+ console.log("registerWithIdentity - calling contract.register");
281
+ // Estimate gas for the transaction
282
+ const estimatedGas = await this.contract.estimateGas.register(identity.IDCommitmentBigInt, this.rateLimit, []);
283
+ const gasLimit = estimatedGas.add(10000);
284
+ const txRegisterResponse = await this.contract.register(identity.IDCommitmentBigInt, this.rateLimit, [], { gasLimit });
285
+ console.log("registerWithIdentity - txRegisterResponse:", txRegisterResponse);
286
+ console.log("registerWithIdentity - hash:", txRegisterResponse.hash);
287
+ console.log("registerWithIdentity - waiting for transaction confirmation...");
227
288
  const txRegisterReceipt = await txRegisterResponse.wait();
289
+ console.log("registerWithIdentity - txRegisterReceipt:", txRegisterReceipt);
290
+ console.log("registerWithIdentity - transaction status:", txRegisterReceipt.status);
291
+ console.log("registerWithIdentity - block number:", txRegisterReceipt.blockNumber);
292
+ console.log("registerWithIdentity - gas used:", txRegisterReceipt.gasUsed.toString());
293
+ // Check transaction status
294
+ if (txRegisterReceipt.status === 0) {
295
+ console.error("Transaction failed on-chain");
296
+ throw new Error("Transaction failed on-chain");
297
+ }
228
298
  const memberRegistered = txRegisterReceipt.events?.find((event) => event.event === "MembershipRegistered");
299
+ console.log("registerWithIdentity - memberRegistered event:", memberRegistered);
229
300
  if (!memberRegistered || !memberRegistered.args) {
230
- log.error("Failed to register membership: No MembershipRegistered event found");
301
+ console.log("registerWithIdentity - ERROR: no memberRegistered event found");
302
+ console.log("registerWithIdentity - all events:", txRegisterReceipt.events);
303
+ console.error("Failed to register membership: No MembershipRegistered event found");
231
304
  return undefined;
232
305
  }
306
+ console.log("registerWithIdentity - memberRegistered args:", memberRegistered.args);
233
307
  const decodedData = {
234
308
  idCommitment: memberRegistered.args.idCommitment,
235
- rateLimit: memberRegistered.args.rateLimit,
309
+ membershipRateLimit: memberRegistered.args.membershipRateLimit,
236
310
  index: memberRegistered.args.index
237
311
  };
312
+ console.log("registerWithIdentity - decodedData:", decodedData);
313
+ console.log("registerWithIdentity - index:", decodedData.index.toString());
314
+ console.log("registerWithIdentity - membershipRateLimit:", decodedData.membershipRateLimit.toString());
238
315
  log.info(`Successfully registered membership with index ${decodedData.index} ` +
239
- `and rate limit ${decodedData.rateLimit}`);
316
+ `and rate limit ${decodedData.membershipRateLimit}`);
317
+ console.log("registerWithIdentity - getting network information");
240
318
  const network = await this.contract.provider.getNetwork();
319
+ console.log("registerWithIdentity - network:", network);
320
+ console.log("registerWithIdentity - chainId:", network.chainId);
241
321
  const address = this.contract.address;
322
+ console.log("registerWithIdentity - contract address:", address);
242
323
  const membershipId = decodedData.index.toNumber();
243
- return {
324
+ console.log("registerWithIdentity - membershipId:", membershipId);
325
+ const result = {
244
326
  identity,
245
327
  membership: {
246
328
  address,
@@ -248,10 +330,39 @@ class RLNContract {
248
330
  chainId: network.chainId
249
331
  }
250
332
  };
333
+ console.log("registerWithIdentity - returning result:", result);
334
+ return result;
251
335
  }
252
336
  catch (error) {
253
- log.error(`Error in registerWithIdentity: ${error.message}`);
254
- return undefined;
337
+ console.log("registerWithIdentity - ERROR:", error);
338
+ // Improved error handling to decode contract errors
339
+ if (error instanceof Error) {
340
+ const errorMessage = error.message;
341
+ console.log("registerWithIdentity - error message:", errorMessage);
342
+ console.log("registerWithIdentity - error stack:", error.stack);
343
+ // Try to extract more specific error information
344
+ if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
345
+ console.error("Registration failed: Cannot exceed maximum total rate limit");
346
+ }
347
+ else if (errorMessage.includes("InvalidIdCommitment")) {
348
+ console.error("Registration failed: Invalid ID commitment");
349
+ }
350
+ else if (errorMessage.includes("InvalidMembershipRateLimit")) {
351
+ console.error("Registration failed: Invalid membership rate limit");
352
+ }
353
+ else if (errorMessage.includes("execution reverted")) {
354
+ // Generic revert
355
+ console.error("Contract execution reverted. Check contract requirements.");
356
+ }
357
+ else {
358
+ console.error(`Error in registerWithIdentity: ${errorMessage}`);
359
+ }
360
+ }
361
+ else {
362
+ console.error("Unknown error in registerWithIdentity");
363
+ }
364
+ // Re-throw the error to allow callers to handle it
365
+ throw error;
255
366
  }
256
367
  }
257
368
  /**
@@ -287,11 +398,11 @@ class RLNContract {
287
398
  }
288
399
  const decodedData = {
289
400
  idCommitment: memberRegistered.args.idCommitment,
290
- rateLimit: memberRegistered.args.rateLimit,
401
+ membershipRateLimit: memberRegistered.args.membershipRateLimit,
291
402
  index: memberRegistered.args.index
292
403
  };
293
404
  log.info(`Successfully registered membership with permit. Index: ${decodedData.index}, ` +
294
- `Rate limit: ${decodedData.rateLimit}, Erased ${idCommitmentsToErase.length} commitments`);
405
+ `Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`);
295
406
  const network = await this.contract.provider.getNetwork();
296
407
  const address = this.contract.address;
297
408
  const membershipId = decodedData.index.toNumber();
@@ -352,17 +463,29 @@ class RLNContract {
352
463
  }
353
464
  }
354
465
  async extendMembership(idCommitment) {
355
- return this.contract.extendMemberships([idCommitment]);
466
+ const tx = await this.contract.extendMemberships([idCommitment]);
467
+ return await tx.wait();
356
468
  }
357
469
  async eraseMembership(idCommitment, eraseFromMembershipSet = true) {
358
- return this.contract.eraseMemberships([idCommitment], eraseFromMembershipSet);
470
+ const tx = await this.contract.eraseMemberships([idCommitment], eraseFromMembershipSet);
471
+ return await tx.wait();
359
472
  }
360
473
  async registerMembership(idCommitment, rateLimit = DEFAULT_RATE_LIMIT) {
361
- if (rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
362
- rateLimit > RATE_LIMIT_PARAMS.MAX_RATE) {
363
- throw new Error(`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`);
474
+ try {
475
+ if (rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
476
+ rateLimit > RATE_LIMIT_PARAMS.MAX_RATE) {
477
+ throw new Error(`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`);
478
+ }
479
+ // Try to register
480
+ return this.contract.register(idCommitment, rateLimit, []);
481
+ }
482
+ catch (error) {
483
+ console.error("Error in registerMembership:", error);
484
+ // Run debug to help diagnose the issue
485
+ await this.debugRegistration(idCommitment, rateLimit);
486
+ // Re-throw the error
487
+ throw error;
364
488
  }
365
- return this.contract.register(idCommitment, rateLimit, []);
366
489
  }
367
490
  async getMemberIndex(idCommitment) {
368
491
  try {
@@ -377,6 +500,74 @@ class RLNContract {
377
500
  return undefined;
378
501
  }
379
502
  }
503
+ /**
504
+ * Debug function to check why a registration might fail
505
+ * @param idCommitment The ID commitment to check
506
+ * @param rateLimit The rate limit to check
507
+ */
508
+ async debugRegistration(idCommitment, rateLimit) {
509
+ console.log("=== DEBUG REGISTRATION ===");
510
+ console.log(`ID Commitment: ${idCommitment}`);
511
+ console.log(`Rate Limit: ${rateLimit}`);
512
+ // Check if ID commitment is already registered
513
+ try {
514
+ const existingIndex = await this.getMemberIndex(idCommitment);
515
+ if (existingIndex) {
516
+ console.error(`ERROR: ID commitment is already registered with index ${existingIndex}`);
517
+ }
518
+ else {
519
+ console.log("ID commitment is not yet registered ✓");
520
+ }
521
+ }
522
+ catch (error) {
523
+ console.error("Error checking if ID commitment is registered:", error);
524
+ }
525
+ // Check rate limit constraints
526
+ try {
527
+ const minRateLimit = await this.getMinRateLimit();
528
+ const maxRateLimit = await this.getMaxRateLimit();
529
+ const maxTotalRateLimit = await this.getMaxTotalRateLimit();
530
+ const currentTotalRateLimit = await this.getCurrentTotalRateLimit();
531
+ const remainingRateLimit = await this.getRemainingTotalRateLimit();
532
+ console.log(`Min Rate Limit: ${minRateLimit}`);
533
+ console.log(`Max Rate Limit: ${maxRateLimit}`);
534
+ console.log(`Max Total Rate Limit: ${maxTotalRateLimit}`);
535
+ console.log(`Current Total Rate Limit: ${currentTotalRateLimit}`);
536
+ console.log(`Remaining Rate Limit: ${remainingRateLimit}`);
537
+ if (rateLimit < minRateLimit) {
538
+ console.error(`ERROR: Rate limit ${rateLimit} is below minimum ${minRateLimit}`);
539
+ }
540
+ if (rateLimit > maxRateLimit) {
541
+ console.error(`ERROR: Rate limit ${rateLimit} exceeds maximum ${maxRateLimit}`);
542
+ }
543
+ if (rateLimit > remainingRateLimit) {
544
+ console.error(`ERROR: Rate limit ${rateLimit} exceeds remaining capacity ${remainingRateLimit}`);
545
+ }
546
+ }
547
+ catch (error) {
548
+ console.error("Error checking rate limit constraints:", error);
549
+ }
550
+ // Try to estimate gas for the transaction to see if it would revert
551
+ try {
552
+ await this.contract.estimateGas.register(idCommitment, rateLimit, []);
553
+ console.log("Transaction gas estimation succeeded ✓");
554
+ }
555
+ catch (error) {
556
+ console.error("Transaction would revert with error:", error);
557
+ // Try to extract more specific error information
558
+ const errorMessage = error.message;
559
+ if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
560
+ console.error("Cannot exceed maximum total rate limit");
561
+ }
562
+ else if (errorMessage.includes("InvalidIdCommitment")) {
563
+ console.error("Invalid ID commitment format");
564
+ }
565
+ else if (errorMessage.includes("InvalidMembershipRateLimit")) {
566
+ console.error("Invalid membership rate limit");
567
+ }
568
+ }
569
+ console.log("=== END DEBUG ===");
570
+ }
380
571
  }
381
572
  // These values should be tested on other networks
382
573
  const FETCH_CHUNK = 5;
@@ -100,8 +100,6 @@ class RLNInstance {
100
100
  return this._signer;
101
101
  }
102
102
  async start(options = {}) {
103
- // eslint-disable-next-line no-console
104
- console.log("starting", options);
105
103
  if (this.started || this.starting) {
106
104
  return;
107
105
  }
@@ -133,12 +131,6 @@ class RLNInstance {
133
131
  if (address === SEPOLIA_CONTRACT.address) {
134
132
  chainId = SEPOLIA_CONTRACT.chainId;
135
133
  }
136
- // eslint-disable-next-line no-console
137
- console.log({
138
- chainId,
139
- address,
140
- SEPOLIA_CONTRACT
141
- });
142
134
  const signer = options.signer || (await extractMetaMaskSigner());
143
135
  const currentChainId = await signer.getChainId();
144
136
  if (chainId && chainId !== currentChainId) {
@@ -174,6 +166,8 @@ class RLNInstance {
174
166
  if ("signature" in options) {
175
167
  identity = this.zerokit.generateSeededIdentityCredential(options.signature);
176
168
  }
169
+ // eslint-disable-next-line no-console
170
+ console.log("registering membership", identity);
177
171
  if (!identity) {
178
172
  throw Error("Missing signature or identity to register membership.");
179
173
  }