Skip to content

Commit

Permalink
apply pr suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-roslaniec committed Sep 4, 2023
1 parent ff8ae9e commit b0b478f
Show file tree
Hide file tree
Showing 19 changed files with 226 additions and 165 deletions.
3 changes: 2 additions & 1 deletion src/agents/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ChainId, ChecksumAddress } from '../types';
import { ChecksumAddress } from '../types';
import { ChainId } from '../web3';

type Contracts = {
readonly SUBSCRIPTION_MANAGER: ChecksumAddress | undefined;
Expand Down
9 changes: 3 additions & 6 deletions src/conditions/base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ export class TimeCondition extends Condition {
}
}

export {
contractConditionSchema,
type ContractConditionProps,
} from './contract';
export { rpcConditionSchema, type RpcConditionProps } from './rpc';
export { timeConditionSchema, type TimeConditionProps } from './time';
export { type ContractConditionProps } from './contract';
export { type RpcConditionProps } from './rpc';
export { type TimeConditionProps } from './time';
24 changes: 5 additions & 19 deletions src/conditions/base/rpc.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
import { z } from 'zod';

import { ETH_ADDRESS_REGEXP, USER_ADDRESS_PARAM } from '../const';
import { SUPPORTED_CHAIN_IDS } from '../const';
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<typeof returnValueTestSchema>;

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,
});

Expand Down
18 changes: 18 additions & 0 deletions src/conditions/base/shared.ts
Original file line number Diff line number Diff line change
@@ -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<typeof returnValueTestSchema>;

const EthAddressSchema = z.string().regex(ETH_ADDRESS_REGEXP);
const UserAddressSchema = z.literal(USER_ADDRESS_PARAM);
export const EthAddressOrUserAddressSchema = z.union([
EthAddressSchema,
UserAddressSchema,
]);
6 changes: 3 additions & 3 deletions src/conditions/compound-condition.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { z } from 'zod';

import { contractConditionSchema } from './base';
import { rpcConditionSchema } from './base';
import { timeConditionSchema } from './base';
import { contractConditionSchema } from './base/contract';
import { rpcConditionSchema } from './base/rpc';
import { timeConditionSchema } from './base/time';

