Skip to content

Commit

Permalink
Separate condition deserialization from base Condition via ConditionF…
Browse files Browse the repository at this point in the history
…actory (#292)
  • Loading branch information
derekpierre authored Sep 6, 2023
2 parents 67e43cd + 539d5dc commit 8f1dcf1
Show file tree
Hide file tree
Showing 12 changed files with 88 additions and 43 deletions.
6 changes: 5 additions & 1 deletion src/conditions/base/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,13 @@ const functionAbiSchema = z

export type FunctionAbiProps = z.infer<typeof functionAbiSchema>;

export const ContractConditionType = 'contract';

export const contractConditionSchema = rpcConditionSchema
.extend({
conditionType: z.literal('contract').default('contract'),
conditionType: z
.literal(ContractConditionType)
.default(ContractConditionType),
contractAddress: z.string().regex(ETH_ADDRESS_REGEXP),
standardContractType: z.enum(['ERC20', 'ERC721']).optional(),
method: z.string(),
Expand Down
10 changes: 7 additions & 3 deletions src/conditions/base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ export class TimeCondition extends Condition {
}
}

export { type ContractConditionProps } from './contract';
export { type RpcConditionProps } from './rpc';
export { type TimeConditionProps } from './time';
export { type ContractConditionProps, ContractConditionType } from './contract';
export { type RpcConditionProps, RpcConditionType } from './rpc';
export {
type TimeConditionProps,
TimeConditionType,
TimeConditionMethod,
} from './time';
4 changes: 3 additions & 1 deletion src/conditions/base/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import createUnionSchema from '../zod';

import { EthAddressOrUserAddressSchema, returnValueTestSchema } from './shared';

export const RpcConditionType = 'rpc';

export const rpcConditionSchema = z.object({
conditionType: z.literal('rpc').default('rpc'),
conditionType: z.literal(RpcConditionType).default(RpcConditionType),
chain: createUnionSchema(SUPPORTED_CHAIN_IDS),
method: z.enum(['eth_getBalance', 'balanceOf']),
parameters: z.array(EthAddressOrUserAddressSchema),
Expand Down
7 changes: 5 additions & 2 deletions src/conditions/base/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { rpcConditionSchema } from './rpc';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { parameters: _, ...restShape } = rpcConditionSchema.shape;

export const TimeConditionType = 'time';
export const TimeConditionMethod = 'blocktime';

export const timeConditionSchema = z.object({
...restShape,
conditionType: z.literal('time').default('time'),
method: z.literal('blocktime').default('blocktime'),
conditionType: z.literal(TimeConditionType).default(TimeConditionType),
method: z.literal(TimeConditionMethod).default(TimeConditionMethod),
});

export type TimeConditionProps = z.infer<typeof timeConditionSchema>;
6 changes: 5 additions & 1 deletion src/conditions/compound-condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import { contractConditionSchema } from './base/contract';
import { rpcConditionSchema } from './base/rpc';
import { timeConditionSchema } from './base/time';

export const CompoundConditionType = 'compound';

export const compoundConditionSchema: z.ZodSchema = z.object({
conditionType: z.literal('compound').default('compound'),
conditionType: z
.literal(CompoundConditionType)
.default(CompoundConditionType),
operator: z.enum(['and', 'or']),
operands: z
.array(
Expand Down
42 changes: 25 additions & 17 deletions src/conditions/condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,40 @@ import {
CompoundCondition,
ContractCondition,
ContractConditionProps,
ContractConditionType,
RpcCondition,
RpcConditionProps,
RpcConditionType,
TimeCondition,
TimeConditionProps,
TimeConditionType,
} from './base';
import { CompoundConditionProps } from './compound-condition';
import {
CompoundConditionProps,
CompoundConditionType,
} from './compound-condition';
import { USER_ADDRESS_PARAM } from './const';

type ConditionSchema = z.ZodSchema;
export type ConditionProps = z.infer<ConditionSchema>;

class ConditionFactory {
public static conditionFromProps(obj: ConditionProps): Condition {
switch (obj.conditionType) {
case RpcConditionType:
return new RpcCondition(obj as RpcConditionProps);
case TimeConditionType:
return new TimeCondition(obj as TimeConditionProps);
case ContractConditionType:
return new ContractCondition(obj as ContractConditionProps);
case CompoundConditionType:
return new CompoundCondition(obj as CompoundConditionProps);
default:
throw new Error(`Invalid conditionType: ${obj.conditionType}`);
}
}
}

export class Condition {
constructor(
public readonly schema: ConditionSchema,
Expand Down Expand Up @@ -55,23 +78,8 @@ export class Condition {
return data;
}

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);
return ConditionFactory.conditionFromProps(obj);
}

public equals(other: Condition) {
Expand Down
5 changes: 4 additions & 1 deletion src/conditions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ export {
} from './condition-expr';
export { ConditionContext, type CustomContextParam } from './context';
export { Condition, type ConditionProps } from './condition';
export { type CompoundConditionProps } from './compound-condition';
export {
type CompoundConditionProps,
CompoundConditionType,
} from './compound-condition';
5 changes: 3 additions & 2 deletions src/conditions/predefined/erc721.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ContractCondition, ContractConditionProps } from '../base';
import { ContractConditionType } from '../base/contract';
import { USER_ADDRESS_PARAM } from '../const';

// TODO: Rewrite these using Zod schemas?
Expand All @@ -9,7 +10,7 @@ const ERC721OwnershipDefaults: Omit<
ContractConditionProps,
ERC721OwnershipFields
> = {
conditionType: 'contract',
conditionType: ContractConditionType,
method: 'ownerOf',
standardContractType: 'ERC721',
returnValueTest: {
Expand All @@ -29,7 +30,7 @@ type ERC721BalanceFields = 'contractAddress' | 'chain';

const ERC721BalanceDefaults: Omit<ContractConditionProps, ERC721BalanceFields> =
{
conditionType: 'contract',
conditionType: ContractConditionType,
method: 'balanceOf',
parameters: [USER_ADDRESS_PARAM],
standardContractType: 'ERC721',
Expand Down
12 changes: 8 additions & 4 deletions test/unit/conditions/base/time.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import {
TimeConditionProps,
} from '../../../../src/conditions/base';
import { ReturnValueTestProps } from '../../../../src/conditions/base/shared';
import { timeConditionSchema } from '../../../../src/conditions/base/time';
import {
TimeConditionMethod,
timeConditionSchema,
TimeConditionType,
} from '../../../../src/conditions/base/time';

describe('validation', () => {
const returnValueTest: ReturnValueTestProps = {
Expand All @@ -14,9 +18,9 @@ describe('validation', () => {

it('accepts a valid schema', () => {
const conditionObj: TimeConditionProps = {
conditionType: TimeConditionType,
returnValueTest,
conditionType: 'time',
method: 'blocktime',
method: TimeConditionMethod,
chain: 1,
};
const result = TimeCondition.validate(timeConditionSchema, conditionObj);
Expand All @@ -27,7 +31,7 @@ describe('validation', () => {

it('rejects an invalid schema', () => {
const badObj = {
conditionType: 'time',
conditionType: TimeConditionType,
// Intentionally replacing `returnValueTest` with an invalid test
returnValueTest: {
...returnValueTest,
Expand Down
13 changes: 8 additions & 5 deletions test/unit/conditions/compound-condition.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Condition } from '../../../src/conditions';
import { CompoundCondition } from '../../../src/conditions/base';
import { compoundConditionSchema } from '../../../src/conditions/compound-condition';
import {
compoundConditionSchema,
CompoundConditionType,
} from '../../../src/conditions/compound-condition';
import {
testContractConditionObj,
testRpcConditionObj,
Expand All @@ -18,7 +21,7 @@ describe('validation', () => {
expect(result.error).toBeUndefined();
expect(result.data).toEqual({
...conditionObj,
conditionType: 'compound',
conditionType: CompoundConditionType,
});
});

Expand All @@ -35,7 +38,7 @@ describe('validation', () => {
expect(result.error).toBeUndefined();
expect(result.data).toEqual({
...conditionObj,
conditionType: 'compound',
conditionType: CompoundConditionType,
});
});

Expand Down Expand Up @@ -104,14 +107,14 @@ describe('validation', () => {
);
expect(result.error).toBeUndefined();
expect(result.data).toEqual({
conditionType: 'compound',
conditionType: CompoundConditionType,
operator: 'and',
operands: [
testContractConditionObj,
testTimeConditionObj,
testRpcConditionObj,
{
conditionType: 'compound',
conditionType: CompoundConditionType,
operator: 'or',
operands: [testTimeConditionObj, testContractConditionObj],
},
Expand Down
3 changes: 2 additions & 1 deletion test/unit/conditions/condition-expr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
TimeCondition,
TimeConditionProps,
} from '../../../src/conditions/base';
import { RpcConditionType } from '../../../src/conditions/base/rpc';
import { USER_ADDRESS_PARAM } from '../../../src/conditions/const';
import { ERC721Balance } from '../../../src/conditions/predefined';
import { objectEquals, toJSON } from '../../../src/utils';
Expand Down Expand Up @@ -232,7 +233,7 @@ describe('condition set', () => {
it('rejects a mismatched condition type', () => {
const conditionObj = {
...testTimeConditionObj,
conditionType: 'rpc',
conditionType: RpcConditionType,
} as unknown as TimeConditionProps;
expect(() => {
ConditionExpression.fromObj({
Expand Down
18 changes: 13 additions & 5 deletions test/unit/testVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@ import {
RpcConditionProps,
TimeConditionProps,
} from '../../src/conditions/base';
import { FunctionAbiProps } from '../../src/conditions/base/contract';
import {
ContractConditionType,
FunctionAbiProps,
} from '../../src/conditions/base/contract';
import { RpcConditionType } from '../../src/conditions/base/rpc';
import { ReturnValueTestProps } from '../../src/conditions/base/shared';
import {
TimeConditionMethod,
TimeConditionType,
} from '../../src/conditions/base/time';

export const aliceSecretKeyBytes = new Uint8Array([
55, 82, 190, 189, 203, 164, 60, 148, 36, 86, 46, 123, 63, 152, 215, 113, 174,
Expand All @@ -28,26 +36,26 @@ export const testReturnValueTest: ReturnValueTestProps = {
};

export const testTimeConditionObj: TimeConditionProps = {
conditionType: 'time',
conditionType: TimeConditionType,
returnValueTest: {
index: 0,
comparator: '>',
value: '100',
},
method: 'blocktime',
method: TimeConditionMethod,
chain: 5,
};

export const testRpcConditionObj: RpcConditionProps = {
conditionType: 'rpc',
conditionType: RpcConditionType,
chain: TEST_CHAIN_ID,
method: 'eth_getBalance',
parameters: ['0x1e988ba4692e52Bc50b375bcC8585b95c48AaD77'],
returnValueTest: testReturnValueTest,
};

export const testContractConditionObj: ContractConditionProps = {
conditionType: 'contract',
conditionType: ContractConditionType,
contractAddress: '0x0000000000000000000000000000000000000000',
chain: 5,
standardContractType: 'ERC20',
Expand Down

0 comments on commit 8f1dcf1

Please sign in to comment.