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 Aug 30, 2023
1 parent e5b0976 commit 19d2a9e
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 113 deletions.
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 '../../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<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,
]);
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 } 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
65 changes: 41 additions & 24 deletions src/conditions/condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,58 +3,75 @@ import { z } from 'zod';
import { objectEquals } from '../utils';

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

// 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 validate(override: Partial<ConditionProps> = {}): {
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 static validate(
schema: ConditionSchema,
value: ConditionProps,
override: Partial<ConditionProps> = {}
): {
data?: ConditionProps;
error?: z.ZodError;
} {
const newValue = {
...this.value,
...value,
...override,
};
const result = this.schema.safeParse(newValue);
const result = schema.safeParse(newValue);
if (result.success) {
return { data: result.data };
}
return { error: result.error };
}

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
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ export enum ChainId {
GOERLI = 5,
MAINNET = 1,
}

export const SUPPORTED_CHAIN_IDS = [
ChainId.POLYGON,
ChainId.MUMBAI,
ChainId.GOERLI,
ChainId.MAINNET,
];
31 changes: 31 additions & 0 deletions src/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;
15 changes: 12 additions & 3 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 @@ -17,7 +18,7 @@ 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);
});
Expand All @@ -27,7 +28,11 @@ describe('validation', () => {
chain: TEST_CHAIN_ID,
contractAddress: TEST_CONTRACT_ADDR_2,
};
const result = condition.validate(validOverride);
const result = Condition.validate(
condition.schema,
condition.value,
validOverride
);
expect(result.error).toBeUndefined();
expect(result.data).toMatchObject(validOverride);
});
Expand All @@ -37,7 +42,11 @@ describe('validation', () => {
chain: -1,
contractAddress: TEST_CONTRACT_ADDR,
};
const result = condition.validate(invalidOverride);
const result = Condition.validate(
condition.schema,
condition.value,
invalidOverride
);
expect(result.error).toBeDefined();
expect(result.data).toBeUndefined();
expect(result.error?.format()).toMatchObject({
Expand Down
39 changes: 29 additions & 10 deletions test/unit/conditions/base/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
ContractCondition,
ContractConditionProps,
contractConditionSchema,
} from '../../../../src/conditions/base';
import { FunctionAbiProps } from '../../../../src/conditions/base/contract';
import { USER_ADDRESS_PARAM } from '../../../../src/conditions/const';
Expand All @@ -15,7 +16,10 @@ import { testContractConditionObj, testFunctionAbi } from '../../testVariables';

describe('validation', () => {
it('accepts on a valid schema', () => {
const result = new ContractCondition(testContractConditionObj).validate();
const result = ContractCondition.validate(
contractConditionSchema,
testContractConditionObj
);

expect(result.error).toBeUndefined();
expect(result.data).toEqual(testContractConditionObj);
Expand All @@ -27,7 +31,10 @@ describe('validation', () => {
// Intentionally removing `contractAddress`
contractAddress: undefined,
} as unknown as ContractConditionProps;
const result = new ContractCondition(badContractCondition).validate();
const result = ContractCondition.validate(
contractConditionSchema,
badContractCondition
);

expect(result.error).toBeDefined();
expect(result.data).toBeUndefined();
Expand Down Expand Up @@ -67,7 +74,10 @@ describe('accepts either standardContractType or functionAbi but not both or non
standardContractType,
functionAbi: undefined,
} as typeof testContractConditionObj;
const result = new ContractCondition(conditionObj).validate();
const result = ContractCondition.validate(
contractConditionSchema,
conditionObj
);

expect(result.error).toBeUndefined();
expect(result.data).toEqual(conditionObj);
Expand All @@ -79,7 +89,10 @@ describe('accepts either standardContractType or functionAbi but not both or non
functionAbi,
standardContractType: undefined,
} as typeof testContractConditionObj;
const result = new ContractCondition(conditionObj).validate();
const result = ContractCondition.validate(
contractConditionSchema,
conditionObj
);

expect(result.error).toBeUndefined();
expect(result.data).toEqual(conditionObj);
Expand All @@ -91,7 +104,10 @@ describe('accepts either standardContractType or functionAbi but not both or non
standardContractType,
functionAbi,
} as typeof testContractConditionObj;
const result = new ContractCondition(conditionObj).validate();
const result = ContractCondition.validate(
contractConditionSchema,
conditionObj
);

expect(result.error).toBeDefined();
expect(result.data).toBeUndefined();
Expand All @@ -108,7 +124,10 @@ describe('accepts either standardContractType or functionAbi but not both or non
standardContractType: undefined,
functionAbi: undefined,
} as typeof testContractConditionObj;
const result = new ContractCondition(conditionObj).validate();
const result = ContractCondition.validate(
contractConditionSchema,
conditionObj
);

expect(result.error).toBeDefined();
expect(result.data).toBeUndefined();
Expand Down Expand Up @@ -177,12 +196,12 @@ describe('supports custom function abi', () => {
},
},
])('accepts well-formed functionAbi', ({ method, functionAbi }) => {
const result = new ContractCondition({
const result = ContractCondition.validate(contractConditionSchema, {
...contractConditionObj,
parameters: functionAbi.inputs.map((input) => `fake_parameter_${input}`), //
functionAbi: functionAbi as FunctionAbiProps,
method,
}).validate();
});

expect(result.error).toBeUndefined();
expect(result.data).toBeDefined();
Expand Down Expand Up @@ -259,11 +278,11 @@ describe('supports custom function abi', () => {
])(
'rejects malformed functionAbi',
({ method, badField, expectedErrors, functionAbi }) => {
const result = new ContractCondition({
const result = ContractCondition.validate(contractConditionSchema, {
...contractConditionObj,
functionAbi: functionAbi as unknown as FunctionAbiProps,
method,
}).validate();
});

expect(result.error).toBeDefined();
expect(result.data).toBeUndefined();
Expand Down
Loading

1 comment on commit 19d2a9e

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bundled size for the package is listed below:

build/main/src/characters: 74.22 KB
build/main/src/kits: 19.53 KB
build/main/src/conditions/context: 42.97 KB
build/main/src/conditions/predefined: 19.53 KB
build/main/src/conditions/base: 54.69 KB
build/main/src/conditions: 164.06 KB
build/main/src/agents: 39.06 KB
build/main/src/sdk/strategy: 35.16 KB
build/main/src/sdk: 46.88 KB
build/main/src/policies: 19.53 KB
build/main/src: 449.22 KB
build/main/types/ethers-contracts/factories: 82.03 KB
build/main/types/ethers-contracts: 152.34 KB
build/main/types: 156.25 KB
build/main: 664.06 KB
build/module/src/characters: 74.22 KB
build/module/src/kits: 19.53 KB
build/module/src/conditions/context: 42.97 KB
build/module/src/conditions/predefined: 19.53 KB
build/module/src/conditions/base: 54.69 KB
build/module/src/conditions: 164.06 KB
build/module/src/agents: 39.06 KB
build/module/src/sdk/strategy: 31.25 KB
build/module/src/sdk: 42.97 KB
build/module/src/policies: 19.53 KB
build/module/src: 441.41 KB
build/module/types/ethers-contracts/factories: 82.03 KB
build/module/types/ethers-contracts: 152.34 KB
build/module/types: 156.25 KB
build/module: 656.25 KB
build: 1.29 MB

Please sign in to comment.