export const compoundConditionSchema: z.ZodSchema = z.object({
conditionType: z.literal('compound').default('compound'),
Expand Down
29 changes: 2 additions & 27 deletions src/conditions/condition-expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,7 @@ import { SemVer } from 'semver';

import { toBytes, toJSON } from '../utils';

import {
CompoundCondition,
ContractCondition,
ContractConditionProps,
RpcCondition,
RpcConditionProps,
TimeCondition,
TimeConditionProps,
} from './base';
import { CompoundConditionProps } from './compound-condition';
import { Condition, ConditionProps } from './condition';
import { Condition } from './condition';
import { ConditionContext, CustomContextParam } from './context';

export type ConditionExpressionJSON = {
Expand All @@ -38,21 +28,6 @@ export class ConditionExpression {
};
}

private static conditionFromObject(obj: ConditionProps): Condition {
switch (obj.conditionType) {
case 'rpc':
return new RpcCondition(obj as RpcConditionProps);
case 'time':
return new TimeCondition(obj as TimeConditionProps);
case 'contract':
return new ContractCondition(obj as ContractConditionProps);
case 'compound':
return new CompoundCondition(obj as CompoundConditionProps);
default:
throw new Error(`Invalid conditionType: ${obj.conditionType}`);
}
}

public static fromObj(obj: ConditionExpressionJSON): ConditionExpression {
const receivedVersion = new SemVer(obj.version);
const currentVersion = new SemVer(ConditionExpression.VERSION);
Expand All @@ -70,7 +45,7 @@ export class ConditionExpression {
);
}

const condition = this.conditionFromObject(obj.condition as ConditionProps);
const condition = Condition.fromObj(obj.condition);
return new ConditionExpression(condition, obj.version);
}

Expand Down
64 changes: 38 additions & 26 deletions src/conditions/condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,40 @@ import { z } from 'zod';
import { objectEquals } from '../utils';

import {
CompoundCondition,
ContractCondition,
ContractConditionProps,
RpcCondition,
RpcConditionProps,
TimeCondition,
TimeConditionProps,
} from './base';
import { CompoundConditionProps } from './compound-condition';
import { USER_ADDRESS_PARAM } from './const';

// Not using discriminated union because of inconsistent Zod types
// Some conditions have ZodEffect types because of .refine() calls
export type ConditionProps =
| RpcConditionProps
| TimeConditionProps
| ContractConditionProps
| CompoundConditionProps;
type ConditionSchema = z.ZodSchema;
export type ConditionProps = z.infer<ConditionSchema>;

export class Condition {
constructor(
public readonly schema: z.ZodSchema,
public readonly value:
| RpcConditionProps
| TimeConditionProps
| ContractConditionProps
| CompoundConditionProps
) {}
public readonly schema: ConditionSchema,
public readonly value: ConditionProps
) {
const { data, error } = Condition.validate(schema, value);
if (error) {
throw new Error(`Invalid condition: ${JSON.stringify(error.issues)}`);
}
this.value = data;
}

public validate(override: Partial<ConditionProps> = {}): {
public static validate(
schema: ConditionSchema,
value: ConditionProps
): {
data?: ConditionProps;
error?: z.ZodError;
} {
const newValue = {
...this.value,
...override,
};
const result = this.schema.safeParse(newValue);
const result = schema.safeParse(value);
if (result.success) {
return { data: result.data };
}
Expand All @@ -48,18 +48,30 @@ export class Condition {
}

public toObj() {
const { data, error } = this.validate(this.value);
const { data, error } = Condition.validate(this.schema, this.value);
if (error) {
throw new Error(`Invalid condition: ${JSON.stringify(error.issues)}`);
}
return data;
}

public static fromObj<T extends Condition>(
this: new (...args: unknown[]) => T,
obj: Record<string, unknown>
): T {
return new this(obj);
private static conditionFromObject(obj: ConditionProps): Condition {
switch (obj.conditionType) {
case 'rpc':
return new RpcCondition(obj as RpcConditionProps);
case 'time':
return new TimeCondition(obj as TimeConditionProps);
case 'contract':
return new ContractCondition(obj as ContractConditionProps);
case 'compound':
return new CompoundCondition(obj as CompoundConditionProps);
default:
throw new Error(`Invalid conditionType: ${obj.conditionType}`);
}
}

public static fromObj(obj: ConditionProps): Condition {
return Condition.conditionFromObject(obj);
}

public equals(other: Condition) {
Expand Down
9 changes: 9 additions & 0 deletions src/conditions/const.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import { ChainId } from '../web3';

export const USER_ADDRESS_PARAM = ':userAddress';

export const ETH_ADDRESS_REGEXP = new RegExp('^0x[a-fA-F0-9]{40}$');

export const SUPPORTED_CHAIN_IDS = [
ChainId.POLYGON,
ChainId.MUMBAI,
ChainId.GOERLI,
ChainId.MAINNET,
];
5 changes: 1 addition & 4 deletions src/conditions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,4 @@ export {
} from './condition-expr';
export { ConditionContext, type CustomContextParam } from './context';
export { Condition, type ConditionProps } from './condition';
export {
compoundConditionSchema,
type CompoundConditionProps,
} from './compound-condition';
export { type CompoundConditionProps } from './compound-condition';
31 changes: 31 additions & 0 deletions src/conditions/zod.ts
Original file line number Diff line number Diff line change
@@ -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<Primitive>,
ZodLiteral<Primitive>,
...ZodLiteral<Primitive>[]
];
return z.union(zodLiterals);
};

function createUnionSchema<T extends readonly Primitive[]>(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;
7 changes: 0 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
export type ChecksumAddress = string;
export type HexEncodedBytes = string;
export type Base64EncodedBytes = string;

export enum ChainId {
POLYGON = 137,
MUMBAI = 80001,
GOERLI = 5,
MAINNET = 1,
}
8 changes: 8 additions & 0 deletions src/web3.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { fromHexString } from './utils';

export enum ChainId {
POLYGON = 137,
MUMBAI = 80001,
GOERLI = 5,
MAINNET = 1,
}

export const toCanonicalAddress = (address: string): Uint8Array => {
const ETH_ADDRESS_STRING_PREFIX = '0x';
const nonPrefixed = address.startsWith(ETH_ADDRESS_STRING_PREFIX)
Expand Down
34 changes: 2 additions & 32 deletions test/unit/conditions/base/condition.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Condition } from '../../../../src/conditions';
import { ContractCondition } from '../../../../src/conditions/base';
import {
ERC721Balance,
Expand All @@ -6,7 +7,6 @@ import {
import {
TEST_CHAIN_ID,
TEST_CONTRACT_ADDR,
TEST_CONTRACT_ADDR_2,
testContractConditionObj,
} from '../../testVariables';

Expand All @@ -17,40 +17,10 @@ describe('validation', () => {
});

it('accepts a correct schema', async () => {
const result = condition.validate();
const result = Condition.validate(condition.schema, condition.value);
expect(result.error).toBeUndefined();
expect(result.data.contractAddress).toEqual(TEST_CONTRACT_ADDR);
});

it('accepts on a valid value override', async () => {
const validOverride = {
chain: TEST_CHAIN_ID,
contractAddress: TEST_CONTRACT_ADDR_2,
};
const result = condition.validate(validOverride);
expect(result.error).toBeUndefined();
expect(result.data).toMatchObject(validOverride);
});

it('rejects on an invalid value override', async () => {
const invalidOverride = {
chain: -1,
contractAddress: TEST_CONTRACT_ADDR,
};
const result = condition.validate(invalidOverride);
expect(result.error).toBeDefined();
expect(result.data).toBeUndefined();
expect(result.error?.format()).toMatchObject({
chain: {
_errors: [
'Invalid literal value, expected 137',
'Invalid literal value, expected 80001',
'Invalid literal value, expected 5',
'Invalid literal value, expected 1',
],
},
});
});
});

describe('serialization', () => {
Expand Down
Loading

0 comments on commit b0b478f

Please sign in to comment.