From 5ea1745850274f2cf22b3e15ef740d594c7b2d21 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Fri, 16 Jun 2023 14:47:15 -0400 Subject: [PATCH 1/6] Add version to condition data. Rename schema package to return-value package. --- .../base/{schema.ts => return-value.ts} | 0 src/conditions/base/rpc.ts | 5 +- src/conditions/condition-set.ts | 42 ++++++++++++--- test/unit/conditions/condition-set.test.ts | 52 +++++++++++++------ 4 files changed, 77 insertions(+), 22 deletions(-) rename src/conditions/base/{schema.ts => return-value.ts} (100%) diff --git a/src/conditions/base/schema.ts b/src/conditions/base/return-value.ts similarity index 100% rename from src/conditions/base/schema.ts rename to src/conditions/base/return-value.ts diff --git a/src/conditions/base/rpc.ts b/src/conditions/base/rpc.ts index 2eb7d2202..27ed5f0b7 100644 --- a/src/conditions/base/rpc.ts +++ b/src/conditions/base/rpc.ts @@ -3,7 +3,10 @@ import Joi from 'joi'; import { SUPPORTED_CHAINS } from '../const'; import { Condition } from './condition'; -import { ethAddressOrUserAddressSchema, returnValueTestSchema } from './schema'; +import { + ethAddressOrUserAddressSchema, + returnValueTestSchema, +} from './return-value'; const rpcMethodSchemas: Record = { eth_getBalance: Joi.array().items(ethAddressOrUserAddressSchema).required(), diff --git a/src/conditions/condition-set.ts b/src/conditions/condition-set.ts index 674a9d5b7..1bc406338 100644 --- a/src/conditions/condition-set.ts +++ b/src/conditions/condition-set.ts @@ -14,37 +14,67 @@ import { CompoundCondition } from './compound-condition'; import { ConditionContext } from './context'; export type ConditionSetJSON = { + version: string; condition: Record; }; export class ConditionSet { - constructor(public readonly condition: Condition) {} + static VERSION = '1.0.0'; + + constructor( + public readonly condition: Condition, + public readonly version: string = ConditionSet.VERSION + ) {} public toObj(): ConditionSetJSON { // TODO add version here const conditionData = this.condition.toObj(); - return { condition: conditionData }; + return { + version: this.version, + condition: conditionData, + }; } public static fromObj(obj: ConditionSetJSON): ConditionSet { + const version = obj.version; // version specific logic can go here + const receivedMajorVersion = version.split('.')[0]; + const currentMajorVersion = ConditionSet.VERSION.split('.')[0]; + if (receivedMajorVersion > currentMajorVersion) { + throw new Error( + `Version provided, ${version}, is incompatible with current version, ${ConditionSet.VERSION}` + ); + } + const underlyingConditionData = obj.condition; if (underlyingConditionData.operator) { - return new ConditionSet(new CompoundCondition(underlyingConditionData)); + return new ConditionSet( + new CompoundCondition(underlyingConditionData), + version + ); } if (underlyingConditionData.method) { if (underlyingConditionData.method === BLOCKTIME_METHOD) { - return new ConditionSet(new TimeCondition(underlyingConditionData)); + return new ConditionSet( + new TimeCondition(underlyingConditionData), + version + ); } if (underlyingConditionData.contractAddress) { - return new ConditionSet(new ContractCondition(underlyingConditionData)); + return new ConditionSet( + new ContractCondition(underlyingConditionData), + version + ); } if ((underlyingConditionData.method as string).startsWith('eth_')) { - return new ConditionSet(new RpcCondition(underlyingConditionData)); + return new ConditionSet( + new RpcCondition(underlyingConditionData), + version + ); } } diff --git a/test/unit/conditions/condition-set.test.ts b/test/unit/conditions/condition-set.test.ts index bc3104008..4a5de8848 100644 --- a/test/unit/conditions/condition-set.test.ts +++ b/test/unit/conditions/condition-set.test.ts @@ -76,37 +76,59 @@ describe('condition set', () => { }); }); + it('incompatible version', async () => { + const invalidVersion = '100.0.0'; + expect(() => { + ConditionSet.fromObj({ + version: invalidVersion, + condition: testTimeConditionObj, + }); + }).toThrow( + `Version provided, ${invalidVersion}, is incompatible with current version, ${ConditionSet.VERSION}` + ); + }); + it.each([ // no "operator" nor "method" value { - randoKey: 'randoValue', - otherKey: 'otherValue', + version: ConditionSet.VERSION, + condition: { + randoKey: 'randoValue', + otherKey: 'otherValue', + }, }, // invalid "method" and no "contractAddress" { - method: 'doWhatIWant', - returnValueTest: { - index: 0, - comparator: '>', - value: '100', + version: ConditionSet.VERSION, + condition: { + method: 'doWhatIWant', + returnValueTest: { + index: 0, + comparator: '>', + value: '100', + }, + chain: 5, }, - chain: 5, }, // condition with wrong method "method" and no contract address { - ...testTimeConditionObj, - method: 'doWhatIWant', + version: ConditionSet.VERSION, + condition: { + ...testTimeConditionObj, + method: 'doWhatIWant', + }, }, // rpc condition (no contract address) with disallowed method { - ...testRpcConditionObj, - method: 'isPolicyActive', + version: ConditionSet.VERSION, + condition: { + ...testRpcConditionObj, + method: 'isPolicyActive', + }, }, ])("can't determine condition type", async (invalidCondition) => { expect(() => { - ConditionSet.fromObj({ - condition: invalidCondition, - }); + ConditionSet.fromObj(invalidCondition); }).toThrow('unrecognized condition data'); }); From b3d0c396d5f71b5538aa1b1fa00afd58543699dc Mon Sep 17 00:00:00 2001 From: derekpierre Date: Mon, 19 Jun 2023 11:49:38 -0400 Subject: [PATCH 2/6] Update ConditionSet.equals() method and add tests. --- src/conditions/condition-set.ts | 5 +- test/unit/conditions/condition-set.test.ts | 447 ++++++++++++--------- 2 files changed, 253 insertions(+), 199 deletions(-) diff --git a/src/conditions/condition-set.ts b/src/conditions/condition-set.ts index 1bc406338..ea514de22 100644 --- a/src/conditions/condition-set.ts +++ b/src/conditions/condition-set.ts @@ -100,6 +100,9 @@ export class ConditionSet { } public equals(other: ConditionSet): boolean { - return objectEquals(this.condition.toObj(), other.condition.toObj()); + return ( + this.version === other.version && + objectEquals(this.condition.toObj(), other.condition.toObj()) + ); } } diff --git a/test/unit/conditions/condition-set.test.ts b/test/unit/conditions/condition-set.test.ts index 4a5de8848..6a2465f02 100644 --- a/test/unit/conditions/condition-set.test.ts +++ b/test/unit/conditions/condition-set.test.ts @@ -6,6 +6,7 @@ import { } from '../../../src/conditions/base'; import { USER_ADDRESS_PARAM } from '../../../src/conditions/const'; import { ERC721Balance } from '../../../src/conditions/predefined'; +import { toJSON } from '../../../src/utils'; import { TEST_CHAIN_ID, TEST_CONTRACT_ADDR, @@ -58,7 +59,48 @@ describe('condition set', () => { ], }); - describe('serialization', () => { + it('equality', async () => { + const conditionSetCurrentVersion = new ConditionSet(rpcCondition); + const conditionSetSameCurrentVerstion = new ConditionSet( + rpcCondition, + ConditionSet.VERSION + ); + // same version and condition + expect( + conditionSetCurrentVersion.equals(conditionSetSameCurrentVerstion) + ).toBeTruthy(); + + // same version but different condition + const conditionSetSameVersionDifferentCondition = new ConditionSet( + timeCondition + ); + expect( + conditionSetCurrentVersion.equals( + conditionSetSameVersionDifferentCondition + ) + ).not.toBeTruthy(); + + // different minor/patch version but same condition + const conditionSetOlderMinorVersion = new ConditionSet( + rpcCondition, + '0.1.0' + ); + const conditionSetOlderPatchVersion = new ConditionSet( + rpcCondition, + '0.0.1' + ); + expect( + conditionSetCurrentVersion.equals(conditionSetOlderMinorVersion) + ).not.toBeTruthy(); + expect( + conditionSetCurrentVersion.equals(conditionSetOlderPatchVersion) + ).not.toBeTruthy(); + expect( + conditionSetOlderMinorVersion.equals(conditionSetOlderPatchVersion) + ).not.toBeTruthy(); + }); + + describe('serialization / deserialization', () => { it.each([ erc721BalanceCondition, contractConditionNoAbi, @@ -70,223 +112,232 @@ describe('condition set', () => { const conditionSet = new ConditionSet(condition); const conditionSetJson = conditionSet.toJson(); expect(conditionSetJson).toBeDefined(); + expect(conditionSetJson).toContain('version'); + expect(conditionSetJson).toContain(ConditionSet.VERSION); + expect(conditionSetJson).toContain('condition'); + expect(conditionSetJson).toContain(toJSON(condition.toObj())); + const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); expect(conditionSetFromJson).toBeDefined(); expect(conditionSetFromJson.equals(conditionSetFromJson)).toBeTruthy(); }); - }); - it('incompatible version', async () => { - const invalidVersion = '100.0.0'; - expect(() => { - ConditionSet.fromObj({ - version: invalidVersion, - condition: testTimeConditionObj, - }); - }).toThrow( - `Version provided, ${invalidVersion}, is incompatible with current version, ${ConditionSet.VERSION}` - ); - }); + it('incompatible version', async () => { + const invalidVersion = '100.0.0'; + expect(() => { + ConditionSet.fromObj({ + version: invalidVersion, + condition: testTimeConditionObj, + }); + }).toThrow( + `Version provided, ${invalidVersion}, is incompatible with current version, ${ConditionSet.VERSION}` + ); + }); - it.each([ - // no "operator" nor "method" value - { - version: ConditionSet.VERSION, - condition: { - randoKey: 'randoValue', - otherKey: 'otherValue', + it.each([ + // no "operator" nor "method" value + { + version: ConditionSet.VERSION, + condition: { + randoKey: 'randoValue', + otherKey: 'otherValue', + }, }, - }, - // invalid "method" and no "contractAddress" - { - version: ConditionSet.VERSION, - condition: { - method: 'doWhatIWant', - returnValueTest: { - index: 0, - comparator: '>', - value: '100', + // invalid "method" and no "contractAddress" + { + version: ConditionSet.VERSION, + condition: { + method: 'doWhatIWant', + returnValueTest: { + index: 0, + comparator: '>', + value: '100', + }, + chain: 5, }, - chain: 5, }, - }, - // condition with wrong method "method" and no contract address - { - version: ConditionSet.VERSION, - condition: { - ...testTimeConditionObj, - method: 'doWhatIWant', + // condition with wrong method "method" and no contract address + { + version: ConditionSet.VERSION, + condition: { + ...testTimeConditionObj, + method: 'doWhatIWant', + }, }, - }, - // rpc condition (no contract address) with disallowed method - { - version: ConditionSet.VERSION, - condition: { - ...testRpcConditionObj, - method: 'isPolicyActive', + // rpc condition (no contract address) with disallowed method + { + version: ConditionSet.VERSION, + condition: { + ...testRpcConditionObj, + method: 'isPolicyActive', + }, }, - }, - ])("can't determine condition type", async (invalidCondition) => { - expect(() => { - ConditionSet.fromObj(invalidCondition); - }).toThrow('unrecognized condition data'); - }); + ])("can't determine condition type", async (invalidCondition) => { + expect(() => { + ConditionSet.fromObj(invalidCondition); + }).toThrow('unrecognized condition data'); + }); - it('erc721 condition serialization', async () => { - const conditionSet = new ConditionSet(erc721BalanceCondition); - - const erc721BalanceConditionObj = erc721BalanceCondition.toObj(); - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('contractAddress'); - expect(conditionSetJson).toContain( - erc721BalanceConditionObj.contractAddress - ); - expect(conditionSetJson).toContain('standardContractType'); - expect(conditionSetJson).toContain('ERC721'); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(erc721BalanceConditionObj.method); - expect(conditionSetJson).toContain('returnValueTest'); - - expect(conditionSetJson).not.toContain('functionAbi'); - expect(conditionSetJson).not.toContain('operator'); - expect(conditionSetJson).not.toContain('operands'); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(ContractCondition); - }); + it('erc721 condition serialization', async () => { + const conditionSet = new ConditionSet(erc721BalanceCondition); - it('contract condition no abi serialization', async () => { - const conditionSet = new ConditionSet(contractConditionNoAbi); + const erc721BalanceConditionObj = erc721BalanceCondition.toObj(); + const conditionSetJson = conditionSet.toJson(); + expect(conditionSetJson).toBeDefined(); + expect(conditionSetJson).toContain('chain'); + expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionSetJson).toContain('contractAddress'); + expect(conditionSetJson).toContain( + erc721BalanceConditionObj.contractAddress + ); + expect(conditionSetJson).toContain('standardContractType'); + expect(conditionSetJson).toContain('ERC721'); + expect(conditionSetJson).toContain('method'); + expect(conditionSetJson).toContain(erc721BalanceConditionObj.method); + expect(conditionSetJson).toContain('returnValueTest'); - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('contractAddress'); - expect(conditionSetJson).toContain( - testContractConditionObj.contractAddress - ); - expect(conditionSetJson).toContain('standardContractType'); - expect(conditionSetJson).toContain( - testContractConditionObj.standardContractType - ); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(testContractConditionObj.method); - expect(conditionSetJson).toContain('parameters'); - expect(conditionSetJson).toContain(testContractConditionObj.parameters[0]); - expect(conditionSetJson).toContain('returnValueTest'); - expect(conditionSetJson).not.toContain('functionAbi'); - expect(conditionSetJson).not.toContain('operator'); - expect(conditionSetJson).not.toContain('operands'); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(ContractCondition); - }); + expect(conditionSetJson).not.toContain('functionAbi'); + expect(conditionSetJson).not.toContain('operator'); + expect(conditionSetJson).not.toContain('operands'); - it('contract condition with abi serialization', async () => { - const conditionSet = new ConditionSet(contractConditionWithAbi); + const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); + expect(conditionSetFromJson).toBeDefined(); + expect(conditionSetFromJson.condition).toBeInstanceOf(ContractCondition); + }); - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('contractAddress'); - expect(conditionSetJson).toContain( - contractConditionWithAbiObj.contractAddress - ); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(contractConditionWithAbiObj.method); - expect(conditionSetJson).toContain('parameters'); - expect(conditionSetJson).toContain( - contractConditionWithAbiObj.parameters[0] - ); - expect(conditionSetJson).toContain( - contractConditionWithAbiObj.parameters[1] - ); - expect(conditionSetJson).toContain('returnValueTest'); - expect(conditionSetJson).toContain('functionAbi'); + it('contract condition no abi serialization', async () => { + const conditionSet = new ConditionSet(contractConditionNoAbi); - expect(conditionSetJson).not.toContain('standardContractType'); - expect(conditionSetJson).not.toContain('operator'); - expect(conditionSetJson).not.toContain('operands'); + const conditionSetJson = conditionSet.toJson(); + expect(conditionSetJson).toBeDefined(); + expect(conditionSetJson).toContain('chain'); + expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionSetJson).toContain('contractAddress'); + expect(conditionSetJson).toContain( + testContractConditionObj.contractAddress + ); + expect(conditionSetJson).toContain('standardContractType'); + expect(conditionSetJson).toContain( + testContractConditionObj.standardContractType + ); + expect(conditionSetJson).toContain('method'); + expect(conditionSetJson).toContain(testContractConditionObj.method); + expect(conditionSetJson).toContain('parameters'); + expect(conditionSetJson).toContain( + testContractConditionObj.parameters[0] + ); + expect(conditionSetJson).toContain('returnValueTest'); + expect(conditionSetJson).not.toContain('functionAbi'); + expect(conditionSetJson).not.toContain('operator'); + expect(conditionSetJson).not.toContain('operands'); - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(ContractCondition); - }); + const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); + expect(conditionSetFromJson).toBeDefined(); + expect(conditionSetFromJson.condition).toBeInstanceOf(ContractCondition); + }); - it('time condition serialization', async () => { - const conditionSet = new ConditionSet(timeCondition); - - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(testTimeConditionObj.method); - expect(conditionSetJson).toContain('returnValueTest'); - expect(conditionSetJson).not.toContain('parameters'); - expect(conditionSetJson).not.toContain('contractAddress'); - expect(conditionSetJson).not.toContain('standardContractType'); - expect(conditionSetJson).not.toContain('functionAbi'); - expect(conditionSetJson).not.toContain('operator'); - expect(conditionSetJson).not.toContain('operands'); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(TimeCondition); - }); + it('contract condition with abi serialization', async () => { + const conditionSet = new ConditionSet(contractConditionWithAbi); - it('rpc condition serialization', async () => { - const conditionSet = new ConditionSet(rpcCondition); - - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(testRpcConditionObj.method); - expect(conditionSetJson).toContain('parameters'); - expect(conditionSetJson).toContain(testRpcConditionObj.parameters[0]); - expect(conditionSetJson).toContain('returnValueTest'); - expect(conditionSetJson).not.toContain('contractAddress'); - expect(conditionSetJson).not.toContain('standardContractType'); - expect(conditionSetJson).not.toContain('functionAbi'); - expect(conditionSetJson).not.toContain('operator'); - expect(conditionSetJson).not.toContain('operands'); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(RpcCondition); - }); + const conditionSetJson = conditionSet.toJson(); + expect(conditionSetJson).toBeDefined(); + expect(conditionSetJson).toContain('chain'); + expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionSetJson).toContain('contractAddress'); + expect(conditionSetJson).toContain( + contractConditionWithAbiObj.contractAddress + ); + expect(conditionSetJson).toContain('method'); + expect(conditionSetJson).toContain(contractConditionWithAbiObj.method); + expect(conditionSetJson).toContain('parameters'); + expect(conditionSetJson).toContain( + contractConditionWithAbiObj.parameters[0] + ); + expect(conditionSetJson).toContain( + contractConditionWithAbiObj.parameters[1] + ); + expect(conditionSetJson).toContain('returnValueTest'); + expect(conditionSetJson).toContain('functionAbi'); + + expect(conditionSetJson).not.toContain('standardContractType'); + expect(conditionSetJson).not.toContain('operator'); + expect(conditionSetJson).not.toContain('operands'); + + const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); + expect(conditionSetFromJson).toBeDefined(); + expect(conditionSetFromJson.condition).toBeInstanceOf(ContractCondition); + }); + + it('time condition serialization', async () => { + const conditionSet = new ConditionSet(timeCondition); + + const conditionSetJson = conditionSet.toJson(); + expect(conditionSetJson).toBeDefined(); + expect(conditionSetJson).toContain('chain'); + expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionSetJson).toContain('method'); + expect(conditionSetJson).toContain(testTimeConditionObj.method); + expect(conditionSetJson).toContain('returnValueTest'); + expect(conditionSetJson).not.toContain('parameters'); + expect(conditionSetJson).not.toContain('contractAddress'); + expect(conditionSetJson).not.toContain('standardContractType'); + expect(conditionSetJson).not.toContain('functionAbi'); + expect(conditionSetJson).not.toContain('operator'); + expect(conditionSetJson).not.toContain('operands'); - it('compound condition serialization', async () => { - const conditionSet = new ConditionSet(compoundCondition); - const compoundConditionObj = compoundCondition.toObj(); - - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toContain('operator'); - expect(conditionSetJson).toContain(compoundConditionObj.operator); - expect(conditionSetJson).toContain('operands'); - - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(testRpcConditionObj.method); - expect(conditionSetJson).toContain(testTimeConditionObj.method); - expect(conditionSetJson).toContain(testContractConditionObj.method); - expect(conditionSetJson).toContain('parameters'); - expect(conditionSetJson).toContain(testRpcConditionObj.parameters[0]); - expect(conditionSetJson).toContain(testContractConditionObj.parameters[0]); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(CompoundCondition); + const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); + expect(conditionSetFromJson).toBeDefined(); + expect(conditionSetFromJson.condition).toBeInstanceOf(TimeCondition); + }); + + it('rpc condition serialization', async () => { + const conditionSet = new ConditionSet(rpcCondition); + + const conditionSetJson = conditionSet.toJson(); + expect(conditionSetJson).toBeDefined(); + expect(conditionSetJson).toContain('chain'); + expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionSetJson).toContain('method'); + expect(conditionSetJson).toContain(testRpcConditionObj.method); + expect(conditionSetJson).toContain('parameters'); + expect(conditionSetJson).toContain(testRpcConditionObj.parameters[0]); + expect(conditionSetJson).toContain('returnValueTest'); + expect(conditionSetJson).not.toContain('contractAddress'); + expect(conditionSetJson).not.toContain('standardContractType'); + expect(conditionSetJson).not.toContain('functionAbi'); + expect(conditionSetJson).not.toContain('operator'); + expect(conditionSetJson).not.toContain('operands'); + + const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); + expect(conditionSetFromJson).toBeDefined(); + expect(conditionSetFromJson.condition).toBeInstanceOf(RpcCondition); + }); + + it('compound condition serialization', async () => { + const conditionSet = new ConditionSet(compoundCondition); + const compoundConditionObj = compoundCondition.toObj(); + + const conditionSetJson = conditionSet.toJson(); + expect(conditionSetJson).toContain('operator'); + expect(conditionSetJson).toContain(compoundConditionObj.operator); + expect(conditionSetJson).toContain('operands'); + + expect(conditionSetJson).toBeDefined(); + expect(conditionSetJson).toContain('chain'); + expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionSetJson).toContain('method'); + expect(conditionSetJson).toContain(testRpcConditionObj.method); + expect(conditionSetJson).toContain(testTimeConditionObj.method); + expect(conditionSetJson).toContain(testContractConditionObj.method); + expect(conditionSetJson).toContain('parameters'); + expect(conditionSetJson).toContain(testRpcConditionObj.parameters[0]); + expect(conditionSetJson).toContain( + testContractConditionObj.parameters[0] + ); + + const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); + expect(conditionSetFromJson).toBeDefined(); + expect(conditionSetFromJson.condition).toBeInstanceOf(CompoundCondition); + }); }); }); From 80d43472766b82c249bc1020799d24e037f9e860 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Mon, 19 Jun 2023 13:26:51 -0400 Subject: [PATCH 3/6] Rename `ConditionSet` to `ConditionLingo`. --- src/characters/cbd-recipient.ts | 16 +- src/characters/enrico.ts | 10 +- .../{condition-set.ts => condition-expr.ts} | 28 +- src/conditions/index.ts | 4 +- src/sdk/strategy/cbd-strategy.ts | 8 +- src/sdk/strategy/pre-strategy.ts | 6 +- test/docs/cbd.test.ts | 6 +- test/integration/enrico.test.ts | 8 +- test/integration/pre.test.ts | 4 +- test/unit/cbd-strategy.test.ts | 8 +- test/unit/conditions/base/contract.test.ts | 9 +- test/unit/conditions/condition-expr.test.ts | 353 ++++++++++++++++++ test/unit/conditions/condition-set.test.ts | 343 ----------------- test/unit/conditions/context.test.ts | 12 +- test/unit/pre-strategy.test.ts | 6 +- 15 files changed, 417 insertions(+), 404 deletions(-) rename src/conditions/{condition-set.ts => condition-expr.ts} (75%) create mode 100644 test/unit/conditions/condition-expr.test.ts delete mode 100644 test/unit/conditions/condition-set.test.ts diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts index c461dc102..1a350755f 100644 --- a/src/characters/cbd-recipient.ts +++ b/src/characters/cbd-recipient.ts @@ -15,7 +15,7 @@ import { import { ethers } from 'ethers'; import { DkgCoordinatorAgent, DkgParticipant } from '../agents/coordinator'; -import { ConditionSet } from '../conditions'; +import { ConditionExpression } from '../conditions'; import { DkgRitual, getCombineDecryptionSharesFunction, @@ -54,14 +54,14 @@ export class CbdTDecDecrypter { // Retrieve and decrypt ciphertext using provider and condition set public async retrieveAndDecrypt( provider: ethers.providers.Web3Provider, - conditionSet: ConditionSet, + conditionExpr: ConditionExpression, variant: number, ciphertext: Ciphertext, aad: Uint8Array ): Promise { const decryptionShares = await this.retrieve( provider, - conditionSet, + conditionExpr, variant, ciphertext ); @@ -82,7 +82,7 @@ export class CbdTDecDecrypter { // Retrieve decryption shares public async retrieve( provider: ethers.providers.Web3Provider, - conditionSet: ConditionSet, + conditionExpr: ConditionExpression, variant: number, ciphertext: Ciphertext ): Promise { @@ -90,12 +90,12 @@ export class CbdTDecDecrypter { provider, this.ritualId ); - const contextStr = await conditionSet.buildContext(provider).toJson(); + const contextStr = await conditionExpr.buildContext(provider).toJson(); const { sharedSecrets, encryptedRequests } = this.makeDecryptionRequests( this.ritualId, variant, ciphertext, - conditionSet, + conditionExpr, contextStr, dkgParticipants ); @@ -150,7 +150,7 @@ export class CbdTDecDecrypter { ritualId: number, variant: number, ciphertext: Ciphertext, - conditionSet: ConditionSet, + conditionExpr: ConditionExpression, contextStr: string, dkgParticipants: Array ): { @@ -161,7 +161,7 @@ export class CbdTDecDecrypter { ritualId, variant, ciphertext, - conditionSet.toWASMConditions(), + conditionExpr.toWASMConditions(), new Context(contextStr) ); diff --git a/src/characters/enrico.ts b/src/characters/enrico.ts index a089455d9..2f8115937 100644 --- a/src/characters/enrico.ts +++ b/src/characters/enrico.ts @@ -7,19 +7,19 @@ import { SecretKey, } from '@nucypher/nucypher-core'; -import { ConditionSet } from '../conditions'; +import { ConditionExpression } from '../conditions'; import { Keyring } from '../keyring'; import { toBytes } from '../utils'; export class Enrico { public readonly encryptingKey: PublicKey | DkgPublicKey; private readonly keyring: Keyring; - public conditions?: ConditionSet; + public conditions?: ConditionExpression; constructor( encryptingKey: PublicKey | DkgPublicKey, verifyingKey?: SecretKey, - conditions?: ConditionSet + conditions?: ConditionExpression ) { this.encryptingKey = encryptingKey; this.keyring = new Keyring(verifyingKey ?? SecretKey.random()); @@ -32,7 +32,7 @@ export class Enrico { public encryptMessagePre( plaintext: Uint8Array | string, - withConditions?: ConditionSet + withConditions?: ConditionExpression ): MessageKit { if (!withConditions) { withConditions = this.conditions; @@ -51,7 +51,7 @@ export class Enrico { public encryptMessageCbd( plaintext: Uint8Array | string, - withConditions?: ConditionSet + withConditions?: ConditionExpression ): { ciphertext: Ciphertext; aad: Uint8Array } { if (!withConditions) { withConditions = this.conditions; diff --git a/src/conditions/condition-set.ts b/src/conditions/condition-expr.ts similarity index 75% rename from src/conditions/condition-set.ts rename to src/conditions/condition-expr.ts index ea514de22..d584a26fd 100644 --- a/src/conditions/condition-set.ts +++ b/src/conditions/condition-expr.ts @@ -13,20 +13,20 @@ import { BLOCKTIME_METHOD } from './base/time'; import { CompoundCondition } from './compound-condition'; import { ConditionContext } from './context'; -export type ConditionSetJSON = { +export type ConditionExpressionJSON = { version: string; condition: Record; }; -export class ConditionSet { +export class ConditionExpression { static VERSION = '1.0.0'; constructor( public readonly condition: Condition, - public readonly version: string = ConditionSet.VERSION + public readonly version: string = ConditionExpression.VERSION ) {} - public toObj(): ConditionSetJSON { + public toObj(): ConditionExpressionJSON { // TODO add version here const conditionData = this.condition.toObj(); return { @@ -35,21 +35,21 @@ export class ConditionSet { }; } - public static fromObj(obj: ConditionSetJSON): ConditionSet { + public static fromObj(obj: ConditionExpressionJSON): ConditionExpression { const version = obj.version; // version specific logic can go here const receivedMajorVersion = version.split('.')[0]; - const currentMajorVersion = ConditionSet.VERSION.split('.')[0]; + const currentMajorVersion = ConditionExpression.VERSION.split('.')[0]; if (receivedMajorVersion > currentMajorVersion) { throw new Error( - `Version provided, ${version}, is incompatible with current version, ${ConditionSet.VERSION}` + `Version provided, ${version}, is incompatible with current version, ${ConditionExpression.VERSION}` ); } const underlyingConditionData = obj.condition; if (underlyingConditionData.operator) { - return new ConditionSet( + return new ConditionExpression( new CompoundCondition(underlyingConditionData), version ); @@ -57,21 +57,21 @@ export class ConditionSet { if (underlyingConditionData.method) { if (underlyingConditionData.method === BLOCKTIME_METHOD) { - return new ConditionSet( + return new ConditionExpression( new TimeCondition(underlyingConditionData), version ); } if (underlyingConditionData.contractAddress) { - return new ConditionSet( + return new ConditionExpression( new ContractCondition(underlyingConditionData), version ); } if ((underlyingConditionData.method as string).startsWith('eth_')) { - return new ConditionSet( + return new ConditionExpression( new RpcCondition(underlyingConditionData), version ); @@ -85,8 +85,8 @@ export class ConditionSet { return toJSON(this.toObj()); } - public static fromJSON(json: string): ConditionSet { - return ConditionSet.fromObj(JSON.parse(json)); + public static fromJSON(json: string): ConditionExpression { + return ConditionExpression.fromObj(JSON.parse(json)); } public toWASMConditions(): WASMConditions { @@ -99,7 +99,7 @@ export class ConditionSet { return new ConditionContext([this.condition], provider); } - public equals(other: ConditionSet): boolean { + public equals(other: ConditionExpression): boolean { return ( this.version === other.version && objectEquals(this.condition.toObj(), other.condition.toObj()) diff --git a/src/conditions/index.ts b/src/conditions/index.ts index 7e335a0e0..2d5661fdb 100644 --- a/src/conditions/index.ts +++ b/src/conditions/index.ts @@ -3,8 +3,8 @@ import * as predefined from './predefined'; export { predefined, base }; export { Condition } from './base/condition'; -export type { ConditionSetJSON } from './condition-set'; -export { ConditionSet } from './condition-set'; +export type { ConditionExpressionJSON } from './condition-expr'; +export { ConditionExpression } from './condition-expr'; export { CompoundCondition } from './compound-condition'; export type { CustomContextParam } from './context'; export { ConditionContext } from './context'; diff --git a/src/sdk/strategy/cbd-strategy.ts b/src/sdk/strategy/cbd-strategy.ts index 4b1ae65b9..a56f93e72 100644 --- a/src/sdk/strategy/cbd-strategy.ts +++ b/src/sdk/strategy/cbd-strategy.ts @@ -7,14 +7,14 @@ import { CbdTDecDecrypterJSON, } from '../../characters/cbd-recipient'; import { Enrico } from '../../characters/enrico'; -import { ConditionSet, ConditionSetJSON } from '../../conditions'; +import { ConditionExpression, ConditionExpressionJSON } from '../../conditions'; import { DkgClient, DkgRitual } from '../../dkg'; import { fromJSON, toJSON } from '../../utils'; import { Cohort, CohortJSON } from '../cohort'; export type CbdStrategyJSON = { cohort: CohortJSON; - conditionSet?: ConditionSetJSON | undefined; + conditionExpr?: ConditionExpressionJSON | undefined; }; export type DeployedStrategyJSON = { @@ -81,8 +81,8 @@ export class DeployedCbdStrategy { return new DeployedCbdStrategy(decrypter, dkgRitual.dkgPublicKey); } - public makeEncrypter(conditionSet: ConditionSet): Enrico { - return new Enrico(this.dkgPublicKey, undefined, conditionSet); + public makeEncrypter(conditionExpr: ConditionExpression): Enrico { + return new Enrico(this.dkgPublicKey, undefined, conditionExpr); } public static fromJSON(json: string) { diff --git a/src/sdk/strategy/pre-strategy.ts b/src/sdk/strategy/pre-strategy.ts index 40f0cb12d..24d46c132 100644 --- a/src/sdk/strategy/pre-strategy.ts +++ b/src/sdk/strategy/pre-strategy.ts @@ -8,7 +8,7 @@ import { PreTDecDecrypter, PreTDecDecrypterJSON, } from '../../characters/pre-recipient'; -import { ConditionSet } from '../../conditions'; +import { ConditionExpression } from '../../conditions'; import { EnactedPolicy } from '../../policies/policy'; import { base64ToU8Receiver, bytesEquals, toJSON } from '../../utils'; import { Cohort, CohortJSON } from '../cohort'; @@ -165,8 +165,8 @@ export class DeployedPreStrategy { return new DeployedPreStrategy(cohort, decrypter, policy.policyKey); } - public makeEncrypter(conditionSet: ConditionSet): Enrico { - return new Enrico(this.policyKey, undefined, conditionSet); + public makeEncrypter(conditionExpr: ConditionExpression): Enrico { + return new Enrico(this.policyKey, undefined, conditionExpr); } public static fromJSON(json: string) { diff --git a/test/docs/cbd.test.ts b/test/docs/cbd.test.ts index b5de452be..cac579632 100644 --- a/test/docs/cbd.test.ts +++ b/test/docs/cbd.test.ts @@ -19,7 +19,7 @@ import { const { predefined: { ERC721Ownership }, base: { ContractCondition }, - ConditionSet, + ConditionExpression, } = conditions; describe('Get Started (CBD PoC)', () => { @@ -74,7 +74,7 @@ describe('Get Started (CBD PoC)', () => { parameters: [5954], }); - const conditions = new ConditionSet( + const conditions = new ConditionExpression( NFTOwnership // Other conditions can be added here ); @@ -101,7 +101,7 @@ describe('Get Started (CBD PoC)', () => { }, }; const NFTBalance = new ContractCondition(NFTBalanceConfig); - const newConditions = new ConditionSet(NFTBalance); + const newConditions = new ConditionExpression(NFTBalance); const plaintext = 'this is a secret'; const encrypter = newDeployed.makeEncrypter(newConditions); const encryptedMessageKit = encrypter.encryptMessagePre(plaintext); diff --git a/test/integration/enrico.test.ts b/test/integration/enrico.test.ts index 86eb4f18e..d28ea9f77 100644 --- a/test/integration/enrico.test.ts +++ b/test/integration/enrico.test.ts @@ -13,7 +13,7 @@ import { const { predefined: { ERC721Ownership }, - ConditionSet, + ConditionExpression, } = conditions; describe('enrico', () => { @@ -105,7 +105,7 @@ describe('enrico', () => { chain: 5, }); - const conditions = new ConditionSet(ownsBufficornNFT); + const conditions = new ConditionExpression(ownsBufficornNFT); const enrico = new Enrico(policyKey, undefined, conditions); const encrypted = enrico.encryptMessagePre(toBytes(message)); @@ -135,8 +135,8 @@ describe('enrico', () => { parameters: [6969], }); - const conditions = new ConditionSet(ownsBufficornNFT); - const updatedConditions = new ConditionSet(ownsNonsenseNFT); + const conditions = new ConditionExpression(ownsBufficornNFT); + const updatedConditions = new ConditionExpression(ownsNonsenseNFT); const enrico = new Enrico(policyKey, undefined, conditions); const encrypted = enrico.encryptMessagePre( diff --git a/test/integration/pre.test.ts b/test/integration/pre.test.ts index 1db2b8df2..e3087e16c 100644 --- a/test/integration/pre.test.ts +++ b/test/integration/pre.test.ts @@ -8,7 +8,7 @@ import { fakeAlice, fakeBob, fakeUrsulas, reencryptKFrags } from '../utils'; const { predefined: { ERC721Ownership }, - ConditionSet, + ConditionExpression, } = conditions; describe('proxy reencryption', () => { @@ -94,7 +94,7 @@ describe('proxy reencryption', () => { chain: 1, parameters: [1], }); - const conditionsSet = new ConditionSet( + const conditionsSet = new ConditionExpression( new CompoundCondition({ operator: 'or', operands: [genuineUndead.toObj(), gnomePals.toObj()], diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts index 743c65d0b..1e45606a4 100644 --- a/test/unit/cbd-strategy.test.ts +++ b/test/unit/cbd-strategy.test.ts @@ -27,7 +27,7 @@ import { aliceSecretKeyBytes } from './testVariables'; const { predefined: { ERC721Ownership }, - ConditionSet, + ConditionExpression, } = conditions; // Shared test variables @@ -38,7 +38,7 @@ const ownsNFT = new ERC721Ownership({ parameters: [3591], chain: 5, }); -const conditionSet = new ConditionSet(ownsNFT); +const conditionExpr = new ConditionExpression(ownsNFT); const ursulas = fakeUrsulas().slice(0, 3); const variant = FerveoVariant.Precomputed; @@ -105,7 +105,7 @@ describe('CbdDeployedStrategy', () => { const message = 'this is a secret'; const { ciphertext, aad } = deployedStrategy - .makeEncrypter(conditionSet) + .makeEncrypter(conditionExpr) .encryptMessageCbd(message); // Setup mocks for `retrieveAndDecrypt` @@ -134,7 +134,7 @@ describe('CbdDeployedStrategy', () => { const decryptedMessage = await deployedStrategy.decrypter.retrieveAndDecrypt( aliceProvider, - conditionSet, + conditionExpr, variant, ciphertext, aad diff --git a/test/unit/conditions/base/contract.test.ts b/test/unit/conditions/base/contract.test.ts index 3be6bdcd6..25fdddd8b 100644 --- a/test/unit/conditions/base/contract.test.ts +++ b/test/unit/conditions/base/contract.test.ts @@ -1,6 +1,9 @@ import { SecretKey } from '@nucypher/nucypher-core'; -import { ConditionSet, CustomContextParam } from '../../../../src/conditions'; +import { + ConditionExpression, + CustomContextParam, +} from '../../../../src/conditions'; import { ContractCondition } from '../../../../src/conditions/base'; import { USER_ADDRESS_PARAM } from '../../../../src/conditions/const'; import { fakeWeb3Provider } from '../../../utils'; @@ -134,8 +137,8 @@ describe('supports custom function abi', () => { }; const contractCondition = new ContractCondition(contractConditionObj); const web3Provider = fakeWeb3Provider(SecretKey.random().toBEBytes()); - const conditionSet = new ConditionSet(contractCondition); - const conditionContext = conditionSet.buildContext(web3Provider); + const conditionExpr = new ConditionExpression(contractCondition); + const conditionContext = conditionExpr.buildContext(web3Provider); const myCustomParam = ':customParam'; const customParams: Record = {}; customParams[myCustomParam] = 1234; diff --git a/test/unit/conditions/condition-expr.test.ts b/test/unit/conditions/condition-expr.test.ts new file mode 100644 index 000000000..429aad867 --- /dev/null +++ b/test/unit/conditions/condition-expr.test.ts @@ -0,0 +1,353 @@ +import { + CompoundCondition, + ConditionExpression, +} from '../../../src/conditions'; +import { + ContractCondition, + RpcCondition, + TimeCondition, +} from '../../../src/conditions/base'; +import { USER_ADDRESS_PARAM } from '../../../src/conditions/const'; +import { ERC721Balance } from '../../../src/conditions/predefined'; +import { toJSON } from '../../../src/utils'; +import { + TEST_CHAIN_ID, + TEST_CONTRACT_ADDR, + testFunctionAbi, + testReturnValueTest, +} from '../testVariables'; +import { + testContractConditionObj, + testRpcConditionObj, + testTimeConditionObj, +} from '../testVariables'; + +describe('condition set', () => { + const erc721BalanceCondition = new ERC721Balance({ + chain: TEST_CHAIN_ID, + contractAddress: TEST_CONTRACT_ADDR, + }); + + const contractConditionNoAbi = new ContractCondition( + testContractConditionObj + ); + + const customParamKey = ':customParam'; + const contractConditionWithAbiObj = { + ...testContractConditionObj, + standardContractType: undefined, + functionAbi: testFunctionAbi, + method: testFunctionAbi.name, + parameters: [USER_ADDRESS_PARAM, customParamKey], + returnValueTest: { + ...testReturnValueTest, + }, + }; + const contractConditionWithAbi = new ContractCondition( + contractConditionWithAbiObj + ); + + const rpcCondition = new RpcCondition(testRpcConditionObj); + const timeCondition = new TimeCondition(testTimeConditionObj); + const compoundCondition = new CompoundCondition({ + operator: 'and', + operands: [ + testContractConditionObj, + testTimeConditionObj, + testRpcConditionObj, + { + operator: 'or', + operands: [testTimeConditionObj, testContractConditionObj], + }, + ], + }); + + it('equality', async () => { + const conditionExprCurrentVersion = new ConditionExpression(rpcCondition); + const conditionExprSameCurrentVerstion = new ConditionExpression( + rpcCondition, + ConditionExpression.VERSION + ); + // same version and condition + expect( + conditionExprCurrentVersion.equals(conditionExprSameCurrentVerstion) + ).toBeTruthy(); + + // same version but different condition + const conditionExprSameVersionDifferentCondition = new ConditionExpression( + timeCondition + ); + expect( + conditionExprCurrentVersion.equals( + conditionExprSameVersionDifferentCondition + ) + ).not.toBeTruthy(); + + // different minor/patch version but same condition + const conditionExprOlderMinorVersion = new ConditionExpression( + rpcCondition, + '0.1.0' + ); + const conditionExprOlderPatchVersion = new ConditionExpression( + rpcCondition, + '0.0.1' + ); + expect( + conditionExprCurrentVersion.equals(conditionExprOlderMinorVersion) + ).not.toBeTruthy(); + expect( + conditionExprCurrentVersion.equals(conditionExprOlderPatchVersion) + ).not.toBeTruthy(); + expect( + conditionExprOlderMinorVersion.equals(conditionExprOlderPatchVersion) + ).not.toBeTruthy(); + }); + + describe('serialization / deserialization', () => { + it.each([ + erc721BalanceCondition, + contractConditionNoAbi, + contractConditionWithAbi, + rpcCondition, + timeCondition, + compoundCondition, + ])('serializes to and from json', async (condition) => { + const conditionExpr = new ConditionExpression(condition); + const conditionExprJson = conditionExpr.toJson(); + expect(conditionExprJson).toBeDefined(); + expect(conditionExprJson).toContain('version'); + expect(conditionExprJson).toContain(ConditionExpression.VERSION); + expect(conditionExprJson).toContain('condition'); + expect(conditionExprJson).toContain(toJSON(condition.toObj())); + + const conditionExprFromJson = + ConditionExpression.fromJSON(conditionExprJson); + expect(conditionExprFromJson).toBeDefined(); + expect(conditionExprFromJson.equals(conditionExprFromJson)).toBeTruthy(); + }); + + it('incompatible version', async () => { + const invalidVersion = '100.0.0'; + expect(() => { + ConditionExpression.fromObj({ + version: invalidVersion, + condition: testTimeConditionObj, + }); + }).toThrow( + `Version provided, ${invalidVersion}, is incompatible with current version, ${ConditionExpression.VERSION}` + ); + }); + + it.each([ + // no "operator" nor "method" value + { + version: ConditionExpression.VERSION, + condition: { + randoKey: 'randoValue', + otherKey: 'otherValue', + }, + }, + // invalid "method" and no "contractAddress" + { + version: ConditionExpression.VERSION, + condition: { + method: 'doWhatIWant', + returnValueTest: { + index: 0, + comparator: '>', + value: '100', + }, + chain: 5, + }, + }, + // condition with wrong method "method" and no contract address + { + version: ConditionExpression.VERSION, + condition: { + ...testTimeConditionObj, + method: 'doWhatIWant', + }, + }, + // rpc condition (no contract address) with disallowed method + { + version: ConditionExpression.VERSION, + condition: { + ...testRpcConditionObj, + method: 'isPolicyActive', + }, + }, + ])("can't determine condition type", async (invalidCondition) => { + expect(() => { + ConditionExpression.fromObj(invalidCondition); + }).toThrow('unrecognized condition data'); + }); + + it('erc721 condition serialization', async () => { + const conditionExpr = new ConditionExpression(erc721BalanceCondition); + + const erc721BalanceConditionObj = erc721BalanceCondition.toObj(); + const conditionExprJson = conditionExpr.toJson(); + expect(conditionExprJson).toBeDefined(); + expect(conditionExprJson).toContain('chain'); + expect(conditionExprJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionExprJson).toContain('contractAddress'); + expect(conditionExprJson).toContain( + erc721BalanceConditionObj.contractAddress + ); + expect(conditionExprJson).toContain('standardContractType'); + expect(conditionExprJson).toContain('ERC721'); + expect(conditionExprJson).toContain('method'); + expect(conditionExprJson).toContain(erc721BalanceConditionObj.method); + expect(conditionExprJson).toContain('returnValueTest'); + + expect(conditionExprJson).not.toContain('functionAbi'); + expect(conditionExprJson).not.toContain('operator'); + expect(conditionExprJson).not.toContain('operands'); + + const conditionExprFromJson = + ConditionExpression.fromJSON(conditionExprJson); + expect(conditionExprFromJson).toBeDefined(); + expect(conditionExprFromJson.condition).toBeInstanceOf(ContractCondition); + }); + + it('contract condition no abi serialization', async () => { + const conditionExpr = new ConditionExpression(contractConditionNoAbi); + + const conditionExprJson = conditionExpr.toJson(); + expect(conditionExprJson).toBeDefined(); + expect(conditionExprJson).toContain('chain'); + expect(conditionExprJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionExprJson).toContain('contractAddress'); + expect(conditionExprJson).toContain( + testContractConditionObj.contractAddress + ); + expect(conditionExprJson).toContain('standardContractType'); + expect(conditionExprJson).toContain( + testContractConditionObj.standardContractType + ); + expect(conditionExprJson).toContain('method'); + expect(conditionExprJson).toContain(testContractConditionObj.method); + expect(conditionExprJson).toContain('parameters'); + expect(conditionExprJson).toContain( + testContractConditionObj.parameters[0] + ); + expect(conditionExprJson).toContain('returnValueTest'); + expect(conditionExprJson).not.toContain('functionAbi'); + expect(conditionExprJson).not.toContain('operator'); + expect(conditionExprJson).not.toContain('operands'); + + const conditionExprFromJson = + ConditionExpression.fromJSON(conditionExprJson); + expect(conditionExprFromJson).toBeDefined(); + expect(conditionExprFromJson.condition).toBeInstanceOf(ContractCondition); + }); + + it('contract condition with abi serialization', async () => { + const conditionExpr = new ConditionExpression(contractConditionWithAbi); + + const conditionExprJson = conditionExpr.toJson(); + expect(conditionExprJson).toBeDefined(); + expect(conditionExprJson).toContain('chain'); + expect(conditionExprJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionExprJson).toContain('contractAddress'); + expect(conditionExprJson).toContain( + contractConditionWithAbiObj.contractAddress + ); + expect(conditionExprJson).toContain('method'); + expect(conditionExprJson).toContain(contractConditionWithAbiObj.method); + expect(conditionExprJson).toContain('parameters'); + expect(conditionExprJson).toContain( + contractConditionWithAbiObj.parameters[0] + ); + expect(conditionExprJson).toContain( + contractConditionWithAbiObj.parameters[1] + ); + expect(conditionExprJson).toContain('returnValueTest'); + expect(conditionExprJson).toContain('functionAbi'); + + expect(conditionExprJson).not.toContain('standardContractType'); + expect(conditionExprJson).not.toContain('operator'); + expect(conditionExprJson).not.toContain('operands'); + + const conditionExprFromJson = + ConditionExpression.fromJSON(conditionExprJson); + expect(conditionExprFromJson).toBeDefined(); + expect(conditionExprFromJson.condition).toBeInstanceOf(ContractCondition); + }); + + it('time condition serialization', async () => { + const conditionExpr = new ConditionExpression(timeCondition); + + const conditionExprJson = conditionExpr.toJson(); + expect(conditionExprJson).toBeDefined(); + expect(conditionExprJson).toContain('chain'); + expect(conditionExprJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionExprJson).toContain('method'); + expect(conditionExprJson).toContain(testTimeConditionObj.method); + expect(conditionExprJson).toContain('returnValueTest'); + expect(conditionExprJson).not.toContain('parameters'); + expect(conditionExprJson).not.toContain('contractAddress'); + expect(conditionExprJson).not.toContain('standardContractType'); + expect(conditionExprJson).not.toContain('functionAbi'); + expect(conditionExprJson).not.toContain('operator'); + expect(conditionExprJson).not.toContain('operands'); + + const conditionExprFromJson = + ConditionExpression.fromJSON(conditionExprJson); + expect(conditionExprFromJson).toBeDefined(); + expect(conditionExprFromJson.condition).toBeInstanceOf(TimeCondition); + }); + + it('rpc condition serialization', async () => { + const conditionExpr = new ConditionExpression(rpcCondition); + + const conditionExprJson = conditionExpr.toJson(); + expect(conditionExprJson).toBeDefined(); + expect(conditionExprJson).toContain('chain'); + expect(conditionExprJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionExprJson).toContain('method'); + expect(conditionExprJson).toContain(testRpcConditionObj.method); + expect(conditionExprJson).toContain('parameters'); + expect(conditionExprJson).toContain(testRpcConditionObj.parameters[0]); + expect(conditionExprJson).toContain('returnValueTest'); + expect(conditionExprJson).not.toContain('contractAddress'); + expect(conditionExprJson).not.toContain('standardContractType'); + expect(conditionExprJson).not.toContain('functionAbi'); + expect(conditionExprJson).not.toContain('operator'); + expect(conditionExprJson).not.toContain('operands'); + + const conditionExprFromJson = + ConditionExpression.fromJSON(conditionExprJson); + expect(conditionExprFromJson).toBeDefined(); + expect(conditionExprFromJson.condition).toBeInstanceOf(RpcCondition); + }); + + it('compound condition serialization', async () => { + const conditionExpr = new ConditionExpression(compoundCondition); + const compoundConditionObj = compoundCondition.toObj(); + + const conditionExprJson = conditionExpr.toJson(); + expect(conditionExprJson).toContain('operator'); + expect(conditionExprJson).toContain(compoundConditionObj.operator); + expect(conditionExprJson).toContain('operands'); + + expect(conditionExprJson).toBeDefined(); + expect(conditionExprJson).toContain('chain'); + expect(conditionExprJson).toContain(TEST_CHAIN_ID.toString()); + expect(conditionExprJson).toContain('method'); + expect(conditionExprJson).toContain(testRpcConditionObj.method); + expect(conditionExprJson).toContain(testTimeConditionObj.method); + expect(conditionExprJson).toContain(testContractConditionObj.method); + expect(conditionExprJson).toContain('parameters'); + expect(conditionExprJson).toContain(testRpcConditionObj.parameters[0]); + expect(conditionExprJson).toContain( + testContractConditionObj.parameters[0] + ); + + const conditionExprFromJson = + ConditionExpression.fromJSON(conditionExprJson); + expect(conditionExprFromJson).toBeDefined(); + expect(conditionExprFromJson.condition).toBeInstanceOf(CompoundCondition); + }); + }); +}); diff --git a/test/unit/conditions/condition-set.test.ts b/test/unit/conditions/condition-set.test.ts deleted file mode 100644 index 6a2465f02..000000000 --- a/test/unit/conditions/condition-set.test.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { CompoundCondition, ConditionSet } from '../../../src/conditions'; -import { - ContractCondition, - RpcCondition, - TimeCondition, -} from '../../../src/conditions/base'; -import { USER_ADDRESS_PARAM } from '../../../src/conditions/const'; -import { ERC721Balance } from '../../../src/conditions/predefined'; -import { toJSON } from '../../../src/utils'; -import { - TEST_CHAIN_ID, - TEST_CONTRACT_ADDR, - testFunctionAbi, - testReturnValueTest, -} from '../testVariables'; -import { - testContractConditionObj, - testRpcConditionObj, - testTimeConditionObj, -} from '../testVariables'; - -describe('condition set', () => { - const erc721BalanceCondition = new ERC721Balance({ - chain: TEST_CHAIN_ID, - contractAddress: TEST_CONTRACT_ADDR, - }); - - const contractConditionNoAbi = new ContractCondition( - testContractConditionObj - ); - - const customParamKey = ':customParam'; - const contractConditionWithAbiObj = { - ...testContractConditionObj, - standardContractType: undefined, - functionAbi: testFunctionAbi, - method: testFunctionAbi.name, - parameters: [USER_ADDRESS_PARAM, customParamKey], - returnValueTest: { - ...testReturnValueTest, - }, - }; - const contractConditionWithAbi = new ContractCondition( - contractConditionWithAbiObj - ); - - const rpcCondition = new RpcCondition(testRpcConditionObj); - const timeCondition = new TimeCondition(testTimeConditionObj); - const compoundCondition = new CompoundCondition({ - operator: 'and', - operands: [ - testContractConditionObj, - testTimeConditionObj, - testRpcConditionObj, - { - operator: 'or', - operands: [testTimeConditionObj, testContractConditionObj], - }, - ], - }); - - it('equality', async () => { - const conditionSetCurrentVersion = new ConditionSet(rpcCondition); - const conditionSetSameCurrentVerstion = new ConditionSet( - rpcCondition, - ConditionSet.VERSION - ); - // same version and condition - expect( - conditionSetCurrentVersion.equals(conditionSetSameCurrentVerstion) - ).toBeTruthy(); - - // same version but different condition - const conditionSetSameVersionDifferentCondition = new ConditionSet( - timeCondition - ); - expect( - conditionSetCurrentVersion.equals( - conditionSetSameVersionDifferentCondition - ) - ).not.toBeTruthy(); - - // different minor/patch version but same condition - const conditionSetOlderMinorVersion = new ConditionSet( - rpcCondition, - '0.1.0' - ); - const conditionSetOlderPatchVersion = new ConditionSet( - rpcCondition, - '0.0.1' - ); - expect( - conditionSetCurrentVersion.equals(conditionSetOlderMinorVersion) - ).not.toBeTruthy(); - expect( - conditionSetCurrentVersion.equals(conditionSetOlderPatchVersion) - ).not.toBeTruthy(); - expect( - conditionSetOlderMinorVersion.equals(conditionSetOlderPatchVersion) - ).not.toBeTruthy(); - }); - - describe('serialization / deserialization', () => { - it.each([ - erc721BalanceCondition, - contractConditionNoAbi, - contractConditionWithAbi, - rpcCondition, - timeCondition, - compoundCondition, - ])('serializes to and from json', async (condition) => { - const conditionSet = new ConditionSet(condition); - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('version'); - expect(conditionSetJson).toContain(ConditionSet.VERSION); - expect(conditionSetJson).toContain('condition'); - expect(conditionSetJson).toContain(toJSON(condition.toObj())); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.equals(conditionSetFromJson)).toBeTruthy(); - }); - - it('incompatible version', async () => { - const invalidVersion = '100.0.0'; - expect(() => { - ConditionSet.fromObj({ - version: invalidVersion, - condition: testTimeConditionObj, - }); - }).toThrow( - `Version provided, ${invalidVersion}, is incompatible with current version, ${ConditionSet.VERSION}` - ); - }); - - it.each([ - // no "operator" nor "method" value - { - version: ConditionSet.VERSION, - condition: { - randoKey: 'randoValue', - otherKey: 'otherValue', - }, - }, - // invalid "method" and no "contractAddress" - { - version: ConditionSet.VERSION, - condition: { - method: 'doWhatIWant', - returnValueTest: { - index: 0, - comparator: '>', - value: '100', - }, - chain: 5, - }, - }, - // condition with wrong method "method" and no contract address - { - version: ConditionSet.VERSION, - condition: { - ...testTimeConditionObj, - method: 'doWhatIWant', - }, - }, - // rpc condition (no contract address) with disallowed method - { - version: ConditionSet.VERSION, - condition: { - ...testRpcConditionObj, - method: 'isPolicyActive', - }, - }, - ])("can't determine condition type", async (invalidCondition) => { - expect(() => { - ConditionSet.fromObj(invalidCondition); - }).toThrow('unrecognized condition data'); - }); - - it('erc721 condition serialization', async () => { - const conditionSet = new ConditionSet(erc721BalanceCondition); - - const erc721BalanceConditionObj = erc721BalanceCondition.toObj(); - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('contractAddress'); - expect(conditionSetJson).toContain( - erc721BalanceConditionObj.contractAddress - ); - expect(conditionSetJson).toContain('standardContractType'); - expect(conditionSetJson).toContain('ERC721'); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(erc721BalanceConditionObj.method); - expect(conditionSetJson).toContain('returnValueTest'); - - expect(conditionSetJson).not.toContain('functionAbi'); - expect(conditionSetJson).not.toContain('operator'); - expect(conditionSetJson).not.toContain('operands'); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(ContractCondition); - }); - - it('contract condition no abi serialization', async () => { - const conditionSet = new ConditionSet(contractConditionNoAbi); - - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('contractAddress'); - expect(conditionSetJson).toContain( - testContractConditionObj.contractAddress - ); - expect(conditionSetJson).toContain('standardContractType'); - expect(conditionSetJson).toContain( - testContractConditionObj.standardContractType - ); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(testContractConditionObj.method); - expect(conditionSetJson).toContain('parameters'); - expect(conditionSetJson).toContain( - testContractConditionObj.parameters[0] - ); - expect(conditionSetJson).toContain('returnValueTest'); - expect(conditionSetJson).not.toContain('functionAbi'); - expect(conditionSetJson).not.toContain('operator'); - expect(conditionSetJson).not.toContain('operands'); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(ContractCondition); - }); - - it('contract condition with abi serialization', async () => { - const conditionSet = new ConditionSet(contractConditionWithAbi); - - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('contractAddress'); - expect(conditionSetJson).toContain( - contractConditionWithAbiObj.contractAddress - ); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(contractConditionWithAbiObj.method); - expect(conditionSetJson).toContain('parameters'); - expect(conditionSetJson).toContain( - contractConditionWithAbiObj.parameters[0] - ); - expect(conditionSetJson).toContain( - contractConditionWithAbiObj.parameters[1] - ); - expect(conditionSetJson).toContain('returnValueTest'); - expect(conditionSetJson).toContain('functionAbi'); - - expect(conditionSetJson).not.toContain('standardContractType'); - expect(conditionSetJson).not.toContain('operator'); - expect(conditionSetJson).not.toContain('operands'); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(ContractCondition); - }); - - it('time condition serialization', async () => { - const conditionSet = new ConditionSet(timeCondition); - - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(testTimeConditionObj.method); - expect(conditionSetJson).toContain('returnValueTest'); - expect(conditionSetJson).not.toContain('parameters'); - expect(conditionSetJson).not.toContain('contractAddress'); - expect(conditionSetJson).not.toContain('standardContractType'); - expect(conditionSetJson).not.toContain('functionAbi'); - expect(conditionSetJson).not.toContain('operator'); - expect(conditionSetJson).not.toContain('operands'); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(TimeCondition); - }); - - it('rpc condition serialization', async () => { - const conditionSet = new ConditionSet(rpcCondition); - - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(testRpcConditionObj.method); - expect(conditionSetJson).toContain('parameters'); - expect(conditionSetJson).toContain(testRpcConditionObj.parameters[0]); - expect(conditionSetJson).toContain('returnValueTest'); - expect(conditionSetJson).not.toContain('contractAddress'); - expect(conditionSetJson).not.toContain('standardContractType'); - expect(conditionSetJson).not.toContain('functionAbi'); - expect(conditionSetJson).not.toContain('operator'); - expect(conditionSetJson).not.toContain('operands'); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(RpcCondition); - }); - - it('compound condition serialization', async () => { - const conditionSet = new ConditionSet(compoundCondition); - const compoundConditionObj = compoundCondition.toObj(); - - const conditionSetJson = conditionSet.toJson(); - expect(conditionSetJson).toContain('operator'); - expect(conditionSetJson).toContain(compoundConditionObj.operator); - expect(conditionSetJson).toContain('operands'); - - expect(conditionSetJson).toBeDefined(); - expect(conditionSetJson).toContain('chain'); - expect(conditionSetJson).toContain(TEST_CHAIN_ID.toString()); - expect(conditionSetJson).toContain('method'); - expect(conditionSetJson).toContain(testRpcConditionObj.method); - expect(conditionSetJson).toContain(testTimeConditionObj.method); - expect(conditionSetJson).toContain(testContractConditionObj.method); - expect(conditionSetJson).toContain('parameters'); - expect(conditionSetJson).toContain(testRpcConditionObj.parameters[0]); - expect(conditionSetJson).toContain( - testContractConditionObj.parameters[0] - ); - - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.condition).toBeInstanceOf(CompoundCondition); - }); - }); -}); diff --git a/test/unit/conditions/context.test.ts b/test/unit/conditions/context.test.ts index 958454cd1..035b704d6 100644 --- a/test/unit/conditions/context.test.ts +++ b/test/unit/conditions/context.test.ts @@ -1,7 +1,7 @@ import { SecretKey } from '@nucypher/nucypher-core'; import { CustomContextParam } from '../../../src'; -import { ConditionSet } from '../../../src/conditions'; +import { ConditionExpression } from '../../../src/conditions'; import { ContractCondition, RpcCondition } from '../../../src/conditions/base'; import { USER_ADDRESS_PARAM } from '../../../src/conditions/const'; import { RESERVED_CONTEXT_PARAMS } from '../../../src/conditions/context/context'; @@ -26,7 +26,7 @@ describe('serialization', () => { value: USER_ADDRESS_PARAM, }, }); - const conditionContext = new ConditionSet(rpcCondition).buildContext( + const conditionContext = new ConditionExpression(rpcCondition).buildContext( web3Provider ); const asJson = await conditionContext.toJson(); @@ -48,8 +48,8 @@ describe('context parameters', () => { }, }; const contractCondition = new ContractCondition(contractConditionObj); - const conditionSet = new ConditionSet(contractCondition); - const conditionContext = conditionSet.buildContext(web3Provider); + const conditionExpr = new ConditionExpression(contractCondition); + const conditionContext = conditionExpr.buildContext(web3Provider); describe('return value test', () => { it('accepts on a custom context parameters', async () => { @@ -94,7 +94,7 @@ describe('context parameters', () => { ...contractConditionObj, parameters: [USER_ADDRESS_PARAM, customParamKey], }); - const conditionContext = new ConditionSet( + const conditionContext = new ConditionExpression( customContractCondition ).buildContext(web3Provider); @@ -108,7 +108,7 @@ describe('context parameters', () => { ...contractConditionObj, parameters: [USER_ADDRESS_PARAM, 100], }); - const conditionContext = new ConditionSet( + const conditionContext = new ConditionExpression( customContractCondition ).buildContext(web3Provider); diff --git a/test/unit/pre-strategy.test.ts b/test/unit/pre-strategy.test.ts index 3b15ba0eb..9fa599a22 100644 --- a/test/unit/pre-strategy.test.ts +++ b/test/unit/pre-strategy.test.ts @@ -24,7 +24,7 @@ import { aliceSecretKeyBytes, bobSecretKeyBytes } from './testVariables'; const { predefined: { ERC721Ownership }, - ConditionSet, + ConditionExpression, } = conditions; // Shared test variables @@ -37,7 +37,7 @@ const ownsNFT = new ERC721Ownership({ parameters: [3591], chain: 5, }); -const conditionSet = new ConditionSet(ownsNFT); +const conditionExpr = new ConditionExpression(ownsNFT); const mockedUrsulas = fakeUrsulas().slice(0, 3); const makePreStrategy = async () => { @@ -113,7 +113,7 @@ describe('PreDeployedStrategy', () => { const plaintext = 'this is a secret'; const encryptedMessageKit = deployedStrategy - .makeEncrypter(conditionSet) + .makeEncrypter(conditionExpr) .encryptMessagePre(plaintext); // Setup mocks for `retrieveAndDecrypt` From 324d2d952cb0028dc99b9520436f22011aceaa6c Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 20 Jun 2023 09:42:27 -0400 Subject: [PATCH 4/6] Additional testing for equality of `ConditionExpression` objects. --- test/unit/conditions/condition-expr.test.ts | 134 ++++++++++++++------ 1 file changed, 96 insertions(+), 38 deletions(-) diff --git a/test/unit/conditions/condition-expr.test.ts b/test/unit/conditions/condition-expr.test.ts index 429aad867..a824ee2f4 100644 --- a/test/unit/conditions/condition-expr.test.ts +++ b/test/unit/conditions/condition-expr.test.ts @@ -62,45 +62,103 @@ describe('condition set', () => { ], }); - it('equality', async () => { + describe('equality', () => { const conditionExprCurrentVersion = new ConditionExpression(rpcCondition); - const conditionExprSameCurrentVerstion = new ConditionExpression( - rpcCondition, - ConditionExpression.VERSION - ); - // same version and condition - expect( - conditionExprCurrentVersion.equals(conditionExprSameCurrentVerstion) - ).toBeTruthy(); - - // same version but different condition - const conditionExprSameVersionDifferentCondition = new ConditionExpression( - timeCondition - ); - expect( - conditionExprCurrentVersion.equals( - conditionExprSameVersionDifferentCondition - ) - ).not.toBeTruthy(); - - // different minor/patch version but same condition - const conditionExprOlderMinorVersion = new ConditionExpression( - rpcCondition, - '0.1.0' - ); - const conditionExprOlderPatchVersion = new ConditionExpression( - rpcCondition, - '0.0.1' - ); - expect( - conditionExprCurrentVersion.equals(conditionExprOlderMinorVersion) - ).not.toBeTruthy(); - expect( - conditionExprCurrentVersion.equals(conditionExprOlderPatchVersion) - ).not.toBeTruthy(); - expect( - conditionExprOlderMinorVersion.equals(conditionExprOlderPatchVersion) - ).not.toBeTruthy(); + + it('same version and condition', async () => { + const conditionExprSameCurrentVerstion = new ConditionExpression( + rpcCondition, + ConditionExpression.VERSION + ); + expect( + conditionExprCurrentVersion.equals(conditionExprSameCurrentVerstion) + ).toBeTruthy(); + }); + + it('different minor/patch version but same condition', async () => { + const conditionExprOlderMinorVersion = new ConditionExpression( + rpcCondition, + '0.1.0' + ); + const conditionExprOlderPatchVersion = new ConditionExpression( + rpcCondition, + '0.0.1' + ); + expect( + conditionExprCurrentVersion.equals(conditionExprOlderMinorVersion) + ).not.toBeTruthy(); + expect( + conditionExprCurrentVersion.equals(conditionExprOlderPatchVersion) + ).not.toBeTruthy(); + expect( + conditionExprOlderMinorVersion.equals(conditionExprOlderPatchVersion) + ).not.toBeTruthy(); + }); + + it('minor/patch number greater than major; still older', async () => { + const conditionExprOlderMinorVersion = new ConditionExpression( + rpcCondition, + '0.9.0' + ); + const conditionExprOlderPatchVersion = new ConditionExpression( + rpcCondition, + '0.0.9' + ); + const conditionExprOlderMinorPatchVersion = new ConditionExpression( + rpcCondition, + '0.9.9' + ); + expect( + conditionExprCurrentVersion.equals(conditionExprOlderMinorVersion) + ).not.toBeTruthy(); + expect( + conditionExprCurrentVersion.equals(conditionExprOlderPatchVersion) + ).not.toBeTruthy(); + expect( + conditionExprCurrentVersion.equals(conditionExprOlderMinorPatchVersion) + ).not.toBeTruthy(); + expect( + conditionExprOlderMinorVersion.equals(conditionExprOlderPatchVersion) + ).not.toBeTruthy(); + expect( + conditionExprOlderMinorVersion.equals( + conditionExprOlderMinorPatchVersion + ) + ).not.toBeTruthy(); + expect( + conditionExprOlderPatchVersion.equals( + conditionExprOlderMinorPatchVersion + ) + ).not.toBeTruthy(); + }); + + it.each([ + erc721BalanceCondition, + contractConditionNoAbi, + contractConditionWithAbi, + timeCondition, + compoundCondition, + ])('same version but different condition', async (condition) => { + const conditionExprSameVersionDifferentCondition = + new ConditionExpression(condition); + expect( + conditionExprCurrentVersion.equals( + conditionExprSameVersionDifferentCondition + ) + ).not.toBeTruthy(); + }); + + it('same contract condition although using erc721 helper', async () => { + const erc721ConditionExpr = new ConditionExpression( + erc721BalanceCondition + ); + const erc721ConditionData = erc721BalanceCondition.toObj(); + const sameContractCondition = new ContractCondition(erc721ConditionData); + const contractConditionExpr = new ConditionExpression( + sameContractCondition + ); + expect(erc721ConditionExpr.equals(contractConditionExpr)).toBeTruthy(); + }); }); describe('serialization / deserialization', () => { From 8a599be98e32174c397dd7cadc4e37695ab62607 Mon Sep 17 00:00:00 2001 From: derekpierre Date: Tue, 20 Jun 2023 11:45:07 -0400 Subject: [PATCH 5/6] Apply RFCs from #229. - Logic cleanup for determining condition type. - Use semver library for processing version strings - Remove resolved TODO - Added some tests --- package.json | 4 +- src/conditions/condition-expr.ts | 55 +++++++++------------ test/unit/conditions/condition-expr.test.ts | 12 +++++ yarn.lock | 12 +++++ 4 files changed, 50 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index b3a930bde..ce0808094 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,8 @@ "deep-equal": "^2.2.1", "ethers": "^5.4.1", "joi": "^17.7.0", - "qs": "^6.10.1" + "qs": "^6.10.1", + "semver": "^7.5.2" }, "devDependencies": { "@babel/core": "^7.18.10", @@ -67,6 +68,7 @@ "@types/deep-equal": "^1.0.1", "@types/jest": "^26.0.24", "@types/qs": "^6.9.7", + "@types/semver": "^7.5.0", "@typescript-eslint/eslint-plugin": "^4.0.1", "@typescript-eslint/parser": "^4.0.1", "cz-conventional-changelog": "^3.0.1", diff --git a/src/conditions/condition-expr.ts b/src/conditions/condition-expr.ts index d584a26fd..b762c6b13 100644 --- a/src/conditions/condition-expr.ts +++ b/src/conditions/condition-expr.ts @@ -1,5 +1,6 @@ import { Conditions as WASMConditions } from '@nucypher/nucypher-core'; import { ethers } from 'ethers'; +import { SemVer } from 'semver'; import { objectEquals, toJSON } from '../utils'; @@ -27,7 +28,6 @@ export class ConditionExpression { ) {} public toObj(): ConditionExpressionJSON { - // TODO add version here const conditionData = this.condition.toObj(); return { version: this.version, @@ -36,49 +36,40 @@ export class ConditionExpression { } public static fromObj(obj: ConditionExpressionJSON): ConditionExpression { - const version = obj.version; - // version specific logic can go here - const receivedMajorVersion = version.split('.')[0]; - const currentMajorVersion = ConditionExpression.VERSION.split('.')[0]; - if (receivedMajorVersion > currentMajorVersion) { + const receivedVersion = new SemVer(obj.version); + const currentVersion = new SemVer(ConditionExpression.VERSION); + if (receivedVersion.major > currentVersion.major) { throw new Error( - `Version provided, ${version}, is incompatible with current version, ${ConditionExpression.VERSION}` + `Version provided, ${obj.version}, is incompatible with current version, ${ConditionExpression.VERSION}` ); } const underlyingConditionData = obj.condition; + let condition: Condition | undefined; if (underlyingConditionData.operator) { - return new ConditionExpression( - new CompoundCondition(underlyingConditionData), - version - ); - } - - if (underlyingConditionData.method) { + condition = new CompoundCondition(underlyingConditionData); + } else if (underlyingConditionData.method) { if (underlyingConditionData.method === BLOCKTIME_METHOD) { - return new ConditionExpression( - new TimeCondition(underlyingConditionData), - version - ); - } - - if (underlyingConditionData.contractAddress) { - return new ConditionExpression( - new ContractCondition(underlyingConditionData), - version - ); + condition = new TimeCondition(underlyingConditionData); + } else if (underlyingConditionData.contractAddress) { + condition = new ContractCondition(underlyingConditionData); + } else if ( + (underlyingConditionData.method as string).startsWith('eth_') + ) { + condition = new RpcCondition(underlyingConditionData); } + } - if ((underlyingConditionData.method as string).startsWith('eth_')) { - return new ConditionExpression( - new RpcCondition(underlyingConditionData), - version - ); - } + if (!condition) { + throw new Error( + `Invalid condition: unrecognized condition data ${JSON.stringify( + underlyingConditionData + )}` + ); } - throw new Error('Invalid condition: unrecognized condition data'); + return new ConditionExpression(condition, obj.version); } public toJson(): string { diff --git a/test/unit/conditions/condition-expr.test.ts b/test/unit/conditions/condition-expr.test.ts index a824ee2f4..5ee867200 100644 --- a/test/unit/conditions/condition-expr.test.ts +++ b/test/unit/conditions/condition-expr.test.ts @@ -196,6 +196,18 @@ 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) => { + expect(() => { + ConditionExpression.fromObj({ + version: invalidVersion, + condition: testTimeConditionObj, + }); + }).toThrow(`Invalid Version: ${invalidVersion}`); + } + ); + it.each([ // no "operator" nor "method" value { diff --git a/yarn.lock b/yarn.lock index 6f4792ae7..4836111c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1891,6 +1891,11 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/semver@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -6183,6 +6188,13 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.5.2: + version "7.5.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.2.tgz#5b851e66d1be07c1cdaf37dfc856f543325a2beb" + integrity sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ== + dependencies: + lru-cache "^6.0.0" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" From 553f9304de1226b75e1fd5cda743bb5d03024faf Mon Sep 17 00:00:00 2001 From: derekpierre Date: Wed, 21 Jun 2023 08:15:20 -0400 Subject: [PATCH 6/6] Make incompatible version test more robust. --- test/unit/conditions/condition-expr.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/unit/conditions/condition-expr.test.ts b/test/unit/conditions/condition-expr.test.ts index 5ee867200..806269a7f 100644 --- a/test/unit/conditions/condition-expr.test.ts +++ b/test/unit/conditions/condition-expr.test.ts @@ -1,3 +1,5 @@ +import { SemVer } from 'semver'; + import { CompoundCondition, ConditionExpression, @@ -185,10 +187,11 @@ describe('condition set', () => { }); it('incompatible version', async () => { - const invalidVersion = '100.0.0'; + const currentVersion = new SemVer(ConditionExpression.VERSION); + const invalidVersion = currentVersion.inc('major'); expect(() => { ConditionExpression.fromObj({ - version: invalidVersion, + version: invalidVersion.version, condition: testTimeConditionObj, }); }).toThrow(