@waku/rln 0.1.5-053bb95.0 → 0.1.5-35b50c3.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.
- package/bundle/_virtual/utils.js +2 -2
- package/bundle/_virtual/utils2.js +2 -2
- package/bundle/index.js +2 -1
- package/bundle/packages/rln/dist/contract/constants.js +6 -2
- package/bundle/packages/rln/dist/contract/rln_base_contract.js +279 -144
- package/bundle/packages/rln/dist/contract/rln_contract.js +74 -89
- package/bundle/packages/rln/dist/credentials_manager.js +1 -1
- package/bundle/packages/rln/dist/identity.js +0 -8
- package/bundle/packages/rln/dist/keystore/keystore.js +28 -15
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/random.js +1 -1
- package/bundle/packages/rln/node_modules/@chainsafe/bls-keystore/node_modules/ethereum-cryptography/utils.js +2 -2
- package/bundle/packages/rln/node_modules/@noble/hashes/_sha2.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/hmac.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/pbkdf2.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/scrypt.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/sha256.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/sha512.js +1 -1
- package/bundle/packages/rln/node_modules/@noble/hashes/utils.js +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/contract/constants.d.ts +1 -1
- package/dist/contract/constants.js +1 -1
- package/dist/contract/constants.js.map +1 -1
- package/dist/contract/index.d.ts +1 -0
- package/dist/contract/index.js +1 -0
- package/dist/contract/index.js.map +1 -1
- package/dist/contract/rln_base_contract.d.ts +27 -25
- package/dist/contract/rln_base_contract.js +279 -144
- package/dist/contract/rln_base_contract.js.map +1 -1
- package/dist/contract/rln_contract.d.ts +4 -24
- package/dist/contract/rln_contract.js +74 -89
- package/dist/contract/rln_contract.js.map +1 -1
- package/dist/contract/types.d.ts +5 -0
- package/dist/contract/types.js.map +1 -1
- package/dist/credentials_manager.js +1 -1
- package/dist/credentials_manager.js.map +1 -1
- package/dist/identity.d.ts +0 -1
- package/dist/identity.js +0 -8
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/keystore/keystore.d.ts +1 -0
- package/dist/keystore/keystore.js +28 -15
- package/dist/keystore/keystore.js.map +1 -1
- package/dist/keystore/types.d.ts +2 -2
- package/package.json +1 -1
- package/src/contract/constants.ts +1 -1
- package/src/contract/index.ts +1 -0
- package/src/contract/rln_base_contract.ts +427 -216
- package/src/contract/rln_contract.ts +95 -120
- package/src/contract/types.ts +5 -0
- package/src/credentials_manager.ts +1 -1
- package/src/identity.ts +0 -9
- package/src/index.ts +3 -1
- package/src/keystore/keystore.ts +54 -29
- package/src/keystore/types.ts +2 -2
- package/bundle/packages/rln/dist/contract/errors.js +0 -62
- package/dist/contract/errors.d.ts +0 -30
- package/dist/contract/errors.js +0 -61
- package/dist/contract/errors.js.map +0 -1
- package/src/contract/errors.ts +0 -75
@@ -1,3 +1,4 @@
|
|
1
|
+
/* eslint-disable no-console */
|
1
2
|
import { Logger } from "@waku/utils";
|
2
3
|
import { ethers } from "ethers";
|
3
4
|
|
@@ -7,15 +8,8 @@ import { DecryptedCredentials } from "../keystore/types.js";
|
|
7
8
|
import { RLN_ABI } from "./abi.js";
|
8
9
|
import { DEFAULT_RATE_LIMIT, RATE_LIMIT_PARAMS } from "./constants.js";
|
9
10
|
import {
|
10
|
-
|
11
|
-
|
12
|
-
MembershipExistsError,
|
13
|
-
MembershipNotFoundError,
|
14
|
-
RateLimitExceededError,
|
15
|
-
RLNContractError,
|
16
|
-
TransactionError
|
17
|
-
} from "./errors.js";
|
18
|
-
import {
|
11
|
+
CustomQueryOptions,
|
12
|
+
FetchMembersOptions,
|
19
13
|
Member,
|
20
14
|
MembershipInfo,
|
21
15
|
MembershipRegisteredEvent,
|
@@ -27,8 +21,18 @@ const log = new Logger("waku:rln:contract:base");
|
|
27
21
|
|
28
22
|
export class RLNBaseContract {
|
29
23
|
public contract: ethers.Contract;
|
24
|
+
private deployBlock: undefined | number;
|
30
25
|
private rateLimit: number;
|
31
26
|
|
27
|
+
protected _members: Map<number, Member> = new Map();
|
28
|
+
private _membersFilter: ethers.EventFilter;
|
29
|
+
private _membershipErasedFilter: ethers.EventFilter;
|
30
|
+
private _membersExpiredFilter: ethers.EventFilter;
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Constructor for RLNBaseContract.
|
34
|
+
* Allows injecting a mocked contract for testing purposes.
|
35
|
+
*/
|
32
36
|
public constructor(options: RLNContractInitOptions) {
|
33
37
|
const {
|
34
38
|
address,
|
@@ -37,9 +41,38 @@ export class RLNBaseContract {
|
|
37
41
|
contract
|
38
42
|
} = options;
|
39
43
|
|
40
|
-
|
44
|
+
log.info("Initializing RLNBaseContract", { address, rateLimit });
|
45
|
+
|
41
46
|
this.contract = contract || new ethers.Contract(address, RLN_ABI, signer);
|
42
47
|
this.rateLimit = rateLimit;
|
48
|
+
|
49
|
+
try {
|
50
|
+
log.info("Setting up event filters");
|
51
|
+
// Initialize event filters
|
52
|
+
this._membersFilter = this.contract.filters.MembershipRegistered();
|
53
|
+
this._membershipErasedFilter = this.contract.filters.MembershipErased();
|
54
|
+
this._membersExpiredFilter = this.contract.filters.MembershipExpired();
|
55
|
+
log.info("Event filters initialized successfully");
|
56
|
+
} catch (error) {
|
57
|
+
log.error("Failed to initialize event filters", { error });
|
58
|
+
throw new Error(
|
59
|
+
"Failed to initialize event filters: " + (error as Error).message
|
60
|
+
);
|
61
|
+
}
|
62
|
+
|
63
|
+
// Initialize members and subscriptions
|
64
|
+
this.fetchMembers()
|
65
|
+
.then(() => {
|
66
|
+
this.subscribeToMembers();
|
67
|
+
})
|
68
|
+
.catch((error) => {
|
69
|
+
log.error("Failed to initialize members", { error });
|
70
|
+
});
|
71
|
+
|
72
|
+
// Validate rate limit asynchronously
|
73
|
+
this.validateRateLimit(rateLimit).catch((error) => {
|
74
|
+
log.error("Failed to validate initial rate limit", { error });
|
75
|
+
});
|
43
76
|
}
|
44
77
|
|
45
78
|
/**
|
@@ -116,151 +149,313 @@ export class RLNBaseContract {
|
|
116
149
|
* @param newRateLimit The new rate limit to use
|
117
150
|
*/
|
118
151
|
public async setRateLimit(newRateLimit: number): Promise<void> {
|
119
|
-
this.validateRateLimit(newRateLimit);
|
152
|
+
await this.validateRateLimit(newRateLimit);
|
120
153
|
this.rateLimit = newRateLimit;
|
121
154
|
}
|
122
155
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
startIndex: number,
|
130
|
-
endIndex: number
|
131
|
-
): Promise<Member[]> {
|
132
|
-
try {
|
133
|
-
// Get all commitments in one call
|
134
|
-
const idCommitments =
|
135
|
-
await this.contract.getRateCommitmentsInRangeBoundsInclusive(
|
136
|
-
startIndex,
|
137
|
-
endIndex - 1 // -1 because getRateCommitmentsInRangeBoundsInclusive is inclusive
|
138
|
-
);
|
156
|
+
public get members(): Member[] {
|
157
|
+
const sortedMembers = Array.from(this._members.values()).sort(
|
158
|
+
(left, right) => left.index.toNumber() - right.index.toNumber()
|
159
|
+
);
|
160
|
+
return sortedMembers;
|
161
|
+
}
|
139
162
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
163
|
+
public async fetchMembers(options: FetchMembersOptions = {}): Promise<void> {
|
164
|
+
const registeredMemberEvents = await RLNBaseContract.queryFilter(
|
165
|
+
this.contract,
|
166
|
+
{
|
167
|
+
fromBlock: this.deployBlock,
|
168
|
+
...options,
|
169
|
+
membersFilter: this.membersFilter
|
170
|
+
}
|
171
|
+
);
|
172
|
+
const removedMemberEvents = await RLNBaseContract.queryFilter(
|
173
|
+
this.contract,
|
174
|
+
{
|
175
|
+
fromBlock: this.deployBlock,
|
176
|
+
...options,
|
177
|
+
membersFilter: this.membershipErasedFilter
|
178
|
+
}
|
179
|
+
);
|
180
|
+
const expiredMemberEvents = await RLNBaseContract.queryFilter(
|
181
|
+
this.contract,
|
182
|
+
{
|
183
|
+
fromBlock: this.deployBlock,
|
184
|
+
...options,
|
185
|
+
membersFilter: this.membersExpiredFilter
|
186
|
+
}
|
187
|
+
);
|
188
|
+
|
189
|
+
const events = [
|
190
|
+
...registeredMemberEvents,
|
191
|
+
...removedMemberEvents,
|
192
|
+
...expiredMemberEvents
|
193
|
+
];
|
194
|
+
this.processEvents(events);
|
195
|
+
}
|
196
|
+
|
197
|
+
public static async queryFilter(
|
198
|
+
contract: ethers.Contract,
|
199
|
+
options: CustomQueryOptions
|
200
|
+
): Promise<ethers.Event[]> {
|
201
|
+
const FETCH_CHUNK = 5;
|
202
|
+
const BLOCK_RANGE = 3000;
|
203
|
+
|
204
|
+
const {
|
205
|
+
fromBlock,
|
206
|
+
membersFilter,
|
207
|
+
fetchRange = BLOCK_RANGE,
|
208
|
+
fetchChunks = FETCH_CHUNK
|
209
|
+
} = options;
|
210
|
+
|
211
|
+
if (fromBlock === undefined) {
|
212
|
+
return contract.queryFilter(membersFilter);
|
213
|
+
}
|
214
|
+
|
215
|
+
if (!contract.provider) {
|
216
|
+
throw Error("No provider found on the contract.");
|
217
|
+
}
|
218
|
+
|
219
|
+
const toBlock = await contract.provider.getBlockNumber();
|
220
|
+
|
221
|
+
if (toBlock - fromBlock < fetchRange) {
|
222
|
+
return contract.queryFilter(membersFilter, fromBlock, toBlock);
|
223
|
+
}
|
224
|
+
|
225
|
+
const events: ethers.Event[][] = [];
|
226
|
+
const chunks = RLNBaseContract.splitToChunks(
|
227
|
+
fromBlock,
|
228
|
+
toBlock,
|
229
|
+
fetchRange
|
230
|
+
);
|
151
231
|
|
152
|
-
|
153
|
-
|
154
|
-
|
232
|
+
for (const portion of RLNBaseContract.takeN<[number, number]>(
|
233
|
+
chunks,
|
234
|
+
fetchChunks
|
235
|
+
)) {
|
236
|
+
const promises = portion.map(([left, right]) =>
|
237
|
+
RLNBaseContract.ignoreErrors(
|
238
|
+
contract.queryFilter(membersFilter, left, right),
|
239
|
+
[]
|
240
|
+
)
|
155
241
|
);
|
156
|
-
|
157
|
-
|
242
|
+
const fetchedEvents = await Promise.all(promises);
|
243
|
+
events.push(fetchedEvents.flatMap((v) => v));
|
244
|
+
}
|
245
|
+
|
246
|
+
return events.flatMap((v) => v);
|
247
|
+
}
|
248
|
+
|
249
|
+
public processEvents(events: ethers.Event[]): void {
|
250
|
+
const toRemoveTable = new Map<number, number[]>();
|
251
|
+
const toInsertTable = new Map<number, ethers.Event[]>();
|
252
|
+
|
253
|
+
events.forEach((evt) => {
|
254
|
+
if (!evt.args) {
|
255
|
+
return;
|
256
|
+
}
|
257
|
+
|
158
258
|
if (
|
159
|
-
|
160
|
-
|
259
|
+
evt.event === "MembershipErased" ||
|
260
|
+
evt.event === "MembershipExpired"
|
161
261
|
) {
|
162
|
-
|
163
|
-
|
164
|
-
)
|
262
|
+
let index = evt.args.index;
|
263
|
+
|
264
|
+
if (!index) {
|
265
|
+
return;
|
266
|
+
}
|
267
|
+
|
268
|
+
if (typeof index === "number" || typeof index === "string") {
|
269
|
+
index = ethers.BigNumber.from(index);
|
270
|
+
}
|
271
|
+
|
272
|
+
const toRemoveVal = toRemoveTable.get(evt.blockNumber);
|
273
|
+
if (toRemoveVal != undefined) {
|
274
|
+
toRemoveVal.push(index.toNumber());
|
275
|
+
toRemoveTable.set(evt.blockNumber, toRemoveVal);
|
276
|
+
} else {
|
277
|
+
toRemoveTable.set(evt.blockNumber, [index.toNumber()]);
|
278
|
+
}
|
279
|
+
} else if (evt.event === "MembershipRegistered") {
|
280
|
+
let eventsPerBlock = toInsertTable.get(evt.blockNumber);
|
281
|
+
if (eventsPerBlock == undefined) {
|
282
|
+
eventsPerBlock = [];
|
283
|
+
}
|
284
|
+
|
285
|
+
eventsPerBlock.push(evt);
|
286
|
+
toInsertTable.set(evt.blockNumber, eventsPerBlock);
|
165
287
|
}
|
166
|
-
|
288
|
+
});
|
289
|
+
}
|
290
|
+
|
291
|
+
public static splitToChunks(
|
292
|
+
from: number,
|
293
|
+
to: number,
|
294
|
+
step: number
|
295
|
+
): Array<[number, number]> {
|
296
|
+
const chunks: Array<[number, number]> = [];
|
297
|
+
|
298
|
+
let left = from;
|
299
|
+
while (left < to) {
|
300
|
+
const right = left + step < to ? left + step : to;
|
301
|
+
|
302
|
+
chunks.push([left, right] as [number, number]);
|
303
|
+
|
304
|
+
left = right;
|
167
305
|
}
|
306
|
+
|
307
|
+
return chunks;
|
168
308
|
}
|
169
309
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
310
|
+
public static *takeN<T>(array: T[], size: number): Iterable<T[]> {
|
311
|
+
let start = 0;
|
312
|
+
|
313
|
+
while (start < array.length) {
|
314
|
+
const portion = array.slice(start, start + size);
|
315
|
+
|
316
|
+
yield portion;
|
317
|
+
|
318
|
+
start += size;
|
319
|
+
}
|
176
320
|
}
|
177
321
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
private async getMemberIndex(
|
183
|
-
idCommitment: string
|
184
|
-
): Promise<ethers.BigNumber | null> {
|
322
|
+
public static async ignoreErrors<T>(
|
323
|
+
promise: Promise<T>,
|
324
|
+
defaultValue: T
|
325
|
+
): Promise<T> {
|
185
326
|
try {
|
186
|
-
|
187
|
-
|
188
|
-
|
327
|
+
return await promise;
|
328
|
+
} catch (err: unknown) {
|
329
|
+
if (err instanceof Error) {
|
330
|
+
log.info(`Ignoring an error during query: ${err.message}`);
|
331
|
+
} else {
|
332
|
+
log.info(`Ignoring an unknown error during query`);
|
189
333
|
}
|
190
|
-
|
191
|
-
const membershipInfo = await this.contract.memberships(idCommitment);
|
192
|
-
return ethers.BigNumber.from(membershipInfo.index);
|
193
|
-
} catch (error) {
|
194
|
-
log.error(`Error getting member index: ${(error as Error).message}`);
|
195
|
-
throw new InvalidMembershipError(idCommitment);
|
334
|
+
return defaultValue;
|
196
335
|
}
|
197
336
|
}
|
198
337
|
|
338
|
+
public subscribeToMembers(): void {
|
339
|
+
this.contract.on(
|
340
|
+
this.membersFilter,
|
341
|
+
(
|
342
|
+
_idCommitment: bigint,
|
343
|
+
_membershipRateLimit: ethers.BigNumber,
|
344
|
+
_index: ethers.BigNumber,
|
345
|
+
event: ethers.Event
|
346
|
+
) => {
|
347
|
+
this.processEvents([event]);
|
348
|
+
}
|
349
|
+
);
|
350
|
+
|
351
|
+
this.contract.on(
|
352
|
+
this.membershipErasedFilter,
|
353
|
+
(
|
354
|
+
_idCommitment: bigint,
|
355
|
+
_membershipRateLimit: ethers.BigNumber,
|
356
|
+
_index: ethers.BigNumber,
|
357
|
+
event: ethers.Event
|
358
|
+
) => {
|
359
|
+
this.processEvents([event]);
|
360
|
+
}
|
361
|
+
);
|
362
|
+
|
363
|
+
this.contract.on(
|
364
|
+
this.membersExpiredFilter,
|
365
|
+
(
|
366
|
+
_idCommitment: bigint,
|
367
|
+
_membershipRateLimit: ethers.BigNumber,
|
368
|
+
_index: ethers.BigNumber,
|
369
|
+
event: ethers.Event
|
370
|
+
) => {
|
371
|
+
this.processEvents([event]);
|
372
|
+
}
|
373
|
+
);
|
374
|
+
}
|
375
|
+
|
199
376
|
public async getMembershipInfo(
|
200
|
-
|
201
|
-
): Promise<MembershipInfo> {
|
377
|
+
idCommitmentBigInt: bigint
|
378
|
+
): Promise<MembershipInfo | undefined> {
|
202
379
|
try {
|
203
|
-
const
|
204
|
-
await this.contract.
|
380
|
+
const membershipData =
|
381
|
+
await this.contract.memberships(idCommitmentBigInt);
|
205
382
|
const currentBlock = await this.contract.provider.getBlockNumber();
|
383
|
+
console.log("membershipData", membershipData);
|
384
|
+
|
385
|
+
const [
|
386
|
+
depositAmount,
|
387
|
+
activeDuration,
|
388
|
+
gracePeriodStartTimestamp,
|
389
|
+
gracePeriodDuration,
|
390
|
+
rateLimit,
|
391
|
+
index,
|
392
|
+
holder,
|
393
|
+
token
|
394
|
+
] = membershipData;
|
395
|
+
|
396
|
+
const gracePeriodEnd = gracePeriodStartTimestamp.add(gracePeriodDuration);
|
206
397
|
|
207
398
|
let state: MembershipState;
|
208
|
-
if (currentBlock <
|
399
|
+
if (currentBlock < gracePeriodStartTimestamp.toNumber()) {
|
209
400
|
state = MembershipState.Active;
|
210
|
-
} else if (currentBlock <
|
401
|
+
} else if (currentBlock < gracePeriodEnd.toNumber()) {
|
211
402
|
state = MembershipState.GracePeriod;
|
212
403
|
} else {
|
213
404
|
state = MembershipState.Expired;
|
214
405
|
}
|
215
406
|
|
216
|
-
const index = await this.getMemberIndex(idCommitment);
|
217
|
-
if (index === null) {
|
218
|
-
throw new MembershipNotFoundError(idCommitment);
|
219
|
-
}
|
220
|
-
|
221
407
|
return {
|
222
408
|
index,
|
223
|
-
idCommitment,
|
224
|
-
rateLimit: rateLimit
|
225
|
-
startBlock:
|
226
|
-
endBlock:
|
227
|
-
state
|
409
|
+
idCommitment: idCommitmentBigInt.toString(),
|
410
|
+
rateLimit: Number(rateLimit),
|
411
|
+
startBlock: gracePeriodStartTimestamp.toNumber(),
|
412
|
+
endBlock: gracePeriodEnd.toNumber(),
|
413
|
+
state,
|
414
|
+
depositAmount,
|
415
|
+
activeDuration,
|
416
|
+
gracePeriodDuration,
|
417
|
+
holder,
|
418
|
+
token
|
228
419
|
};
|
229
420
|
} catch (error) {
|
230
|
-
|
231
|
-
|
232
|
-
}
|
233
|
-
log.error(`Error getting membership info: ${(error as Error).message}`);
|
234
|
-
throw new InvalidMembershipError(idCommitment);
|
421
|
+
console.error("Error in getMembershipInfo:", error);
|
422
|
+
return undefined;
|
235
423
|
}
|
236
424
|
}
|
237
425
|
|
238
|
-
public async extendMembership(
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
426
|
+
public async extendMembership(
|
427
|
+
idCommitmentBigInt: bigint
|
428
|
+
): Promise<ethers.ContractTransaction> {
|
429
|
+
const tx = await this.contract.extendMemberships([idCommitmentBigInt]);
|
430
|
+
await tx.wait();
|
431
|
+
return tx;
|
244
432
|
}
|
245
433
|
|
246
434
|
public async eraseMembership(
|
247
|
-
|
435
|
+
idCommitmentBigInt: bigint,
|
248
436
|
eraseFromMembershipSet: boolean = true
|
249
|
-
): Promise<
|
437
|
+
): Promise<ethers.ContractTransaction> {
|
250
438
|
const tx = await this.contract.eraseMemberships(
|
251
|
-
[
|
439
|
+
[idCommitmentBigInt],
|
252
440
|
eraseFromMembershipSet
|
253
441
|
);
|
254
|
-
await
|
255
|
-
|
256
|
-
|
257
|
-
|
442
|
+
await tx.wait();
|
443
|
+
return tx;
|
444
|
+
}
|
445
|
+
|
446
|
+
public async withdraw(token: string, from: string): Promise<void> {
|
447
|
+
try {
|
448
|
+
const tx = await this.contract.withdraw(token, from);
|
449
|
+
await tx.wait();
|
450
|
+
} catch (error) {
|
451
|
+
log.error(`Error in withdraw: ${(error as Error).message}`);
|
452
|
+
}
|
258
453
|
}
|
259
454
|
|
260
455
|
public async registerMembership(
|
261
|
-
|
456
|
+
idCommitmentBigInt: bigint,
|
262
457
|
rateLimit: number = DEFAULT_RATE_LIMIT
|
263
|
-
): Promise<
|
458
|
+
): Promise<ethers.ContractTransaction> {
|
264
459
|
if (
|
265
460
|
rateLimit < RATE_LIMIT_PARAMS.MIN_RATE ||
|
266
461
|
rateLimit > RATE_LIMIT_PARAMS.MAX_RATE
|
@@ -269,26 +464,7 @@ export class RLNBaseContract {
|
|
269
464
|
`Rate limit must be between ${RATE_LIMIT_PARAMS.MIN_RATE} and ${RATE_LIMIT_PARAMS.MAX_RATE}`
|
270
465
|
);
|
271
466
|
}
|
272
|
-
|
273
|
-
await this.confirmTransaction(tx, "MembershipRegistered", (event) => ({
|
274
|
-
idCommitment: event.args!.idCommitment,
|
275
|
-
membershipRateLimit: event.args!.membershipRateLimit,
|
276
|
-
index: event.args!.index
|
277
|
-
}));
|
278
|
-
}
|
279
|
-
|
280
|
-
public async withdraw(token: string, holder: string): Promise<void> {
|
281
|
-
try {
|
282
|
-
const tx = await this.contract.withdraw(token, { from: holder });
|
283
|
-
await this.confirmTransaction(tx, "TokenWithdrawn", (event) => ({
|
284
|
-
token: event.args!.token,
|
285
|
-
holder: event.args!.holder,
|
286
|
-
amount: event.args!.amount
|
287
|
-
}));
|
288
|
-
} catch (error) {
|
289
|
-
log.error(`Error in withdraw: ${(error as Error).message}`);
|
290
|
-
throw error;
|
291
|
-
}
|
467
|
+
return this.contract.register(idCommitmentBigInt, rateLimit, []);
|
292
468
|
}
|
293
469
|
|
294
470
|
public async registerWithIdentity(
|
@@ -301,20 +477,20 @@ export class RLNBaseContract {
|
|
301
477
|
|
302
478
|
// Check if the ID commitment is already registered
|
303
479
|
const existingIndex = await this.getMemberIndex(
|
304
|
-
identity.IDCommitmentBigInt
|
480
|
+
identity.IDCommitmentBigInt
|
305
481
|
);
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
identity.IDCommitmentBigInt.toString(),
|
310
|
-
existingIndex.toString()
|
482
|
+
if (existingIndex) {
|
483
|
+
throw new Error(
|
484
|
+
`ID commitment is already registered with index ${existingIndex}`
|
311
485
|
);
|
312
486
|
}
|
313
487
|
|
314
488
|
// Check if there's enough remaining rate limit
|
315
489
|
const remainingRateLimit = await this.getRemainingTotalRateLimit();
|
316
490
|
if (remainingRateLimit < this.rateLimit) {
|
317
|
-
throw new
|
491
|
+
throw new Error(
|
492
|
+
`Not enough remaining rate limit. Requested: ${this.rateLimit}, Available: ${remainingRateLimit}`
|
493
|
+
);
|
318
494
|
}
|
319
495
|
|
320
496
|
const estimatedGas = await this.contract.estimateGas.register(
|
@@ -324,23 +500,37 @@ export class RLNBaseContract {
|
|
324
500
|
);
|
325
501
|
const gasLimit = estimatedGas.add(10000);
|
326
502
|
|
327
|
-
const
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
503
|
+
const txRegisterResponse: ethers.ContractTransaction =
|
504
|
+
await this.contract.register(
|
505
|
+
identity.IDCommitmentBigInt,
|
506
|
+
this.rateLimit,
|
507
|
+
[],
|
508
|
+
{ gasLimit }
|
509
|
+
);
|
510
|
+
|
511
|
+
const txRegisterReceipt = await txRegisterResponse.wait();
|
333
512
|
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
index: event.args!.index
|
341
|
-
})
|
513
|
+
if (txRegisterReceipt.status === 0) {
|
514
|
+
throw new Error("Transaction failed on-chain");
|
515
|
+
}
|
516
|
+
|
517
|
+
const memberRegistered = txRegisterReceipt.events?.find(
|
518
|
+
(event) => event.event === "MembershipRegistered"
|
342
519
|
);
|
343
520
|
|
521
|
+
if (!memberRegistered || !memberRegistered.args) {
|
522
|
+
log.error(
|
523
|
+
"Failed to register membership: No MembershipRegistered event found"
|
524
|
+
);
|
525
|
+
return undefined;
|
526
|
+
}
|
527
|
+
|
528
|
+
const decodedData: MembershipRegisteredEvent = {
|
529
|
+
idCommitment: memberRegistered.args.idCommitment,
|
530
|
+
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
531
|
+
index: memberRegistered.args.index
|
532
|
+
};
|
533
|
+
|
344
534
|
log.info(
|
345
535
|
`Successfully registered membership with index ${decodedData.index} ` +
|
346
536
|
`and rate limit ${decodedData.membershipRateLimit}`
|
@@ -348,55 +538,44 @@ export class RLNBaseContract {
|
|
348
538
|
|
349
539
|
const network = await this.contract.provider.getNetwork();
|
350
540
|
const address = this.contract.address;
|
351
|
-
const membershipId = decodedData.index
|
541
|
+
const membershipId = Number(decodedData.index);
|
352
542
|
|
353
543
|
return {
|
354
544
|
identity,
|
355
545
|
membership: {
|
356
546
|
address,
|
357
|
-
treeIndex:
|
547
|
+
treeIndex: membershipId,
|
358
548
|
chainId: network.chainId.toString(),
|
359
549
|
rateLimit: decodedData.membershipRateLimit.toNumber()
|
360
550
|
}
|
361
551
|
};
|
362
552
|
} catch (error) {
|
363
|
-
if (error instanceof RLNContractError) {
|
364
|
-
throw error;
|
365
|
-
}
|
366
|
-
|
367
553
|
if (error instanceof Error) {
|
368
554
|
const errorMessage = error.message;
|
369
555
|
log.error("registerWithIdentity - error message:", errorMessage);
|
370
556
|
log.error("registerWithIdentity - error stack:", error.stack);
|
371
557
|
|
372
|
-
//
|
558
|
+
// Try to extract more specific error information
|
373
559
|
if (errorMessage.includes("CannotExceedMaxTotalRateLimit")) {
|
374
|
-
throw new
|
375
|
-
|
376
|
-
await this.getRemainingTotalRateLimit()
|
560
|
+
throw new Error(
|
561
|
+
"Registration failed: Cannot exceed maximum total rate limit"
|
377
562
|
);
|
378
563
|
} else if (errorMessage.includes("InvalidIdCommitment")) {
|
379
|
-
throw new
|
380
|
-
identity.IDCommitmentBigInt.toString()
|
381
|
-
);
|
564
|
+
throw new Error("Registration failed: Invalid ID commitment");
|
382
565
|
} else if (errorMessage.includes("InvalidMembershipRateLimit")) {
|
383
|
-
throw new
|
384
|
-
this.rateLimit,
|
385
|
-
RATE_LIMIT_PARAMS.MIN_RATE,
|
386
|
-
RATE_LIMIT_PARAMS.MAX_RATE
|
387
|
-
);
|
566
|
+
throw new Error("Registration failed: Invalid membership rate limit");
|
388
567
|
} else if (errorMessage.includes("execution reverted")) {
|
389
|
-
throw new
|
568
|
+
throw new Error(
|
390
569
|
"Contract execution reverted. Check contract requirements."
|
391
570
|
);
|
571
|
+
} else {
|
572
|
+
throw new Error(`Error in registerWithIdentity: ${errorMessage}`);
|
392
573
|
}
|
393
|
-
|
394
|
-
throw new
|
395
|
-
|
396
|
-
);
|
574
|
+
} else {
|
575
|
+
throw new Error("Unknown error in registerWithIdentity", {
|
576
|
+
cause: error
|
577
|
+
});
|
397
578
|
}
|
398
|
-
|
399
|
-
throw new RLNContractError("Unknown error in registerWithIdentity");
|
400
579
|
}
|
401
580
|
}
|
402
581
|
|
@@ -416,27 +595,36 @@ export class RLNBaseContract {
|
|
416
595
|
`Registering identity with permit and rate limit: ${this.rateLimit} messages/epoch`
|
417
596
|
);
|
418
597
|
|
419
|
-
const
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
598
|
+
const txRegisterResponse: ethers.ContractTransaction =
|
599
|
+
await this.contract.registerWithPermit(
|
600
|
+
permit.owner,
|
601
|
+
permit.deadline,
|
602
|
+
permit.v,
|
603
|
+
permit.r,
|
604
|
+
permit.s,
|
605
|
+
identity.IDCommitmentBigInt,
|
606
|
+
this.rateLimit,
|
607
|
+
idCommitmentsToErase.map((id) => ethers.BigNumber.from(id))
|
608
|
+
);
|
609
|
+
const txRegisterReceipt = await txRegisterResponse.wait();
|
429
610
|
|
430
|
-
const
|
431
|
-
|
432
|
-
"MembershipRegistered",
|
433
|
-
(event): MembershipRegisteredEvent => ({
|
434
|
-
idCommitment: event.args!.idCommitment,
|
435
|
-
membershipRateLimit: event.args!.membershipRateLimit,
|
436
|
-
index: event.args!.index
|
437
|
-
})
|
611
|
+
const memberRegistered = txRegisterReceipt.events?.find(
|
612
|
+
(event) => event.event === "MembershipRegistered"
|
438
613
|
);
|
439
614
|
|
615
|
+
if (!memberRegistered || !memberRegistered.args) {
|
616
|
+
log.error(
|
617
|
+
"Failed to register membership with permit: No MembershipRegistered event found"
|
618
|
+
);
|
619
|
+
return undefined;
|
620
|
+
}
|
621
|
+
|
622
|
+
const decodedData: MembershipRegisteredEvent = {
|
623
|
+
idCommitment: memberRegistered.args.idCommitment,
|
624
|
+
membershipRateLimit: memberRegistered.args.membershipRateLimit,
|
625
|
+
index: memberRegistered.args.index
|
626
|
+
};
|
627
|
+
|
440
628
|
log.info(
|
441
629
|
`Successfully registered membership with permit. Index: ${decodedData.index}, ` +
|
442
630
|
`Rate limit: ${decodedData.membershipRateLimit}, Erased ${idCommitmentsToErase.length} commitments`
|
@@ -444,13 +632,13 @@ export class RLNBaseContract {
|
|
444
632
|
|
445
633
|
const network = await this.contract.provider.getNetwork();
|
446
634
|
const address = this.contract.address;
|
447
|
-
const membershipId = decodedData.index
|
635
|
+
const membershipId = Number(decodedData.index);
|
448
636
|
|
449
637
|
return {
|
450
638
|
identity,
|
451
639
|
membership: {
|
452
640
|
address,
|
453
|
-
treeIndex:
|
641
|
+
treeIndex: membershipId,
|
454
642
|
chainId: network.chainId.toString(),
|
455
643
|
rateLimit: decodedData.membershipRateLimit.toNumber()
|
456
644
|
}
|
@@ -459,7 +647,7 @@ export class RLNBaseContract {
|
|
459
647
|
log.error(
|
460
648
|
`Error in registerWithPermitAndErase: ${(error as Error).message}`
|
461
649
|
);
|
462
|
-
|
650
|
+
return undefined;
|
463
651
|
}
|
464
652
|
}
|
465
653
|
|
@@ -467,34 +655,57 @@ export class RLNBaseContract {
|
|
467
655
|
* Validates that the rate limit is within the allowed range
|
468
656
|
* @throws Error if the rate limit is outside the allowed range
|
469
657
|
*/
|
470
|
-
private validateRateLimit(rateLimit: number): void {
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
)
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
658
|
+
private async validateRateLimit(rateLimit: number): Promise<void> {
|
659
|
+
const [minRate, maxRate] = await Promise.all([
|
660
|
+
this.contract.minMembershipRateLimit(),
|
661
|
+
this.contract.maxMembershipRateLimit()
|
662
|
+
]);
|
663
|
+
|
664
|
+
const minRateNum = ethers.BigNumber.from(minRate).toNumber();
|
665
|
+
const maxRateNum = ethers.BigNumber.from(maxRate).toNumber();
|
666
|
+
|
667
|
+
if (rateLimit < minRateNum || rateLimit > maxRateNum) {
|
668
|
+
throw new Error(
|
669
|
+
`Rate limit must be between ${minRateNum} and ${maxRateNum} messages per epoch`
|
479
670
|
);
|
480
671
|
}
|
481
672
|
}
|
482
673
|
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
transform: (event: ethers.Event) => T
|
490
|
-
): Promise<T> {
|
491
|
-
const receipt = await tx.wait();
|
492
|
-
const event = receipt.events?.find((e) => e.event === expectedEvent);
|
674
|
+
private get membersFilter(): ethers.EventFilter {
|
675
|
+
if (!this._membersFilter) {
|
676
|
+
throw Error("Members filter was not initialized.");
|
677
|
+
}
|
678
|
+
return this._membersFilter;
|
679
|
+
}
|
493
680
|
|
494
|
-
|
495
|
-
|
681
|
+
private get membershipErasedFilter(): ethers.EventFilter {
|
682
|
+
if (!this._membershipErasedFilter) {
|
683
|
+
throw Error("MembershipErased filter was not initialized.");
|
496
684
|
}
|
685
|
+
return this._membershipErasedFilter;
|
686
|
+
}
|
687
|
+
|
688
|
+
private get membersExpiredFilter(): ethers.EventFilter {
|
689
|
+
if (!this._membersExpiredFilter) {
|
690
|
+
throw Error("MembersExpired filter was not initialized.");
|
691
|
+
}
|
692
|
+
return this._membersExpiredFilter;
|
693
|
+
}
|
694
|
+
|
695
|
+
private async getMemberIndex(
|
696
|
+
idCommitmentBigInt: bigint
|
697
|
+
): Promise<ethers.BigNumber | undefined> {
|
698
|
+
try {
|
699
|
+
const events = await this.contract.queryFilter(
|
700
|
+
this.contract.filters.MembershipRegistered(idCommitmentBigInt)
|
701
|
+
);
|
702
|
+
if (events.length === 0) return undefined;
|
497
703
|
|
498
|
-
|
704
|
+
// Get the most recent registration event
|
705
|
+
const event = events[events.length - 1];
|
706
|
+
return event.args?.index;
|
707
|
+
} catch (error) {
|
708
|
+
return undefined;
|
709
|
+
}
|
499
710
|
}
|
500
711
|
}
|