@boostxyz/sdk 0.0.0-alpha.10 → 0.0.0-alpha.12

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.
Files changed (100) hide show
  1. package/dist/Actions/Action.js +2 -2
  2. package/dist/Actions/EventAction.cjs +1 -1
  3. package/dist/Actions/EventAction.cjs.map +1 -1
  4. package/dist/Actions/EventAction.d.ts +115 -31
  5. package/dist/Actions/EventAction.d.ts.map +1 -1
  6. package/dist/Actions/EventAction.js +306 -122
  7. package/dist/Actions/EventAction.js.map +1 -1
  8. package/dist/AllowLists/AllowList.cjs +1 -1
  9. package/dist/AllowLists/AllowList.cjs.map +1 -1
  10. package/dist/AllowLists/AllowList.d.ts +6 -4
  11. package/dist/AllowLists/AllowList.d.ts.map +1 -1
  12. package/dist/AllowLists/AllowList.js +45 -23
  13. package/dist/AllowLists/AllowList.js.map +1 -1
  14. package/dist/AllowLists/OpenAllowList.d.ts +240 -0
  15. package/dist/AllowLists/OpenAllowList.d.ts.map +1 -0
  16. package/dist/AllowLists/SimpleAllowList.js +1 -1
  17. package/dist/AllowLists/SimpleDenyList.cjs +1 -1
  18. package/dist/AllowLists/SimpleDenyList.cjs.map +1 -1
  19. package/dist/AllowLists/SimpleDenyList.d.ts +2 -2
  20. package/dist/AllowLists/SimpleDenyList.d.ts.map +1 -1
  21. package/dist/AllowLists/SimpleDenyList.js +9 -111
  22. package/dist/AllowLists/SimpleDenyList.js.map +1 -1
  23. package/dist/Auth/PassthroughAuth.js +1 -1
  24. package/dist/BoostCore.cjs +2 -2
  25. package/dist/BoostCore.cjs.map +1 -1
  26. package/dist/BoostCore.d.ts +14 -1
  27. package/dist/BoostCore.d.ts.map +1 -1
  28. package/dist/BoostCore.js +169 -151
  29. package/dist/BoostCore.js.map +1 -1
  30. package/dist/BoostRegistry.js +1 -1
  31. package/dist/Budgets/Budget.js +2 -2
  32. package/dist/Budgets/ManagedBudget.cjs +1 -1
  33. package/dist/Budgets/ManagedBudget.cjs.map +1 -1
  34. package/dist/Budgets/ManagedBudget.d.ts +29 -27
  35. package/dist/Budgets/ManagedBudget.d.ts.map +1 -1
  36. package/dist/Budgets/ManagedBudget.js +78 -77
  37. package/dist/Budgets/ManagedBudget.js.map +1 -1
  38. package/dist/Budgets/SimpleBudget.d.ts +6 -6
  39. package/dist/Budgets/SimpleBudget.d.ts.map +1 -1
  40. package/dist/Budgets/VestingBudget.d.ts +6 -6
  41. package/dist/Budgets/VestingBudget.d.ts.map +1 -1
  42. package/dist/Deployable/DeployableTarget.cjs.map +1 -1
  43. package/dist/Deployable/DeployableTarget.d.ts +1 -1
  44. package/dist/Deployable/DeployableTarget.d.ts.map +1 -1
  45. package/dist/Deployable/DeployableTarget.js +1 -1
  46. package/dist/Deployable/DeployableTarget.js.map +1 -1
  47. package/dist/Incentives/AllowListIncentive.cjs +1 -1
  48. package/dist/Incentives/AllowListIncentive.cjs.map +1 -1
  49. package/dist/Incentives/AllowListIncentive.js +2 -2
  50. package/dist/Incentives/CGDAIncentive.js +1 -1
  51. package/dist/Incentives/ERC20Incentive.js +1 -1
  52. package/dist/Incentives/ERC20VariableIncentive.d.ts +5 -5
  53. package/dist/Incentives/Incentive.cjs.map +1 -1
  54. package/dist/Incentives/Incentive.js +16 -16
  55. package/dist/Incentives/Incentive.js.map +1 -1
  56. package/dist/Incentives/PointsIncentive.js +1 -1
  57. package/dist/SimpleDenyList-CqT0BMP7.cjs +2 -0
  58. package/dist/SimpleDenyList-CqT0BMP7.cjs.map +1 -0
  59. package/dist/SimpleDenyList-IJ9Ipya7.js +112 -0
  60. package/dist/SimpleDenyList-IJ9Ipya7.js.map +1 -0
  61. package/dist/Validators/SignerValidator.js +1 -1
  62. package/dist/Validators/Validator.js +2 -2
  63. package/dist/{componentInterfaces-DYkaxBda.js → componentInterfaces-Cmg8tUxq.js} +5 -5
  64. package/dist/componentInterfaces-Cmg8tUxq.js.map +1 -0
  65. package/dist/errors.cjs +1 -1
  66. package/dist/errors.cjs.map +1 -1
  67. package/dist/errors.d.ts +116 -20
  68. package/dist/errors.d.ts.map +1 -1
  69. package/dist/errors.js +106 -52
  70. package/dist/errors.js.map +1 -1
  71. package/dist/{generated-BDeDiaCK.js → generated-HGddZXHJ.js} +21 -21
  72. package/dist/{generated-BDeDiaCK.js.map → generated-HGddZXHJ.js.map} +1 -1
  73. package/dist/index.cjs +1 -1
  74. package/dist/index.js +95 -91
  75. package/package.json +11 -7
  76. package/src/Actions/Action.test.ts +9 -5
  77. package/src/Actions/ContractAction.test.ts +3 -3
  78. package/src/Actions/ERC721MintAction.test.ts +1 -1
  79. package/src/Actions/EventAction.test.ts +527 -99
  80. package/src/Actions/EventAction.ts +296 -58
  81. package/src/AllowLists/AllowList.test.ts +2 -2
  82. package/src/AllowLists/AllowList.ts +5 -3
  83. package/src/AllowLists/OpenAllowList.test.ts +40 -0
  84. package/src/AllowLists/OpenAllowList.ts +45 -0
  85. package/src/AllowLists/SimpleAllowList.test.ts +1 -1
  86. package/src/AllowLists/SimpleDenyList.test.ts +1 -1
  87. package/src/AllowLists/SimpleDenyList.ts +6 -9
  88. package/src/BoostCore.ts +19 -0
  89. package/src/Budgets/ManagedBudget.test.ts +70 -7
  90. package/src/Budgets/ManagedBudget.ts +42 -31
  91. package/src/Budgets/SimpleBudget.test.ts +3 -3
  92. package/src/Budgets/SimpleBudget.ts +6 -6
  93. package/src/Budgets/VestingBudget.test.ts +3 -3
  94. package/src/Budgets/VestingBudget.ts +6 -6
  95. package/src/Deployable/DeployableTarget.ts +1 -1
  96. package/src/Incentives/AllowListIncentive.test.ts +1 -1
  97. package/src/Incentives/ERC20VariableIncentive.ts +5 -5
  98. package/src/Validators/SignerValidator.test.ts +1 -1
  99. package/src/errors.ts +160 -20
  100. package/dist/componentInterfaces-DYkaxBda.js.map +0 -1
