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,