From fcc83eb9270d6f06b369f15754d85d009a703178 Mon Sep 17 00:00:00 2001
From: Piotr Roslaniec
Date: Wed, 30 Aug 2023 18:40:54 +0200
Subject: [PATCH] apply pr suggestions
---
src/conditions/base/rpc.ts | 24 +++--------
src/conditions/base/shared.ts | 18 +++++++++
src/types.ts | 7 ++++
src/zod.ts | 31 ++++++++++++++
test/unit/conditions/base/time.test.ts | 2 +-
test/unit/conditions/condition-expr.test.ts | 45 ++++++++++++++-------
test/unit/testVariables.ts | 2 +-
7 files changed, 94 insertions(+), 35 deletions(-)
create mode 100644 src/conditions/base/shared.ts
create mode 100644 src/zod.ts
diff --git a/src/conditions/base/rpc.ts b/src/conditions/base/rpc.ts
index 2b16d7823..1b6820dce 100644
--- a/src/conditions/base/rpc.ts
+++ b/src/conditions/base/rpc.ts
@@ -1,29 +1,15 @@
import { z } from 'zod';
-import { ETH_ADDRESS_REGEXP, USER_ADDRESS_PARAM } from '../const';
+import { SUPPORTED_CHAIN_IDS } from '../../types';
+import createUnionSchema from '../../zod';
-export const returnValueTestSchema = z.object({
- index: z.number().optional(),
- comparator: z.enum(['==', '>', '<', '>=', '<=', '!=']),
- value: z.union([z.string(), z.number(), z.boolean()]),
-});
-
-export type ReturnValueTestProps = z.infer;
-
-const EthAddressOrUserAddressSchema = z.array(
- z.union([z.string().regex(ETH_ADDRESS_REGEXP), z.literal(USER_ADDRESS_PARAM)])
-);
+import { EthAddressOrUserAddressSchema, returnValueTestSchema } from './shared';
export const rpcConditionSchema = z.object({
conditionType: z.literal('rpc').default('rpc'),
- chain: z.union([
- z.literal(137),
- z.literal(80001),
- z.literal(5),
- z.literal(1),
- ]),
+ chain: createUnionSchema(SUPPORTED_CHAIN_IDS),
method: z.enum(['eth_getBalance', 'balanceOf']),
- parameters: EthAddressOrUserAddressSchema,
+ parameters: z.array(EthAddressOrUserAddressSchema),
returnValueTest: returnValueTestSchema,
});
diff --git a/src/conditions/base/shared.ts b/src/conditions/base/shared.ts
new file mode 100644
index 000000000..904329521
--- /dev/null
+++ b/src/conditions/base/shared.ts
@@ -0,0 +1,18 @@
+import { z } from 'zod';
+
+import { ETH_ADDRESS_REGEXP, USER_ADDRESS_PARAM } from '../const';
+
+export const returnValueTestSchema = z.object({
+ index: z.number().optional(),
+ comparator: z.enum(['==', '>', '<', '>=', '<=', '!=']),
+ value: z.unknown(),
+});
+
+export type ReturnValueTestProps = z.infer;
+
+const EthAddressSchema = z.string().regex(ETH_ADDRESS_REGEXP);
+const UserAddressSchema = z.literal(USER_ADDRESS_PARAM);
+export const EthAddressOrUserAddressSchema = z.union([
+ EthAddressSchema,
+ UserAddressSchema,
+]);
diff --git a/src/types.ts b/src/types.ts
index 2440dcc53..56262e1ea 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -8,3 +8,10 @@ export enum ChainId {
GOERLI = 5,
MAINNET = 1,
}
+
+export const SUPPORTED_CHAIN_IDS = [
+ ChainId.POLYGON,
+ ChainId.MUMBAI,
+ ChainId.GOERLI,
+ ChainId.MAINNET,
+];
diff --git a/src/zod.ts b/src/zod.ts
new file mode 100644
index 000000000..1829e1201
--- /dev/null
+++ b/src/zod.ts
@@ -0,0 +1,31 @@
+import { Primitive, z, ZodLiteral } from 'zod';
+
+// Source: https://github.com/colinhacks/zod/issues/831#issuecomment-1063481764
+const createUnion = <
+ T extends Readonly<[Primitive, Primitive, ...Primitive[]]>
+>(
+ values: T
+) => {
+ const zodLiterals = values.map((value) => z.literal(value)) as unknown as [
+ ZodLiteral,
+ ZodLiteral,
+ ...ZodLiteral[]
+ ];
+ return z.union(zodLiterals);
+};
+
+function createUnionSchema(values: T) {
+ if (values.length === 0) {
+ return z.never();
+ }
+
+ if (values.length === 1) {
+ return z.literal(values[0]);
+ }
+
+ return createUnion(
+ values as unknown as Readonly<[Primitive, Primitive, ...Primitive[]]>
+ );
+}
+
+export default createUnionSchema;
diff --git a/test/unit/conditions/base/time.test.ts b/test/unit/conditions/base/time.test.ts
index e2d30cbed..cbdf90855 100644
--- a/test/unit/conditions/base/time.test.ts
+++ b/test/unit/conditions/base/time.test.ts
@@ -2,7 +2,7 @@ import {
TimeCondition,
TimeConditionProps,
} from '../../../../src/conditions/base';
-import { ReturnValueTestProps } from '../../../../src/conditions/base/rpc';
+import { ReturnValueTestProps } from '../../../../src/conditions/base/shared';
describe('validation', () => {
const returnValueTest: ReturnValueTestProps = {
diff --git a/test/unit/conditions/condition-expr.test.ts b/test/unit/conditions/condition-expr.test.ts
index bcc78e7a7..faaa57949 100644
--- a/test/unit/conditions/condition-expr.test.ts
+++ b/test/unit/conditions/condition-expr.test.ts
@@ -7,6 +7,7 @@ import {
ContractConditionProps,
RpcCondition,
TimeCondition,
+ TimeConditionProps,
} from '../../../src/conditions/base';
import { USER_ADDRESS_PARAM } from '../../../src/conditions/const';
import { ERC721Balance } from '../../../src/conditions/predefined';
@@ -66,7 +67,7 @@ describe('condition set', () => {
describe('equality', () => {
const conditionExprCurrentVersion = new ConditionExpression(rpcCondition);
- it('same version and condition', async () => {
+ it('same version and condition', () => {
const conditionExprSameCurrentVersion = new ConditionExpression(
rpcCondition,
ConditionExpression.VERSION
@@ -76,7 +77,7 @@ describe('condition set', () => {
).toBeTruthy();
});
- it('different minor/patch version but same condition', async () => {
+ it('different minor/patch version but same condition', () => {
const conditionExprOlderMinorVersion = new ConditionExpression(
rpcCondition,
'0.1.0'
@@ -96,7 +97,7 @@ describe('condition set', () => {
).not.toBeTruthy();
});
- it('minor/patch number greater than major; still older', async () => {
+ it('minor/patch number greater than major; still older', () => {
const conditionExprOlderMinorVersion = new ConditionExpression(
rpcCondition,
'0.9.0'
@@ -139,7 +140,7 @@ describe('condition set', () => {
contractConditionWithAbi,
timeCondition,
compoundCondition,
- ])('same version but different condition', async (condition) => {
+ ])('same version but different condition', (condition) => {
const conditionExprSameVersionDifferentCondition =
new ConditionExpression(condition);
expect(
@@ -149,7 +150,7 @@ describe('condition set', () => {
).not.toBeTruthy();
});
- it('same contract condition although using erc721 helper', async () => {
+ it('same contract condition although using erc721 helper', () => {
const erc721ConditionExpr = new ConditionExpression(
erc721BalanceCondition
);
@@ -172,7 +173,7 @@ describe('condition set', () => {
rpcCondition,
timeCondition,
compoundCondition,
- ])('serializes to and from json', async (condition) => {
+ ])('serializes to and from json', (condition) => {
const conditionExpr = new ConditionExpression(condition);
const conditionExprJson = conditionExpr.toJson();
expect(conditionExprJson).toBeDefined();
@@ -187,7 +188,7 @@ describe('condition set', () => {
expect(conditionExprFromJson.equals(conditionExprFromJson)).toBeTruthy();
});
- it('incompatible version', async () => {
+ it('incompatible version', () => {
const currentVersion = new SemVer(ConditionExpression.VERSION);
const invalidVersion = currentVersion.inc('major');
expect(() => {
@@ -202,7 +203,7 @@ describe('condition set', () => {
it.each(['version', 'x.y', 'x.y.z', '-1,0.0', '1.0.0.0.0.0.0'])(
'invalid versions',
- async (invalidVersion) => {
+ (invalidVersion) => {
expect(() => {
ConditionExpression.fromObj({
version: invalidVersion,
@@ -212,7 +213,23 @@ describe('condition set', () => {
}
);
- it('erc721 condition serialization', async () => {
+ it.each(['_invalid_condition_type_', undefined as unknown as string])(
+ 'rejects an invalid condition type',
+ (invalidConditionType) => {
+ const conditionObj = {
+ ...testTimeConditionObj,
+ conditionType: invalidConditionType,
+ } as unknown as TimeConditionProps;
+ expect(() => {
+ ConditionExpression.fromObj({
+ version: ConditionExpression.VERSION,
+ condition: conditionObj,
+ });
+ }).toThrow(`Invalid conditionType: ${invalidConditionType}`);
+ }
+ );
+
+ it('erc721 condition serialization', () => {
const conditionExpr = new ConditionExpression(erc721BalanceCondition);
const erc721BalanceConditionObj = erc721BalanceCondition.toObj();
@@ -240,7 +257,7 @@ describe('condition set', () => {
expect(conditionExprFromJson.condition).toBeInstanceOf(ContractCondition);
});
- it('contract condition no abi serialization', async () => {
+ it('contract condition no abi serialization', () => {
const conditionExpr = new ConditionExpression(contractConditionNoAbi);
const conditionExprJson = conditionExpr.toJson();
@@ -272,7 +289,7 @@ describe('condition set', () => {
expect(conditionExprFromJson.condition).toBeInstanceOf(ContractCondition);
});
- it('contract condition with abi serialization', async () => {
+ it('contract condition with abi serialization', () => {
const conditionExpr = new ConditionExpression(contractConditionWithAbi);
const conditionExprJson = conditionExpr.toJson();
@@ -305,7 +322,7 @@ describe('condition set', () => {
expect(conditionExprFromJson.condition).toBeInstanceOf(ContractCondition);
});
- it('time condition serialization', async () => {
+ it('time condition serialization', () => {
const conditionExpr = new ConditionExpression(timeCondition);
const conditionExprJson = conditionExpr.toJson();
@@ -328,7 +345,7 @@ describe('condition set', () => {
expect(conditionExprFromJson.condition).toBeInstanceOf(TimeCondition);
});
- it('rpc condition serialization', async () => {
+ it('rpc condition serialization', () => {
const conditionExpr = new ConditionExpression(rpcCondition);
const conditionExprJson = conditionExpr.toJson();
@@ -352,7 +369,7 @@ describe('condition set', () => {
expect(conditionExprFromJson.condition).toBeInstanceOf(RpcCondition);
});
- it('compound condition serialization', async () => {
+ it('compound condition serialization', () => {
const conditionExpr = new ConditionExpression(compoundCondition);
const compoundConditionObj = compoundCondition.toObj();
diff --git a/test/unit/testVariables.ts b/test/unit/testVariables.ts
index c7f85dec8..1acfe86eb 100644
--- a/test/unit/testVariables.ts
+++ b/test/unit/testVariables.ts
@@ -4,7 +4,7 @@ import {
TimeConditionProps,
} from '../../src/conditions/base';
import { FunctionAbiProps } from '../../src/conditions/base/contract';
-import { ReturnValueTestProps } from '../../src/conditions/base/rpc';
+import { ReturnValueTestProps } from '../../src/conditions/base/shared';
export const aliceSecretKeyBytes = new Uint8Array([
55, 82, 190, 189, 203, 164, 60, 148, 36, 86, 46, 123, 63, 152, 215, 113, 174,