@@ -7,15 +7,24 @@ import {
7
7
  } from '@boostxyz/evm';
8
8
  import { bytecode } from '@boostxyz/evm/artifacts/contracts/actions/EventAction.sol/EventAction.json';
9
9
  import events from '@boostxyz/signatures/events';
10
+ import functions from '@boostxyz/signatures/functions';
10
11
  import {
11
12
  type Abi,
12
13
  type AbiEvent,
14
+ type AbiFunction,
13
15
  type Address,
14
16
  type ContractEventName,
17
+ type ContractFunctionName,
18
+ type GetLogsReturnType,
19
+ type GetTransactionParameters,
15
20
  type Hex,
16
21
  type Log,
22
+ type PublicClient,
23
+ decodeFunctionData,
17
24
  encodeAbiParameters,
25
+ fromHex,
18
26
  isAddressEqual,
27
+ trim,
19
28
  } from 'viem';
20
29
  import { getLogs } from 'viem/actions';
21
30
  import type {
@@ -24,11 +33,14 @@ import type {
24
33
  } from '../Deployable/Deployable';
25
34
  import { DeployableTarget } from '../Deployable/DeployableTarget';
26
35
  import {
36
+ DecodedArgsMalformedError,
27
37
  FieldValueNotComparableError,
28
38
  FieldValueUndefinedError,
39
+ FunctionDataDecodeError,
29
40
  InvalidNumericalCriteriaError,
30
41
  NoEventActionStepsProvidedError,
31
42
  TooManyEventActionStepsProvidedError,
43
+ UnparseableAbiParamError,
32
44
  UnrecognizedFilterTypeError,
33
45
  } from '../errors';
34
46
  import {
@@ -55,6 +67,7 @@ export enum FilterType {
55
67
  GREATER_THAN = 2,
56
68
  LESS_THAN = 3,
57
69
  CONTAINS = 4,
70
+ REGEX = 5,
58
71
  }
59
72
 
60
73
  /**
@@ -198,6 +211,41 @@ export interface ActionStep {
198
211
  */
199
212
  actionParameter: Criteria;
200
213
  }
214
+
215
+ /**
216
+ * Parameters for validating an event step.
217
+ *
218
+ * This type omits the 'address' field from GetLogsParams and adds optional fields
219
+ * for logs and known events.
220
+ *
221
+ * @typedef {Object} ValidateEventStepParams
222
+ * @property {Log[]} [logs]
223
+ * @property {Record<Hex, AbiEvent>} [knownEvents]
224
+ * @property {number} [fromBlock]
225
+ * @property {number} [toBlock]
226
+ * @property {Hex} [blockHash]
227
+ * @property {Abi} abi
228
+ * @property {ContractEventName<Abi>} eventName
229
+ */
230
+ export type ValidateEventStepParams = Omit<
231
+ GetLogsParams<Abi, ContractEventName<Abi>> & {
232
+ logs?: Log[];
233
+ knownEvents?: Record<Hex, AbiEvent>;
234
+ },
235
+ 'address'
236
+ >;
237
+
238
+ /**
239
+ * Parameters for validating a function step.
240
+ *
241
+ * This type includes all parameters required to get a transaction.
242
+ *
243
+ * @typedef {Object} ValidateFunctionStepParams
244
+ * @property {Hex} hash
245
+ * @property {number} [chainId]
246
+ */
247
+ export type ValidateFunctionStepParams = GetTransactionParameters;
248
+
201
249
  /**
202
250
  * You can either supply a simplified version of the payload, or one that explicitly declares action steps.
203
251
  *
@@ -280,6 +328,26 @@ export interface EventActionPayloadRaw {
280
328
  actionStepFour: ActionStep;
281
329
  }
282
330
 
331
+ /**
332
+ * Array of event logs to pass into TxParams
333
+ * @export
334
+ * @typedef {EventLogs}
335
+ */
336
+ export type EventLogs = GetLogsReturnType<AbiEvent, AbiEvent[], true>;
337
+
338
+ /**
339
+ * Getter params from the event action contract
340
+ *
341
+ * @export
342
+ * @typedef {ReadEventActionParams}
343
+ * @param {fnName} fnName - The getter function name
344
+ */
345
+ export type ReadEventActionParams<
346
+ fnName extends ContractFunctionName<typeof eventActionAbi, 'pure' | 'view'>,
347
+ > = ReadParams<typeof eventActionAbi, fnName>;
348
+
349
+ type TxParams = ValidateEventStepParams | ValidateFunctionStepParams;
350
+
283
351
  /**
284
352
  * A generic event action
285
353
  *
@@ -323,12 +391,12 @@ export class EventAction extends DeployableTarget<
323
391
  * @public
324
392
  * @async
325
393
  * @param {number} index The index of the action event to retrieve
326
- * @param {?ReadParams<typeof eventActionAbi, 'getActionStep'>} [params]
394
+ * @param {?ReadEventActionParams<'getActionStep'>} [params]
327
395
  * @returns {Promise<ActionStep>}
328
396
  */
329
397
  public async getActionStep(
330
398
  index: number,
331
- params?: ReadParams<typeof eventActionAbi, 'getActionStep'>,
399
+ params?: ReadEventActionParams<'getActionStep'>,
332
400
  ) {
333
401
  const steps = await this.getActionSteps(params);
334
402
  return steps.at(index);
@@ -339,11 +407,11 @@ export class EventAction extends DeployableTarget<
339
407
  *
340
408
  * @public
341
409
  * @async
342
- * @param {?ReadParams<typeof eventActionAbi, 'getActionSteps'>} [params]
410
+ * @param {?ReadEventActionParams<'getActionSteps'>} [params]
343
411
  * @returns {Promise<ActionStep[]>}
344
412
  */
345
413
  public async getActionSteps(
346
- params?: ReadParams<typeof eventActionAbi, 'getActionSteps'>,
414
+ params?: ReadEventActionParams<'getActionSteps'>,
347
415
  ) {
348
416
  const steps = (await readEventActionGetActionSteps(this._config, {
349
417
  address: this.assertValidAddress(),
@@ -359,11 +427,11 @@ export class EventAction extends DeployableTarget<
359
427
  *
360
428
  * @public
361
429
  * @async
362
- * @param {?ReadParams<typeof eventActionAbi, 'getActionStepsCount'>} [params]
430
+ * @param {?ReadEventActionParams<'getActionStepsCount'>} [params]
363
431
  * @returns {Promise<bigint>}
364
432
  */
365
433
  public async getActionStepsCount(
366
- params?: ReadParams<typeof eventActionAbi, 'getActionStepsCount'>,
434
+ params?: ReadEventActionParams<'getActionStepsCount'>,
367
435
  ) {
368
436
  const steps = await this.getActionSteps(params);
369
437
  return steps.length;
@@ -374,17 +442,16 @@ export class EventAction extends DeployableTarget<
374
442
  *
375
443
  * @public
376
444
  * @async
377
- * @param {?ReadParams<typeof eventActionAbi, 'getActionClaimant'>} [params]
445
+ * @param {?ReadEventActionParams<'getActionClaimant'>} [params]
378
446
  * @returns {Promise<ActionClaimant>}
379
447
  */
380
448
  public async getActionClaimant(
381
- params?: ReadParams<typeof eventActionAbi, 'getActionClaimant'>,
449
+ params?: ReadEventActionParams<'getActionClaimant'>,
382
450
  ) {
383
451
  const result = (await readEventActionGetActionClaimant(this._config, {
384
452
  address: this.assertValidAddress(),
385
453
  ...this.optionallyAttachAccount(),
386
- // biome-ignore lint/suspicious/noExplicitAny: Accept any shape of valid wagmi/viem parameters, wagmi does the same thing internally
387
- ...(params as any),
454
+ ...params,
388
455
  })) as RawActionClaimant;
389
456
  return _fromRawActionStep(result);
390
457
  }
@@ -435,21 +502,11 @@ export class EventAction extends DeployableTarget<
435
502
  *
436
503
  * @public
437
504
  * @async
438
- * @param {?ReadParams<typeof eventActionAbi, 'getActionSteps'> &
439
- * GetLogsParams<Abi, ContractEventName<Abi>> & {
440
- * knownEvents?: Record<Hex, AbiEvent>;
441
- * logs?: Log[];
442
- * }} [params]
505
+ * @param {?TxParams} [params]
443
506
  * @returns {Promise<boolean>}
444
507
  */
445
- public async validateActionSteps(
446
- params?: ReadParams<typeof eventActionAbi, 'getActionSteps'> &
447
- GetLogsParams<Abi, ContractEventName<Abi>> & {
448
- knownEvents?: Record<Hex, AbiEvent>;
449
- logs?: Log[];
450
- },
451
- ) {
452
- const actionSteps = await this.getActionSteps(params);
508
+ public async validateActionSteps(params?: TxParams) {
509
+ const actionSteps = await this.getActionSteps();
453
510
  for (const actionStep of actionSteps) {
454
511
  if (!(await this.isActionStepValid(actionStep, params))) {
455
512
  return false;
@@ -459,24 +516,45 @@ export class EventAction extends DeployableTarget<
459
516
  }
460
517
 
461
518
  /**
462
- * Validates a single action step with a given criteria against logs.
463
- * If logs are provided in the optional `params` argument, then those logs will be used instead of fetched with the configured client.
519
+ * Validates a single action step with a given criteria against logs or function calls.
520
+ * If logs are provided in the optional `params` argument, then those logs will be used instead of being fetched with the configured client.
521
+ * For functions a hash is required.
464
522
  *
465
523
  * @public
466
524
  * @async
467
- * @param {ActionStep} actionStep
468
- * @param {?GetLogsParams<Abi, ContractEventName<Abi>> & {
469
- * knownEvents?: Record<Hex, AbiEvent>;
470
- * logs?: Log[];
471
- * }} [params]
525
+ * @param {ActionStep} actionStep - The action step to validate. Can be a function of event step.
526
+ * @param {?TxParams & { chainId?: number }} [params] - Additional parameters for validation, including known events, logs, and chain ID.
472
527
  * @returns {Promise<boolean>}
473
528
  */
474
529
  public async isActionStepValid(
475
530
  actionStep: ActionStep,
476
- params?: GetLogsParams<Abi, ContractEventName<Abi>> & {
477
- knownEvents?: Record<Hex, AbiEvent>;
478
- logs?: Log[];
479
- },
531
+ params?: TxParams & { chainId?: number },
532
+ ) {
533
+ if (actionStep.signatureType === SignatureType.EVENT) {
534
+ return await this.isActionEventValid(actionStep, params);
535
+ }
536
+ if (actionStep.signatureType === SignatureType.FUNC) {
537
+ return await this.isActionFunctionValid(
538
+ actionStep,
539
+ params as ValidateFunctionStepParams,
540
+ );
541
+ }
542
+ return false;
543
+ }
544
+
545
+ /**
546
+ * Validates a single action event with a given criteria against logs.
547
+ * If logs are provided in the optional `params` argument, then those logs will be used instead of being fetched with the configured client.
548
+ *
549
+ * @public
550
+ * @async
551
+ * @param {ActionStep} actionStep - The action step containing the event to validate.
552
+ * @param {?ValidateEventStepParams & { chainId?: number }} [params] - Additional parameters for validation, including known events, logs, and chain ID.
553
+ * @returns {Promise<boolean>} Resolves to true if the action event is valid, throws if input is invalid, otherwise false.
554
+ */
555
+ public async isActionEventValid(
556
+ actionStep: ActionStep,
557
+ params?: ValidateEventStepParams & { chainId?: number },
480
558
  ) {
481
559
  const criteria = actionStep.actionParameter;
482
560
  const signature = actionStep.signature;
@@ -487,9 +565,18 @@ export class EventAction extends DeployableTarget<
487
565
  } else {
488
566
  event = (events.abi as Record<Hex, AbiEvent>)[signature] as AbiEvent;
489
567
  }
568
+
490
569
  if (!event) {
491
570
  throw new Error(`No known ABI for given event signature: ${signature}`);
492
571
  }
572
+
573
+ if (this.isArraylikeIndexed(actionStep, event)) {
574
+ // If the field is indexed, we can't filter on it
575
+ throw new UnparseableAbiParamError(
576
+ actionStep.actionParameter.fieldIndex,
577
+ event,
578
+ );
579
+ }
493
580
  const targetContract = actionStep.targetContract;
494
581
  // Get all logs matching the event signature from the target contract
495
582
  const logs =
@@ -502,71 +589,211 @@ export class EventAction extends DeployableTarget<
502
589
  }));
503
590
  if (!logs.length) return false;
504
591
  for (let log of logs) {
505
- if (!this.validateLogAgainstCriteria(criteria, log)) {
592
+ if (!this.validateLogAgainstCriteria(criteria, log as EventLogs[0])) {
506
593
  return false;
507
594
  }
508
595
  }
509
596
  return true;
510
597
  }
598
+ /**
599
+ * Validates a single action function with a given criteria against the transaction input.
600
+ * If a transaction hash is provided in the optional `params` argument, then the transaction
601
+ * will be fetched and decoded using the configured client.
602
+ *
603
+ * @public
604
+ * @async
605
+ * @param {ActionStep} actionStep - The action step containing the function to validate.
606
+ * @param {?ValidateFunctionStepParams & { chainId?: number }} [params] - Additional parameters for validation, including known events, transaction hash, and chain ID.
607
+ * @returns {Promise<boolean>} Resolves to true if the action function is valid, throws if the inputs are invalid, otherwise false.
608
+ */
609
+ public async isActionFunctionValid(
610
+ actionStep: ActionStep,
611
+ params?: ValidateFunctionStepParams & { chainId?: number },
612
+ ) {
613
+ const criteria = actionStep.actionParameter;
614
+ const signature = trim(actionStep.signature);
615
+ if (!params || !params?.hash) {
616
+ // Should we return false in this case?
617
+ throw new Error('Hash is required for function validation');
618
+ }
619
+ const client = this._config.getClient({
620
+ chainId: params?.chainId,
621
+ }) as PublicClient;
622
+ // Fetch the transaction receipt and decode the function input using `viem` utilities
623
+ const transaction = await client.getTransaction({ hash: params.hash });
624
+ const func = (functions.abi as Record<Hex, AbiFunction>)[
625
+ signature
626
+ ] as AbiFunction;
627
+
628
+ if (!func) {
629
+ throw new Error(
630
+ `No known ABI for given function signature: ${signature}`,
631
+ );
632
+ }
633
+ let decodedData;
634
+ try {
635
+ decodedData = decodeFunctionData({
636
+ abi: [func],
637
+ data: transaction.input,
638
+ });
639
+ } catch (e) {
640
+ throw new FunctionDataDecodeError([func], e as Error);
641
+ }
642
+
643
+ // Validate the criteria against decoded arguments using fieldIndex
644
+ const decodedArgs = decodedData.args;
511
645
 
646
+ if (!decodedArgs || !decodedData) return false;
647
+
648
+ if (
649
+ !this.validateFunctionAgainstCriteria(
650
+ criteria,
651
+ decodedArgs as (string | bigint)[],
652
+ )
653
+ ) {
654
+ return false;
655
+ }
656
+
657
+ return true;
658
+ }
512
659
  /**
513
- * Validates a {@link Log} against a given criteria.
660
+ * Validates a field against a given criteria.
514
661
  *
515
662
  * @param {Criteria} criteria - The criteria to validate against.
516
- * @param {Log} log - The Viem event log.
517
- * @returns {boolean} - Returns true if the log passes the criteria, false otherwise.
663
+ * @param {string | bigint} fieldValue - The field value to validate.
664
+ * @returns {Promise<boolean>} - Returns true if the field passes the criteria, false otherwise.
518
665
  */
519
- public validateLogAgainstCriteria(criteria: Criteria, log: Log) {
520
- const fieldValue = log.topics.at(criteria.fieldIndex);
521
- if (fieldValue === undefined) {
522
- throw new FieldValueUndefinedError({ log, criteria, fieldValue });
523
- }
666
+ public validateFieldAgainstCriteria(
667
+ criteria: Criteria,
668
+ fieldValue: string | bigint | Hex,
669
+ input:
670
+ | { log: EventLogs[0] }
671
+ | { decodedArgs: readonly (string | bigint)[] },
672
+ ): boolean {
524
673
  // Type narrow based on criteria.filterType
525
674
  switch (criteria.filterType) {
526
675
  case FilterType.EQUAL:
527
676
  if (criteria.fieldType === PrimitiveType.ADDRESS) {
528
- return isAddressEqual(
529
- criteria.filterData,
530
- `0x${fieldValue.slice(-40)}`,
531
- );
677
+ return isAddressEqual(criteria.filterData, fieldValue as Address);
532
678
  }
533
679
  return fieldValue === criteria.filterData;
534
680
 
535
681
  case FilterType.NOT_EQUAL:
536
- if (criteria.fieldType === PrimitiveType.ADDRESS) {
537
- return !isAddressEqual(
538
- criteria.filterData,
539
- `0x${fieldValue.slice(-40)}`,
540
- );
541
- }
542
682
  return fieldValue !== criteria.filterData;
543
683
 
544
684
  case FilterType.GREATER_THAN:
545
685
  if (criteria.fieldType === PrimitiveType.UINT) {
546
686
  return BigInt(fieldValue) > BigInt(criteria.filterData);
547
687
  }
548
- throw new InvalidNumericalCriteriaError({ log, criteria, fieldValue });
688
+ throw new InvalidNumericalCriteriaError({
689
+ ...input,
690
+ criteria,
691
+ fieldValue,
692
+ });
549
693
 
550
694
  case FilterType.LESS_THAN:
551
695
  if (criteria.fieldType === PrimitiveType.UINT) {
552
696
  return BigInt(fieldValue) < BigInt(criteria.filterData);
553
697
  }
554
- throw new InvalidNumericalCriteriaError({ log, criteria, fieldValue });
698
+ throw new InvalidNumericalCriteriaError({
699
+ ...input,
700
+ criteria,
701
+ fieldValue,
702
+ });
555
703
 
556
704
  case FilterType.CONTAINS:
557
705
  if (
558
706
  criteria.fieldType === PrimitiveType.BYTES ||
559
707
  criteria.fieldType === PrimitiveType.STRING
560
708
  ) {
561
- return fieldValue.includes(criteria.filterData);
709
+ let substring;
710
+ if (criteria.fieldType === PrimitiveType.STRING) {
711
+ substring = fromHex(criteria.filterData, 'string');
712
+ } else {
713
+ // truncate the `0x` prefix
714
+ substring = criteria.filterData.slice(2);
715
+ }
716
+ return (fieldValue as string).includes(substring);
717
+ }
718
+ throw new FieldValueNotComparableError({
719
+ ...input,
720
+ criteria,
721
+ fieldValue,
722
+ });
723
+
724
+ case FilterType.REGEX:
725
+ if (typeof fieldValue !== 'string') {
726
+ throw new FieldValueNotComparableError({
727
+ ...input,
728
+ criteria,
729
+ fieldValue,
730
+ });
731
+ }
732
+
733
+ if (criteria.fieldType === PrimitiveType.STRING) {
734
+ // fieldValue is decoded by the ABI
735
+ const regexString = fromHex(criteria.filterData, 'string');
736
+ return new RegExp(regexString).test(fieldValue);
562
737
  }
563
- throw new FieldValueNotComparableError({ log, criteria, fieldValue });
564
738
 
565
739
  default:
566
- throw new UnrecognizedFilterTypeError({ log, criteria, fieldValue });
740
+ throw new UnrecognizedFilterTypeError({
741
+ ...input,
742
+ criteria,
743
+ fieldValue,
744
+ });
567
745
  }
568
746
  }
569
747
 
748
+ /**
749
+ * Validates a {@link Log} against a given criteria.
750
+ *
751
+ * @param {Criteria} criteria - The criteria to validate against.
752
+ * @param {Log} log - The Viem event log.
753
+ * @returns {Promise<boolean>} - Returns true if the log passes the criteria, false otherwise.
754
+ */
755
+ public validateLogAgainstCriteria(
756
+ criteria: Criteria,
757
+ log: EventLogs[0],
758
+ ): boolean {
759
+ if (!Array.isArray(log.args) || log.args.length <= criteria.fieldIndex) {
760
+ throw new DecodedArgsMalformedError({
761
+ log,
762
+ criteria,
763
+ fieldValue: undefined,
764
+ });
765
+ }
766
+
767
+ const fieldValue = log.args.at(criteria.fieldIndex);
768
+ if (fieldValue === undefined) {
769
+ throw new FieldValueUndefinedError({ log, criteria, fieldValue });
770
+ }
771
+ return this.validateFieldAgainstCriteria(criteria, fieldValue, { log });
772
+ }
773
+
774
+ /**
775
+ * Validates a function's decoded arguments against a given criteria.
776
+ *
777
+ * @param {Criteria} criteria - The criteria to validate against.
778
+ * @param {unknown[]} decodedArgs - The decoded arguments of the function call.
779
+ * @returns {Promise<boolean>} - Returns true if the decoded argument passes the criteria, false otherwise.
780
+ */
781
+ public validateFunctionAgainstCriteria(
782
+ criteria: Criteria,
783
+ decodedArgs: readonly (string | bigint)[],
784
+ ): boolean {
785
+ const fieldValue = decodedArgs[criteria.fieldIndex];
786
+ if (fieldValue === undefined) {
787
+ throw new FieldValueUndefinedError({
788
+ criteria,
789
+ fieldValue,
790
+ });
791
+ }
792
+ return this.validateFieldAgainstCriteria(criteria, fieldValue, {
793
+ decodedArgs,
794
+ });
795
+ }
796
+
570
797
  /**
571
798
  * @inheritdoc
572
799
  *
@@ -615,6 +842,17 @@ export class EventAction extends DeployableTarget<
615
842
  ...this.optionallyAttachAccount(options.account),
616
843
  };
617
844
  }
845
+
846
+ public isArraylikeIndexed(step: ActionStep, event: AbiEvent) {
847
+ if (
848
+ (step.actionParameter.fieldType === PrimitiveType.STRING ||
849
+ step.actionParameter.fieldType === PrimitiveType.BYTES) &&
850
+ event.inputs[step.actionParameter.fieldIndex]?.indexed
851
+ ) {
852
+ return true;
853
+ }
854
+ return false;
855
+ }
618
856
  }
619
857
 
620
858
  function _dedupeActionSteps(_steps: ActionStep[]): ActionStep[] {
@@ -19,7 +19,7 @@ beforeAll(async () => {
19
19
 
20
20
  function freshAllowList(fixtures: Fixtures) {
21
21
  return function freshAllowList() {
22
- return fixtures.registry.clone(
22
+ return fixtures.registry.initialize(
23
23
  crypto.randomUUID(),
24
24
  fixtures.core.SimpleAllowList({
25
25
  owner: defaultOptions.account.address,
@@ -31,7 +31,7 @@ function freshAllowList(fixtures: Fixtures) {
31
31
 
32
32
  function freshDenyList(fixtures: Fixtures) {
33
33
  return function freshDenyList() {
34
- return fixtures.registry.clone(
34
+ return fixtures.registry.initialize(
35
35
  crypto.randomUUID(),
36
36
  fixtures.core.SimpleDenyList({
37
37
  owner: defaultOptions.account.address,
@@ -7,10 +7,11 @@ import { readContract } from '@wagmi/core';
7
7
  import type { Address, Hex } from 'viem';
8
8
  import type { DeployableOptions } from '../Deployable/Deployable';
9
9
  import { InvalidComponentInterfaceError } from '../errors';
10
+ import { OpenAllowList } from './OpenAllowList';
10
11
  import { SimpleAllowList } from './SimpleAllowList';
11
12
  import { SimpleDenyList } from './SimpleDenyList';
12
13
 
13
- export { SimpleAllowList, SimpleDenyList };
14
+ export { OpenAllowList, SimpleAllowList, SimpleDenyList };
14
15
 
15
16
  /**
16
17
  * A union type representing all valid protocol AllowList implementations
@@ -18,7 +19,7 @@ export { SimpleAllowList, SimpleDenyList };
18
19
  * @export
19
20
  * @typedef {AllowList}
20
21
  */
21
- export type AllowList = SimpleAllowList | SimpleDenyList;
22
+ export type AllowList = OpenAllowList | SimpleAllowList | SimpleDenyList;
22
23
 
23
24
  /**
24
25
  * A map of AllowList component interfaces to their constructors.
@@ -32,6 +33,7 @@ export const AllowListByComponentInterface = {
32
33
 
33
34
  /**
34
35
  * A function that will read a contract's component interface using `getComponentInterface` and return the correct instantiated instance.
36
+ * This function will never return an instance of {@link OpenAllowList} because it has the same component interface as {@link SimpleDenyList}
35
37
  *
36
38
  * @export
37
39
  * @async
@@ -56,5 +58,5 @@ export async function allowListFromAddress(
56
58
  interfaceId as Hex,
57
59
  );
58
60
  }
59
- return new Ctor(options, address);
61
+ return new Ctor(options, address) as SimpleDenyList | SimpleAllowList;
60
62
  }
@@ -0,0 +1,40 @@
1
+ import { loadFixture } from '@nomicfoundation/hardhat-network-helpers';
2
+ import { isAddress, zeroAddress } from 'viem';
3
+ import { beforeAll, describe, expect, test } from 'vitest';
4
+ import { accounts } from '../../test/accounts';
5
+ import {
6
+ type Fixtures,
7
+ defaultOptions,
8
+ deployFixtures,
9
+ } from '../../test/helpers';
10
+ import { OpenAllowList } from './OpenAllowList';
11
+
12
+ let fixtures: Fixtures;
13
+
14
+ beforeAll(async () => {
15
+ fixtures = await loadFixture(deployFixtures);
16
+ });
17
+
18
+ function freshOpenAllowList(fixtures: Fixtures) {
19
+ return function freshOpenAllowList() {
20
+ return fixtures.registry.initialize(
21
+ crypto.randomUUID(),
22
+ new fixtures.bases.OpenAllowList(defaultOptions),
23
+ );
24
+ };
25
+ }
26
+
27
+ describe('OpenAllowList', () => {
28
+ test('can successfully be deployed', async () => {
29
+ const denyList = new OpenAllowList(defaultOptions);
30
+ await denyList.deploy();
31
+ expect(isAddress(denyList.assertValidAddress())).toBe(true);
32
+ });
33
+
34
+ test('allows anyone', async () => {
35
+ const denyList = await loadFixture(freshOpenAllowList(fixtures));
36
+ expect(await denyList.isAllowed(defaultOptions.account.address)).toBe(true);
37
+ expect(await denyList.isAllowed(zeroAddress)).toBe(true);
38
+ expect(await denyList.isAllowed(accounts.at(1)!.account)).toBe(true);
39
+ });
40
+ });
@@ -0,0 +1,45 @@
1
+ import { simpleDenyListAbi } from '@boostxyz/evm';
2
+ import { bytecode } from '@boostxyz/evm/artifacts/contracts/allowlists/SimpleDenyList.sol/SimpleDenyList.json';
3
+ import { type Hex, zeroAddress } from 'viem';
4
+ import type {
5
+ DeployableOptions,
6
+ GenericDeployableParams,
7
+ } from '../Deployable/Deployable';
8
+ import {
9
+ SimpleDenyList,
10
+ type SimpleDenyListPayload,
11
+ prepareSimpleDenyListPayload,
12
+ } from './SimpleDenyList';
13
+
14
+ export const openAllowListAbi = simpleDenyListAbi;
15
+
16
+ /**
17
+ * A simple AllowList, extending {@link DenyList}, that is ownerless and allows anyone to claim.
18
+ *
19
+ * @export
20
+ * @class OpenAllowList
21
+ * @typedef {OpenAllowList}
22
+ * @extends {DeployableTarget<OpenAllowListPayload>}
23
+ */
24
+ export class OpenAllowList extends SimpleDenyList<undefined> {
25
+ /**
26
+ * @inheritdoc
27
+ *
28
+ * @public
29
+ * @param {?SimpleDenyListPayload} [_payload]
30
+ * @param {?DeployableOptions} [_options]
31
+ * @returns {GenericDeployableParams}
32
+ */
33
+ public override buildParameters(
34
+ _payload?: SimpleDenyListPayload,
35
+ _options?: DeployableOptions,
36
+ ): GenericDeployableParams {
37
+ const [_, options] = this.validateDeploymentConfig({}, _options);
38
+ return {
39
+ abi: openAllowListAbi,
40
+ bytecode: bytecode as Hex,
41
+ args: [prepareSimpleDenyListPayload({ owner: zeroAddress, denied: [] })],
42
+ ...this.optionallyAttachAccount(options.account),
43
+ };
44
+ }
45
+ }
@@ -16,7 +16,7 @@ beforeAll(async () => {
16
16
 
17
17
  function freshAllowList(fixtures: Fixtures) {
18
18
  return function freshAllowList() {
19
- return fixtures.registry.clone(
19
+ return fixtures.registry.initialize(
20
20
  crypto.randomUUID(),
21
21
  fixtures.core.SimpleAllowList({
22
22
  owner: defaultOptions.account.address,
@@ -16,7 +16,7 @@ beforeAll(async () => {
16
16
 
17
17
  function freshDenyList(fixtures: Fixtures) {
18
18
  return function freshDenyList() {
19
- return fixtures.registry.clone(
19
+ return fixtures.registry.initialize(
20
20
  crypto.randomUUID(),
21
21
  fixtures.core.SimpleDenyList({
22
22
  owner: defaultOptions.account.address,