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/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/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-expr.ts b/src/conditions/condition-expr.ts new file mode 100644 index 000000000..b762c6b13 --- /dev/null +++ b/src/conditions/condition-expr.ts @@ -0,0 +1,99 @@ +import { Conditions as WASMConditions } from '@nucypher/nucypher-core'; +import { ethers } from 'ethers'; +import { SemVer } from 'semver'; + +import { objectEquals, toJSON } from '../utils'; + +import { + Condition, + ContractCondition, + RpcCondition, + TimeCondition, +} from './base'; +import { BLOCKTIME_METHOD } from './base/time'; +import { CompoundCondition } from './compound-condition'; +import { ConditionContext } from './context'; + +export type ConditionExpressionJSON = { + version: string; + condition: Record; +}; + +export class ConditionExpression { + static VERSION = '1.0.0'; + + constructor( + public readonly condition: Condition, + public readonly version: string = ConditionExpression.VERSION + ) {} + + public toObj(): ConditionExpressionJSON { + const conditionData = this.condition.toObj(); + return { + version: this.version, + condition: conditionData, + }; + } + + public static fromObj(obj: ConditionExpressionJSON): ConditionExpression { + const receivedVersion = new SemVer(obj.version); + const currentVersion = new SemVer(ConditionExpression.VERSION); + if (receivedVersion.major > currentVersion.major) { + throw new Error( + `Version provided, ${obj.version}, is incompatible with current version, ${ConditionExpression.VERSION}` + ); + } + + const underlyingConditionData = obj.condition; + let condition: Condition | undefined; + + if (underlyingConditionData.operator) { + condition = new CompoundCondition(underlyingConditionData); + } else if (underlyingConditionData.method) { + if (underlyingConditionData.method === BLOCKTIME_METHOD) { + 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 (!condition) { + throw new Error( + `Invalid condition: unrecognized condition data ${JSON.stringify( + underlyingConditionData + )}` + ); + } + + return new ConditionExpression(condition, obj.version); + } + + public toJson(): string { + return toJSON(this.toObj()); + } + + public static fromJSON(json: string): ConditionExpression { + return ConditionExpression.fromObj(JSON.parse(json)); + } + + public toWASMConditions(): WASMConditions { + return new WASMConditions(toJSON(this.toObj())); + } + + public buildContext( + provider: ethers.providers.Web3Provider + ): ConditionContext { + return new ConditionContext([this.condition], provider); + } + + public equals(other: ConditionExpression): boolean { + return ( + this.version === other.version && + objectEquals(this.condition.toObj(), other.condition.toObj()) + ); + } +} diff --git a/src/conditions/condition-set.ts b/src/conditions/condition-set.ts deleted file mode 100644 index 674a9d5b7..000000000 --- a/src/conditions/condition-set.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Conditions as WASMConditions } from '@nucypher/nucypher-core'; -import { ethers } from 'ethers'; - -import { objectEquals, toJSON } from '../utils'; - -import { - Condition, - ContractCondition, - RpcCondition, - TimeCondition, -} from './base'; -import { BLOCKTIME_METHOD } from './base/time'; -import { CompoundCondition } from './compound-condition'; -import { ConditionContext } from './context'; - -export type ConditionSetJSON = { - condition: Record; -}; - -export class ConditionSet { - constructor(public readonly condition: Condition) {} - - public toObj(): ConditionSetJSON { - // TODO add version here - const conditionData = this.condition.toObj(); - return { condition: conditionData }; - } - - public static fromObj(obj: ConditionSetJSON): ConditionSet { - // version specific logic can go here - const underlyingConditionData = obj.condition; - - if (underlyingConditionData.operator) { - return new ConditionSet(new CompoundCondition(underlyingConditionData)); - } - - if (underlyingConditionData.method) { - if (underlyingConditionData.method === BLOCKTIME_METHOD) { - return new ConditionSet(new TimeCondition(underlyingConditionData)); - } - - if (underlyingConditionData.contractAddress) { - return new ConditionSet(new ContractCondition(underlyingConditionData)); - } - - if ((underlyingConditionData.method as string).startsWith('eth_')) { - return new ConditionSet(new RpcCondition(underlyingConditionData)); - } - } - - throw new Error('Invalid condition: unrecognized condition data'); - } - - public toJson(): string { - return toJSON(this.toObj()); - } - - public static fromJSON(json: string): ConditionSet { - return ConditionSet.fromObj(JSON.parse(json)); - } - - public toWASMConditions(): WASMConditions { - return new WASMConditions(toJSON(this.toObj())); - } - - public buildContext( - provider: ethers.providers.Web3Provider - ): ConditionContext { - return new ConditionContext([this.condition], provider); - } - - public equals(other: ConditionSet): boolean { - return 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..806269a7f --- /dev/null +++ b/test/unit/conditions/condition-expr.test.ts @@ -0,0 +1,426 @@ +import { SemVer } from 'semver'; + +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], + }, + ], + }); + + describe('equality', () => { + const conditionExprCurrentVersion = new ConditionExpression(rpcCondition); + + 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', () => { + 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 currentVersion = new SemVer(ConditionExpression.VERSION); + const invalidVersion = currentVersion.inc('major'); + expect(() => { + ConditionExpression.fromObj({ + version: invalidVersion.version, + condition: testTimeConditionObj, + }); + }).toThrow( + `Version provided, ${invalidVersion}, is incompatible with current version, ${ConditionExpression.VERSION}` + ); + }); + + 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 + { + 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 bc3104008..000000000 --- a/test/unit/conditions/condition-set.test.ts +++ /dev/null @@ -1,270 +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 { - 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], - }, - ], - }); - - describe('serialization', () => { - 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(); - const conditionSetFromJson = ConditionSet.fromJSON(conditionSetJson); - expect(conditionSetFromJson).toBeDefined(); - expect(conditionSetFromJson.equals(conditionSetFromJson)).toBeTruthy(); - }); - }); - - it.each([ - // no "operator" nor "method" value - { - randoKey: 'randoValue', - otherKey: 'otherValue', - }, - // invalid "method" and no "contractAddress" - { - method: 'doWhatIWant', - returnValueTest: { - index: 0, - comparator: '>', - value: '100', - }, - chain: 5, - }, - // condition with wrong method "method" and no contract address - { - ...testTimeConditionObj, - method: 'doWhatIWant', - }, - // rpc condition (no contract address) with disallowed method - { - ...testRpcConditionObj, - method: 'isPolicyActive', - }, - ])("can't determine condition type", async (invalidCondition) => { - expect(() => { - ConditionSet.fromObj({ - condition: 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` 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"