diff --git a/package.json b/package.json index 8af1b16fe..13e51c8dd 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@nucypher/nucypher-core": "^0.12.0", "axios": "^0.21.1", "deep-equal": "^2.2.1", - "ethers": "^5.4.1", + "ethers": "^5.7.2", "joi": "^17.7.0", "qs": "^6.10.1", "semver": "^7.5.2" diff --git a/src/agents/coordinator.ts b/src/agents/coordinator.ts index 830376c98..db8378d8d 100644 --- a/src/agents/coordinator.ts +++ b/src/agents/coordinator.ts @@ -57,10 +57,11 @@ export class DkgCoordinatorAgent { } public static async initializeRitual( - provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, + signer: ethers.Signer, providers: ChecksumAddress[] ): Promise { - const Coordinator = await this.connectReadWrite(provider); + const Coordinator = await this.connectReadWrite(provider, signer); const tx = await Coordinator.initiateRitual(providers); const txReceipt = await tx.wait(DEFAULT_WAIT_N_CONFIRMATIONS); const [ritualStartEvent] = txReceipt.events ?? []; @@ -79,7 +80,7 @@ export class DkgCoordinatorAgent { } public static async getRitualState( - provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, ritualId: number ): Promise { const Coordinator = await this.connectReadOnly(provider); @@ -87,7 +88,7 @@ export class DkgCoordinatorAgent { } public static async onRitualEndEvent( - provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, ritualId: number, callback: (successful: boolean) => void ): Promise { @@ -109,14 +110,15 @@ export class DkgCoordinatorAgent { } private static async connectReadWrite( - web3Provider: ethers.providers.Web3Provider + provider: ethers.providers.Provider, + signer: ethers.Signer ) { - return await this.connect(web3Provider, web3Provider.getSigner()); + return await this.connect(provider, signer); } private static async connect( provider: ethers.providers.Provider, - signer?: ethers.providers.JsonRpcSigner + signer?: ethers.Signer ): Promise { const network = await provider.getNetwork(); const contractAddress = getContract(network.chainId, 'COORDINATOR'); diff --git a/src/agents/subscription-manager.ts b/src/agents/subscription-manager.ts index ac8a5a3c5..da8236588 100644 --- a/src/agents/subscription-manager.ts +++ b/src/agents/subscription-manager.ts @@ -15,7 +15,8 @@ import { DEFAULT_WAIT_N_CONFIRMATIONS, getContract } from './contracts'; export class PreSubscriptionManagerAgent { public static async createPolicy( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, + signer: ethers.Signer, valueInWei: BigNumber, policyId: Uint8Array, size: number, @@ -23,7 +24,7 @@ export class PreSubscriptionManagerAgent { endTimestamp: number, ownerAddress: ChecksumAddress ): Promise { - const SubscriptionManager = await this.connectReadWrite(web3Provider); + const SubscriptionManager = await this.connectReadWrite(provider, signer); const overrides = { value: valueInWei.toString(), }; @@ -66,14 +67,15 @@ export class PreSubscriptionManagerAgent { } private static async connectReadWrite( - web3Provider: ethers.providers.Web3Provider + provider: ethers.providers.Provider, + signer: ethers.Signer ) { - return await this.connect(web3Provider, web3Provider.getSigner()); + return await this.connect(provider, signer); } private static async connect( provider: ethers.providers.Provider, - signer?: ethers.providers.JsonRpcSigner + signer?: ethers.Signer ): Promise { const network = await provider.getNetwork(); const contractAddress = getContract( diff --git a/src/characters/alice.ts b/src/characters/alice.ts index dfe06d02f..579d4a338 100644 --- a/src/characters/alice.ts +++ b/src/characters/alice.ts @@ -42,7 +42,8 @@ export class Alice { } public async grant( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, + signer: ethers.Signer, porterUri: string, policyParameters: BlockchainPolicyParameters, includeUrsulas?: readonly ChecksumAddress[], @@ -54,12 +55,12 @@ export class Alice { excludeUrsulas, includeUrsulas ); - const policy = await this.createPolicy(web3Provider, policyParameters); - return await policy.enact(web3Provider, ursulas); + const policy = await this.createPolicy(provider, policyParameters); + return await policy.enact(provider, signer, ursulas); } public async generatePreEnactedPolicy( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, porterUri: string, policyParameters: BlockchainPolicyParameters, includeUrsulas?: readonly ChecksumAddress[], @@ -71,7 +72,7 @@ export class Alice { excludeUrsulas, includeUrsulas ); - const policy = await this.createPolicy(web3Provider, policyParameters); + const policy = await this.createPolicy(provider, policyParameters); return await policy.generatePreEnactedPolicy(ursulas); } @@ -94,11 +95,11 @@ export class Alice { } private async createPolicy( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, rawParameters: BlockchainPolicyParameters ): Promise { const { bob, label, threshold, shares, startDate, endDate } = - await this.validatePolicyParameters(web3Provider, rawParameters); + await this.validatePolicyParameters(provider, rawParameters); const { delegatingKey, verifiedKFrags } = this.generateKFrags( bob, label, @@ -119,7 +120,7 @@ export class Alice { } private async validatePolicyParameters( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, rawParams: BlockchainPolicyParameters ): Promise { const startDate = rawParams.startDate ?? new Date(); @@ -141,8 +142,8 @@ export class Alice { ); } - const blockNumber = await web3Provider.getBlockNumber(); - const block = await web3Provider.getBlock(blockNumber); + const blockNumber = await provider.getBlockNumber(); + const block = await provider.getBlock(blockNumber); const blockTime = new Date(block.timestamp * 1000); if (endDate < blockTime) { throw new Error( diff --git a/src/characters/cbd-recipient.ts b/src/characters/cbd-recipient.ts index 9a977665d..b7cba3aaa 100644 --- a/src/characters/cbd-recipient.ts +++ b/src/characters/cbd-recipient.ts @@ -45,17 +45,19 @@ export class ThresholdDecrypter { // Retrieve and decrypt ciphertext using provider and condition expression public async retrieveAndDecrypt( - provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, + signer: ethers.Signer, conditionExpr: ConditionExpression, ciphertext: Ciphertext ): Promise { - const acp = await this.makeAcp(provider, conditionExpr, ciphertext); + const acp = await this.makeAcp(provider, signer, conditionExpr, ciphertext); const decryptionShares = await this.retrieve( provider, conditionExpr, ciphertext, - acp + acp, + signer ); const sharedSecret = combineDecryptionSharesSimple(decryptionShares); @@ -67,7 +69,8 @@ export class ThresholdDecrypter { } private async makeAcp( - provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, + signer: ethers.Signer, conditionExpr: ConditionExpression, ciphertext: Ciphertext ) { @@ -81,23 +84,26 @@ export class ThresholdDecrypter { ); const headerHash = keccak256(ciphertext.header.toBytes()); - const authorization = await provider.getSigner().signMessage(headerHash); + const authorization = await signer.signMessage(headerHash); return new AccessControlPolicy(authData, toBytes(authorization)); } // Retrieve decryption shares public async retrieve( - provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, conditionExpr: ConditionExpression, ciphertext: Ciphertext, - acp: AccessControlPolicy + acp: AccessControlPolicy, + signer?: ethers.Signer ): Promise { const dkgParticipants = await DkgCoordinatorAgent.getParticipants( provider, this.ritualId ); - const contextStr = await conditionExpr.buildContext(provider).toJson(); + const contextStr = await conditionExpr + .buildContext(provider, {}, signer) + .toJson(); const { sharedSecrets, encryptedRequests } = this.makeDecryptionRequests( this.ritualId, ciphertext, diff --git a/src/characters/pre-recipient.ts b/src/characters/pre-recipient.ts index f3ae24f5a..964584218 100644 --- a/src/characters/pre-recipient.ts +++ b/src/characters/pre-recipient.ts @@ -63,10 +63,15 @@ export class PreDecrypter { } public async retrieveAndDecrypt( - messageKits: readonly MessageKit[], - provider: ethers.providers.Web3Provider + provider: ethers.providers.Provider, + signer: ethers.Signer, + messageKits: readonly MessageKit[] ): Promise { - const policyMessageKits = await this.retrieve(messageKits, provider); + const policyMessageKits = await this.retrieve( + provider, + signer, + messageKits + ); policyMessageKits.forEach((mk: PolicyMessageKit) => { if (!mk.isDecryptableByReceiver()) { @@ -90,8 +95,9 @@ export class PreDecrypter { } public async retrieve( - messageKits: readonly MessageKit[], - provider: ethers.providers.Web3Provider + provider: ethers.providers.Provider, + signer: ethers.Signer, + messageKits: readonly MessageKit[] ): Promise { const treasureMap = this.encryptedTreasureMap.decrypt( this.keyring.secretKey, @@ -106,7 +112,12 @@ export class PreDecrypter { .reduce((acc: ConditionExpression[], val) => acc.concat(val), []) .map((condExpr: ConditionExpression) => condExpr.condition); - const conditionContext = new ConditionContext(conditions, provider); + const conditionContext = new ConditionContext( + provider, + conditions, + {}, + signer + ); const policyMessageKits = messageKits.map((mk) => PolicyMessageKit.fromMessageKit( diff --git a/src/conditions/base/condition.ts b/src/conditions/base/condition.ts index f4caa12c2..d77fb587e 100644 --- a/src/conditions/base/condition.ts +++ b/src/conditions/base/condition.ts @@ -1,6 +1,7 @@ import Joi from 'joi'; import { objectEquals } from '../../utils'; +import { USER_ADDRESS_PARAM } from '../const'; type Map = Record; @@ -19,6 +20,10 @@ export class Condition { return this.schema.validate(newValue); } + public requiresSigner(): boolean { + return JSON.stringify(this.value).includes(USER_ADDRESS_PARAM); + } + public toObj(): Map { const { error, value } = this.validate(this.value); if (error) { diff --git a/src/conditions/condition-expr.ts b/src/conditions/condition-expr.ts index 61c446e53..a8fcb6be8 100644 --- a/src/conditions/condition-expr.ts +++ b/src/conditions/condition-expr.ts @@ -12,7 +12,7 @@ import { } from './base'; import { BLOCKTIME_METHOD } from './base/time'; import { CompoundCondition } from './compound-condition'; -import { ConditionContext } from './context'; +import { ConditionContext, CustomContextParam } from './context'; export type ConditionExpressionJSON = { version: string; @@ -85,9 +85,20 @@ export class ConditionExpression { } public buildContext( - provider: ethers.providers.Web3Provider + provider: ethers.providers.Provider, + customParameters: Record = {}, + signer?: ethers.Signer ): ConditionContext { - return new ConditionContext([this.condition], provider); + return new ConditionContext( + provider, + [this.condition], + customParameters, + signer + ); + } + + public contextRequiresSigner(): boolean { + return this.condition.requiresSigner(); } public asAad(): Uint8Array { diff --git a/src/conditions/context/context.ts b/src/conditions/context/context.ts index fcf9939c3..0f1f7fc04 100644 --- a/src/conditions/context/context.ts +++ b/src/conditions/context/context.ts @@ -14,16 +14,29 @@ export const RESERVED_CONTEXT_PARAMS = [USER_ADDRESS_PARAM]; export const CONTEXT_PARAM_PREFIX = ':'; export class ConditionContext { - private readonly walletAuthProvider: WalletAuthenticationProvider; + private readonly walletAuthProvider?: WalletAuthenticationProvider; constructor( + private readonly provider: ethers.providers.Provider, private readonly conditions: ReadonlyArray, - // TODO: We don't always need a web3 provider, only in cases where some specific context parameters are used - // TODO: Consider making this optional or introducing a different pattern to handle that - private readonly web3Provider: ethers.providers.Web3Provider, - public readonly customParameters: Record = {} + public readonly customParameters: Record = {}, + private readonly signer?: ethers.Signer ) { - Object.keys(customParameters).forEach((key) => { + if (this.signer) { + this.walletAuthProvider = new WalletAuthenticationProvider( + this.provider, + this.signer + ); + } + this.validate(); + } + + public requiresSigner(): boolean { + return this.conditions.some((cond) => cond.requiresSigner()); + } + + private validate() { + Object.keys(this.customParameters).forEach((key) => { if (RESERVED_CONTEXT_PARAMS.includes(key)) { throw new Error( `Cannot use reserved parameter name ${key} as custom parameter` @@ -35,7 +48,14 @@ export class ConditionContext { ); } }); - this.walletAuthProvider = new WalletAuthenticationProvider(web3Provider); + + if (this.requiresSigner() && !this.signer) { + throw new Error( + `Cannot use ${USER_ADDRESS_PARAM} as custom parameter without a signer` + ); + } + + return this; } public toObj = async (): Promise> => { @@ -70,6 +90,11 @@ export class ConditionContext { // Fill in predefined context parameters if (requestedParameters.has(USER_ADDRESS_PARAM)) { + if (!this.walletAuthProvider) { + throw new Error( + `Cannot use ${USER_ADDRESS_PARAM} as custom parameter without a signer` + ); + } parameters[USER_ADDRESS_PARAM] = await this.walletAuthProvider.getOrCreateWalletSignature(); // Remove from requested parameters @@ -103,6 +128,11 @@ export class ConditionContext { public withCustomParams = ( params: Record ): ConditionContext => { - return new ConditionContext(this.conditions, this.web3Provider, params); + return new ConditionContext( + this.provider, + this.conditions, + params, + this.signer + ); }; } diff --git a/src/conditions/context/providers.ts b/src/conditions/context/providers.ts index 9b8db518e..c58951c32 100644 --- a/src/conditions/context/providers.ts +++ b/src/conditions/context/providers.ts @@ -1,3 +1,4 @@ +import type { TypedDataSigner } from '@ethersproject/abstract-signer'; import { ethers } from 'ethers'; import { utils as ethersUtils } from 'ethers/lib/ethers'; @@ -18,10 +19,13 @@ interface ChainData { export class WalletAuthenticationProvider { private walletSignature?: Record; - constructor(private readonly web3Provider: ethers.providers.Web3Provider) {} + constructor( + private readonly provider: ethers.providers.Provider, + private readonly signer: ethers.Signer + ) {} public async getOrCreateWalletSignature(): Promise { - const address = await this.web3Provider.getSigner().getAddress(); + const address = await this.signer.getAddress(); const storageKey = `wallet-signature-${address}`; // If we have a signature in localStorage, return it @@ -59,7 +63,7 @@ export class WalletAuthenticationProvider { private async createWalletSignature(): Promise { // Ensure freshness of the signature const { blockNumber, blockHash, chainId } = await this.getChainData(); - const address = await this.web3Provider.getSigner().getAddress(); + const address = await this.signer.getAddress(); const signatureText = `I'm the owner of address ${address} as of block number ${blockNumber}`; const salt = ethersUtils.hexlify(ethersUtils.randomBytes(32)); @@ -85,9 +89,10 @@ export class WalletAuthenticationProvider { blockHash, }, }; - const signature = await this.web3Provider - .getSigner() - ._signTypedData(typedData.domain, typedData.types, typedData.message); + // https://github.com/ethers-io/ethers.js/issues/1431#issuecomment-813950552 + const signature = await ( + this.signer as unknown as TypedDataSigner + )._signTypedData(typedData.domain, typedData.types, typedData.message); const formattedTypedData: FormattedTypedData = { ...typedData, @@ -118,9 +123,9 @@ export class WalletAuthenticationProvider { } private async getChainData(): Promise { - const blockNumber = await this.web3Provider.getBlockNumber(); - const blockHash = (await this.web3Provider.getBlock(blockNumber)).hash; - const chainId = (await this.web3Provider.getNetwork()).chainId; + const blockNumber = await this.provider.getBlockNumber(); + const blockHash = (await this.provider.getBlock(blockNumber)).hash; + const chainId = (await this.provider.getNetwork()).chainId; return { blockNumber, blockHash, chainId }; } } diff --git a/src/dkg.ts b/src/dkg.ts index e65095838..c1ab06499 100644 --- a/src/dkg.ts +++ b/src/dkg.ts @@ -65,23 +65,25 @@ const assumedThreshold = (sharesNum: number): number => export class DkgClient { public static async initializeRitual( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, + signer: ethers.Signer, ursulas: ChecksumAddress[], waitUntilEnd = false ): Promise { const ritualId = await DkgCoordinatorAgent.initializeRitual( - web3Provider, + provider, + signer, ursulas.sort() ); if (waitUntilEnd) { const isSuccessful = await DkgClient.waitUntilRitualEnd( - web3Provider, + provider, ritualId ); if (!isSuccessful) { const ritualState = await DkgCoordinatorAgent.getRitualState( - web3Provider, + provider, ritualId ); throw new Error( @@ -94,7 +96,7 @@ export class DkgClient { } private static waitUntilRitualEnd = async ( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, ritualId: number ): Promise => { return new Promise((resolve, reject) => { @@ -105,19 +107,19 @@ export class DkgClient { reject(); } }; - DkgCoordinatorAgent.onRitualEndEvent(web3Provider, ritualId, callback); + DkgCoordinatorAgent.onRitualEndEvent(provider, ritualId, callback); }); }; public static async getExistingRitual( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, ritualId: number ): Promise { const ritualState = await DkgCoordinatorAgent.getRitualState( - web3Provider, + provider, ritualId ); - const ritual = await DkgCoordinatorAgent.getRitual(web3Provider, ritualId); + const ritual = await DkgCoordinatorAgent.getRitual(provider, ritualId); const dkgPkBytes = new Uint8Array([ ...fromHexString(ritual.publicKey.word0), ...fromHexString(ritual.publicKey.word1), diff --git a/src/policies/policy.ts b/src/policies/policy.ts index 00c62003a..2dc9dec07 100644 --- a/src/policies/policy.ts +++ b/src/policies/policy.ts @@ -42,9 +42,10 @@ export class PreEnactedPolicy implements IPreEnactedPolicy { ) {} public async enact( - web3Provider: ethers.providers.Web3Provider + provider: ethers.providers.Provider, + signer: ethers.Signer ): Promise { - const txHash = await this.publish(web3Provider); + const txHash = await this.publish(provider, signer); return { ...this, txHash, @@ -52,19 +53,21 @@ export class PreEnactedPolicy implements IPreEnactedPolicy { } private async publish( - web3Provider: ethers.providers.Web3Provider + provider: ethers.providers.Provider, + signer: ethers.Signer ): Promise { const startTimestamp = toEpoch(this.startTimestamp); const endTimestamp = toEpoch(this.endTimestamp); - const ownerAddress = await web3Provider.getSigner().getAddress(); + const ownerAddress = await signer.getAddress(); const value = await PreSubscriptionManagerAgent.getPolicyCost( - web3Provider, + provider, this.size, startTimestamp, endTimestamp ); const tx = await PreSubscriptionManagerAgent.createPolicy( - web3Provider, + provider, + signer, value, this.id.toBytes(), this.size, @@ -107,11 +110,12 @@ export class BlockchainPolicy { } public async enact( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, + signer: ethers.Signer, ursulas: readonly Ursula[] ): Promise { const preEnacted = await this.generatePreEnactedPolicy(ursulas); - return await preEnacted.enact(web3Provider); + return await preEnacted.enact(provider, signer); } public async generatePreEnactedPolicy( diff --git a/src/sdk/strategy/cbd-strategy.ts b/src/sdk/strategy/cbd-strategy.ts index f48accc9f..56a91c6c3 100644 --- a/src/sdk/strategy/cbd-strategy.ts +++ b/src/sdk/strategy/cbd-strategy.ts @@ -29,13 +29,13 @@ export class CbdStrategy { } public async deploy( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, ritualId: number ): Promise { // TODO(#264): Enable ritual initialization // if (ritualId === undefined) { // ritualId = await DkgClient.initializeRitual( - // web3Provider, + // provider, // this.cohort.ursulaAddresses, // true // ); @@ -44,7 +44,7 @@ export class CbdStrategy { // // Given that we just initialized the ritual, this should never happen // throw new Error('Ritual ID is undefined'); // } - const dkgRitual = await DkgClient.getExistingRitual(web3Provider, ritualId); + const dkgRitual = await DkgClient.getExistingRitual(provider, ritualId); return DeployedCbdStrategy.create(dkgRitual, this.cohort.porterUri); } @@ -84,7 +84,7 @@ export class DeployedCbdStrategy { // TODO: This is analogous to create() above, is it useful? public static async fromRitualId( - provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, porterUri: string, ritualId: number ): Promise { diff --git a/src/sdk/strategy/pre-strategy.ts b/src/sdk/strategy/pre-strategy.ts index 267f0480d..734d10dfc 100644 --- a/src/sdk/strategy/pre-strategy.ts +++ b/src/sdk/strategy/pre-strategy.ts @@ -62,7 +62,8 @@ export class PreStrategy { } public async deploy( - web3Provider: ethers.providers.Web3Provider, + provider: ethers.providers.Provider, + signer: ethers.Signer, label: string, threshold = Math.floor(this.cohort.numUrsulas / 2) + 1, shares = this.cohort.numUrsulas @@ -85,7 +86,8 @@ export class PreStrategy { endDate: this.endDate, }; const policy = await alice.grant( - web3Provider, + provider, + signer, porterUri, policyParams, this.cohort.ursulaAddresses diff --git a/test/acceptance/alice-grants.test.ts b/test/acceptance/alice-grants.test.ts index 9600e7582..1fe6ccad5 100644 --- a/test/acceptance/alice-grants.test.ts +++ b/test/acceptance/alice-grants.test.ts @@ -14,9 +14,10 @@ import { fakeAlice, fakeBob, fakePorterUri, + fakeProvider, fakeRemoteBob, + fakeSigner, fakeUrsulas, - fakeWeb3Provider, fromBytes, mockEncryptTreasureMap, mockGenerateKFrags, @@ -34,7 +35,8 @@ describe('story: alice shares message with bob through policy', () => { const startDate = new Date(); const endDate = new Date(Date.now() + 60 * 1000); const mockedUrsulas = fakeUrsulas(shares); - const web3Provider = fakeWeb3Provider(); + const provider = fakeProvider(); + const signer = fakeSigner(); // Intermediate variables used for mocking let encryptedTreasureMap: EncryptedTreasureMap; @@ -65,7 +67,7 @@ describe('story: alice shares message with bob through policy', () => { startDate, endDate, }; - policy = await alice.grant(web3Provider, fakePorterUri, policyParams); + policy = await alice.grant(provider, signer, fakePorterUri, policyParams); expect( bytesEqual( diff --git a/test/acceptance/delay-enact.test.ts b/test/acceptance/delay-enact.test.ts index ac0183072..8ad674208 100644 --- a/test/acceptance/delay-enact.test.ts +++ b/test/acceptance/delay-enact.test.ts @@ -2,9 +2,10 @@ import { bytesEqual, fakeAlice, fakePorterUri, + fakeProvider, fakeRemoteBob, + fakeSigner, fakeUrsulas, - fakeWeb3Provider, mockEncryptTreasureMap, mockGenerateKFrags, mockGetUrsulas, @@ -18,7 +19,8 @@ describe('story: alice creates a policy but someone else enacts it', () => { const endDate = new Date(Date.now() + 60 * 1000); // 60s later const mockedUrsulas = fakeUrsulas(shares); const label = 'fake-data-label'; - const web3Provider = fakeWeb3Provider(); + const provider = fakeProvider(); + const signer = fakeSigner(); it('alice generates a new policy', async () => { const getUrsulasSpy = mockGetUrsulas(mockedUrsulas); @@ -38,7 +40,7 @@ describe('story: alice creates a policy but someone else enacts it', () => { }; const preEnactedPolicy = await alice.generatePreEnactedPolicy( - web3Provider, + provider, fakePorterUri, policyParams ); @@ -50,7 +52,7 @@ describe('story: alice creates a policy but someone else enacts it', () => { ).toBeTruthy(); expect(preEnactedPolicy.label).toBe(label); - const enacted = await preEnactedPolicy.enact(web3Provider); + const enacted = await preEnactedPolicy.enact(provider, signer); expect(enacted.txHash).toBeDefined(); expect(getUrsulasSpy).toHaveBeenCalled(); diff --git a/test/docs/cbd.test.ts b/test/docs/cbd.test.ts index e657827c7..e0e0c40af 100644 --- a/test/docs/cbd.test.ts +++ b/test/docs/cbd.test.ts @@ -11,8 +11,8 @@ import { import { Ursula } from '../../src/porter'; import { toBytes } from '../../src/utils'; import { + fakeProvider, fakeUrsulas, - fakeWeb3Provider, mockDetectEthereumProvider, mockEncryptTreasureMap, mockGenerateKFrags, @@ -57,9 +57,7 @@ describe('Get Started (CBD PoC)', () => { jest .spyOn(providers, 'Web3Provider') - .mockImplementation(() => - fakeWeb3Provider(SecretKey.random().toBEBytes()) - ); + .mockImplementation(() => fakeProvider(SecretKey.random().toBEBytes())); // // Start of the code example @@ -88,8 +86,9 @@ describe('Get Started (CBD PoC)', () => { const MMprovider = await detectEthereumProvider(); const mumbai = providers.getNetwork(80001); - const web3Provider = new providers.Web3Provider(MMprovider, mumbai); - const newDeployed = await newStrategy.deploy(web3Provider, 'test'); + const provider = new providers.Web3Provider(MMprovider, mumbai); + const signer = provider.getSigner(); + const newDeployed = await newStrategy.deploy(provider, signer, 'test'); // 5. Encrypt the plaintext & update conditions const NFTBalanceConfig = { @@ -117,8 +116,9 @@ describe('Get Started (CBD PoC)', () => { // 6. Request decryption rights const decryptedMessage = await newDeployed.decrypter.retrieveAndDecrypt( - [encryptedMessageKit], - web3Provider + provider, + signer, + [encryptedMessageKit] ); // diff --git a/test/integration/dkg-client.test.ts b/test/integration/dkg-client.test.ts index acb8337b6..c3eed8e8a 100644 --- a/test/integration/dkg-client.test.ts +++ b/test/integration/dkg-client.test.ts @@ -4,8 +4,8 @@ import { DkgCoordinatorAgent } from '../../src/agents/coordinator'; import { fakeCoordinatorRitual, fakeDkgParticipants, + fakeProvider, fakeRitualId, - fakeWeb3Provider, mockGetParticipants, } from '../utils'; @@ -22,13 +22,13 @@ describe('DkgCoordinatorAgent', () => { }); it('fetches transcripts from the coordinator', async () => { - const provider = fakeWeb3Provider(SecretKey.random().toBEBytes()); + const provider = fakeProvider(SecretKey.random().toBEBytes()); const ritual = await DkgCoordinatorAgent.getRitual(provider, fakeRitualId); expect(ritual).toBeDefined(); }); it('fetches participants from the coordinator', async () => { - const provider = fakeWeb3Provider(SecretKey.random().toBEBytes()); + const provider = fakeProvider(SecretKey.random().toBEBytes()); const fakeParticipants = fakeDkgParticipants(fakeRitualId); const getParticipantsSpy = mockGetParticipants( fakeParticipants.participants diff --git a/test/unit/cbd-strategy.test.ts b/test/unit/cbd-strategy.test.ts index d38aeeb45..093dba61c 100644 --- a/test/unit/cbd-strategy.test.ts +++ b/test/unit/cbd-strategy.test.ts @@ -9,9 +9,10 @@ import { fakeDkgFlow, fakeDkgParticipants, fakeDkgRitual, + fakeProvider, + fakeSigner, fakeTDecFlow, fakeUrsulas, - fakeWeb3Provider, makeCohort, mockCbdDecrypt, mockGetExistingRitual, @@ -29,7 +30,8 @@ const { // Shared test variables const aliceSecretKey = SecretKey.fromBEBytes(aliceSecretKeyBytes); -const aliceProvider = fakeWeb3Provider(aliceSecretKey.toBEBytes()); +const aliceSigner = fakeSigner(aliceSecretKey.toBEBytes()); +const aliceProvider = fakeProvider(aliceSecretKey.toBEBytes()); const ownsNFT = new ERC721Ownership({ contractAddress: '0x1e988ba4692e52Bc50b375bcC8585b95c48AaD77', parameters: [3591], @@ -52,7 +54,7 @@ async function makeDeployedCbdStrategy() { const mockedDkg = fakeDkgFlow(variant, 0, 4, 4); const mockedDkgRitual = fakeDkgRitual(mockedDkg); - const web3Provider = fakeWeb3Provider(aliceSecretKey.toBEBytes()); + const web3Provider = fakeProvider(aliceSecretKey.toBEBytes()); const getUrsulasSpy = mockGetUrsulas(ursulas); const getExistingRitualSpy = mockGetExistingRitual(mockedDkgRitual); const deployedStrategy = await strategy.deploy(web3Provider, ritualId); @@ -131,6 +133,7 @@ describe('CbdDeployedStrategy', () => { const decryptedMessage = await deployedStrategy.decrypter.retrieveAndDecrypt( aliceProvider, + aliceSigner, conditionExpr, ciphertext ); diff --git a/test/unit/conditions/base/contract.test.ts b/test/unit/conditions/base/contract.test.ts index ee988bbb6..ef926e640 100644 --- a/test/unit/conditions/base/contract.test.ts +++ b/test/unit/conditions/base/contract.test.ts @@ -1,12 +1,10 @@ -import { SecretKey } from '@nucypher/nucypher-core'; - import { ConditionExpression, CustomContextParam, } from '../../../../src/conditions'; import { ContractCondition } from '../../../../src/conditions/base'; import { USER_ADDRESS_PARAM } from '../../../../src/conditions/const'; -import { fakeWeb3Provider } from '../../../utils'; +import { fakeProvider, fakeSigner } from '../../../utils'; import { testContractConditionObj, testFunctionAbi } from '../../testVariables'; describe('validation', () => { @@ -116,9 +114,10 @@ describe('supports custom function abi', () => { }, }; const contractCondition = new ContractCondition(contractConditionObj); - const web3Provider = fakeWeb3Provider(SecretKey.random().toBEBytes()); + const provider = fakeProvider(); + const signer = fakeSigner(); const conditionExpr = new ConditionExpression(contractCondition); - const conditionContext = conditionExpr.buildContext(web3Provider); + const conditionContext = conditionExpr.buildContext(provider, {}, signer); const myCustomParam = ':customParam'; const customParams: Record = {}; customParams[myCustomParam] = 1234; diff --git a/test/unit/conditions/context.test.ts b/test/unit/conditions/context.test.ts index 035b704d6..126f05df3 100644 --- a/test/unit/conditions/context.test.ts +++ b/test/unit/conditions/context.test.ts @@ -1,11 +1,9 @@ -import { SecretKey } from '@nucypher/nucypher-core'; - import { CustomContextParam } from '../../../src'; 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'; -import { fakeWeb3Provider } from '../../utils'; +import { fakeProvider, fakeSigner } from '../../utils'; import { testContractConditionObj, testFunctionAbi, @@ -13,7 +11,8 @@ import { testRpcConditionObj, } from '../testVariables'; -const web3Provider = fakeWeb3Provider(SecretKey.random().toBEBytes()); +const provider = fakeProvider(); +const signer = fakeSigner(); describe('serialization', () => { it('serializes to json', async () => { @@ -27,7 +26,9 @@ describe('serialization', () => { }, }); const conditionContext = new ConditionExpression(rpcCondition).buildContext( - web3Provider + provider, + {}, + signer ); const asJson = await conditionContext.toJson(); expect(asJson).toBeDefined(); @@ -49,7 +50,7 @@ describe('context parameters', () => { }; const contractCondition = new ContractCondition(contractConditionObj); const conditionExpr = new ConditionExpression(contractCondition); - const conditionContext = conditionExpr.buildContext(web3Provider); + const conditionContext = conditionExpr.buildContext(provider, {}, signer); describe('return value test', () => { it('accepts on a custom context parameters', async () => { @@ -77,6 +78,32 @@ describe('context parameters', () => { }); }); + it('detects if a signer is required', () => { + const conditionObj = { + ...testContractConditionObj, + returnValueTest: { + ...testReturnValueTest, + value: USER_ADDRESS_PARAM, + }, + }; + const condition = new ContractCondition(conditionObj); + const conditionExpr = new ConditionExpression(condition); + const conditionContext = conditionExpr.buildContext(provider, {}, signer); + expect(conditionExpr.contextRequiresSigner()).toBe(true); + expect(conditionContext.requiresSigner()).toBe(true); + }); + + it('detects if a signer is not required', () => { + const condition = new RpcCondition(testRpcConditionObj); + const conditionExpr = new ConditionExpression(condition); + const conditionContext = conditionExpr.buildContext(provider, {}, signer); + expect(JSON.stringify(condition.toObj()).includes(USER_ADDRESS_PARAM)).toBe( + false + ); + expect(conditionExpr.contextRequiresSigner()).toBe(false); + expect(conditionContext.requiresSigner()).toBe(false); + }); + describe('custom method parameters', () => { const contractConditionObj = { ...testContractConditionObj, @@ -96,7 +123,7 @@ describe('context parameters', () => { }); const conditionContext = new ConditionExpression( customContractCondition - ).buildContext(web3Provider); + ).buildContext(provider, {}, signer); await expect(async () => conditionContext.toObj()).rejects.toThrow( `Missing custom context parameter(s): ${customParamKey}` @@ -110,7 +137,7 @@ describe('context parameters', () => { }); const conditionContext = new ConditionExpression( customContractCondition - ).buildContext(web3Provider); + ).buildContext(provider, {}, signer); const asObj = await conditionContext.toObj(); expect(asObj).toBeDefined(); diff --git a/test/unit/pre-strategy.test.ts b/test/unit/pre-strategy.test.ts index daee95dae..c4f775061 100644 --- a/test/unit/pre-strategy.test.ts +++ b/test/unit/pre-strategy.test.ts @@ -9,8 +9,9 @@ import { import { Ursula } from '../../src/porter'; import { toBytes } from '../../src/utils'; import { + fakeProvider, + fakeSigner, fakeUrsulas, - fakeWeb3Provider, makeCohort, mockEncryptTreasureMap, mockGenerateKFrags, @@ -30,8 +31,10 @@ const { // Shared test variables const aliceSecretKey = SecretKey.fromBEBytes(aliceSecretKeyBytes); const bobSecretKey = SecretKey.fromBEBytes(bobSecretKeyBytes); -const aliceProvider = fakeWeb3Provider(aliceSecretKey.toBEBytes()); -const bobProvider = fakeWeb3Provider(bobSecretKey.toBEBytes()); +const aliceSigner = fakeSigner(aliceSecretKey.toBEBytes()); +const aliceProvider = fakeProvider(aliceSecretKey.toBEBytes()); +const bobSigner = fakeSigner(bobSecretKey.toBEBytes()); +const bobProvider = fakeProvider(bobSecretKey.toBEBytes()); const ownsNFT = new ERC721Ownership({ contractAddress: '0x1e988ba4692e52Bc50b375bcC8585b95c48AaD77', parameters: [3591], @@ -54,7 +57,11 @@ const makeDeployedPreStrategy = async () => { const makeTreasureMapSpy = mockMakeTreasureMap(); const encryptTreasureMapSpy = mockEncryptTreasureMap(); - const deployedStrategy = await strategy.deploy(aliceProvider, 'test'); + const deployedStrategy = await strategy.deploy( + aliceProvider, + aliceSigner, + 'test' + ); expect(generateKFragsSpy).toHaveBeenCalled(); expect(publishToBlockchainSpy).toHaveBeenCalled(); @@ -126,8 +133,9 @@ describe('PreDeployedStrategy', () => { const decryptedMessage = await deployedStrategy.decrypter.retrieveAndDecrypt( - [encryptedMessageKit], - bobProvider + bobProvider, + bobSigner, + [encryptedMessageKit] ); expect(getUrsulasSpy).toHaveBeenCalled(); expect(retrieveCFragsSpy).toHaveBeenCalled(); diff --git a/test/utils.ts b/test/utils.ts index eec92f4cb..dad688591 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -81,28 +81,45 @@ export const fakeAlice = (aliceKey = 'fake-secret-key-32-bytes-alice-x') => { return Alice.fromSecretKey(secretKey); }; -export const fakeWeb3Provider = ( - secretKeyBytes = SecretKey.random().toBEBytes(), - blockNumber?: number, - blockTimestamp?: number -): ethers.providers.Web3Provider => { - const block = { timestamp: blockTimestamp ?? 1000 }; - const provider = { - getBlockNumber: () => Promise.resolve(blockNumber ?? 1000), +const makeFakeProvider = (timestamp: number, blockNumber: number) => { + const block = { timestamp }; + return { + getBlockNumber: () => Promise.resolve(blockNumber), getBlock: () => Promise.resolve(block as Block), _isProvider: true, getNetwork: () => Promise.resolve({ name: 'mockNetwork', chainId: -1 }), }; - const fakeSignerWithProvider = { +}; + +export const fakeSigner = ( + secretKeyBytes = SecretKey.random().toBEBytes(), + blockNumber = 1000, + blockTimestamp = 1000 +) => { + const provider = makeFakeProvider(blockNumber, blockTimestamp); + return { ...new Wallet(secretKeyBytes), - provider, + provider: provider, _signTypedData: () => Promise.resolve('fake-typed-signature'), signMessage: () => Promise.resolve('fake-signature'), getAddress: () => Promise.resolve('0x0000000000000000000000000000000000000000'), } as unknown as ethers.providers.JsonRpcSigner; +}; + +export const fakeProvider = ( + secretKeyBytes = SecretKey.random().toBEBytes(), + blockNumber = 1000, + blockTimestamp = 1000 +): ethers.providers.Web3Provider => { + const fakeProvider = makeFakeProvider(blockTimestamp, blockNumber); + const fakeSignerWithProvider = fakeSigner( + secretKeyBytes, + blockNumber, + blockTimestamp + ); return { - ...provider, + ...fakeProvider, getSigner: () => fakeSignerWithProvider, } as unknown as ethers.providers.Web3Provider; }; @@ -497,12 +514,6 @@ export const fakeDkgRitual = (ritual: { ); }; -export const mockInitializeRitual = (ritualId: number) => { - return jest.spyOn(DkgClient, 'initializeRitual').mockImplementation(() => { - return Promise.resolve(ritualId); - }); -}; - export const mockGetExistingRitual = (dkgRitual: DkgRitual) => { return jest.spyOn(DkgClient, 'getExistingRitual').mockImplementation(() => { return Promise.resolve(dkgRitual); @@ -517,9 +528,3 @@ export const makeCohort = async (ursulas: Ursula[]) => { expect(getUrsulasSpy).toHaveBeenCalled(); return cohort; }; - -export const mockGetRitualState = (state = DkgRitualState.FINALIZED) => { - return jest - .spyOn(DkgCoordinatorAgent, 'getRitualState') - .mockImplementation((_provider, _ritualId) => Promise.resolve(state)); -}; diff --git a/yarn.lock b/yarn.lock index 13117376f..24fe06151 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3439,7 +3439,7 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -ethers@^5.4.1: +ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==