From c8ea3eacc395718f9bdeee9e4166add7c424cb6a Mon Sep 17 00:00:00 2001 From: Michele Esposito <34438276+mikesposito@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:11:16 +0100 Subject: [PATCH] Refactor user operations tests (#3832) ## Explanation This PR refactors tests related to user operations methods, to align them with the structure used for other methods. It also adds test coverage for keyrings that do not support user operations ## References * Related to #3830 ## Changelog No functional changes ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've highlighted breaking changes using the "BREAKING" category above as appropriate --- .../src/KeyringController.test.ts | 338 +++++++++++------- 1 file changed, 204 insertions(+), 134 deletions(-) diff --git a/packages/keyring-controller/src/KeyringController.test.ts b/packages/keyring-controller/src/KeyringController.test.ts index e870f4914b..9b9be3fa31 100644 --- a/packages/keyring-controller/src/KeyringController.test.ts +++ b/packages/keyring-controller/src/KeyringController.test.ts @@ -1576,156 +1576,226 @@ describe('KeyringController', () => { }); }); - describe('UserOperation methods', () => { - const addresses: Hex[] = ['0x660265edc169bab511a40c0e049cc1e33774443d']; - - it('should prepare base user operation', async () => { - await withController( - { keyringBuilders: [keyringBuilderFactory(MockErc4337Keyring)] }, - async ({ controller }) => { - const mockKeyring = (await controller.addNewKeyring( - MockErc4337Keyring.type, - )) as EthKeyring; - - jest - .spyOn(mockKeyring, 'getAccounts') - .mockResolvedValueOnce(addresses); - - const baseUserOp = { - callData: '0x7064', - initCode: '0x22ff', - nonce: '0x1', - gasLimits: { - callGasLimit: '0x58a83', - verificationGasLimit: '0xe8c4', - preVerificationGas: '0xc57c', - }, - dummySignature: '0x', - dummyPaymasterAndData: '0x', - bundlerUrl: 'https://bundler.example.com/rpc', - }; + describe('prepareUserOperation', () => { + describe('when the keyring for the given address supports prepareUserOperation', () => { + it('should prepare base user operation', async () => { + const address = '0x660265edc169bab511a40c0e049cc1e33774443d'; + stubKeyringClassWithAccount(MockErc4337Keyring, address); + await withController( + { keyringBuilders: [keyringBuilderFactory(MockErc4337Keyring)] }, + async ({ controller }) => { + const mockKeyring = (await controller.addNewKeyring( + MockErc4337Keyring.type, + )) as EthKeyring; + const baseUserOp = { + callData: '0x7064', + initCode: '0x22ff', + nonce: '0x1', + gasLimits: { + callGasLimit: '0x58a83', + verificationGasLimit: '0xe8c4', + preVerificationGas: '0xc57c', + }, + dummySignature: '0x', + dummyPaymasterAndData: '0x', + bundlerUrl: 'https://bundler.example.com/rpc', + }; + const baseTxs = [ + { + to: '', + value: '0x0', + data: '0x7064', + }, + ]; + jest + .spyOn(mockKeyring, 'prepareUserOperation') + .mockResolvedValueOnce(baseUserOp); - const baseTxs = [ - { - to: '', - value: '0x0', - data: '0x7064', - }, - ]; + const result = await controller.prepareUserOperation( + address, + baseTxs, + ); - jest - .spyOn(mockKeyring, 'prepareUserOperation') - .mockResolvedValueOnce(baseUserOp); + expect(result).toStrictEqual(baseUserOp); + expect(mockKeyring.prepareUserOperation).toHaveBeenCalledTimes(1); + expect(mockKeyring.prepareUserOperation).toHaveBeenCalledWith( + address, + baseTxs, + ); + }, + ); + }); + }); - const result = await controller.prepareUserOperation( - addresses[0], - baseTxs, - ); + describe('when the keyring for the given address does not support prepareUserOperation', () => { + it('should throw error', async () => { + const address = '0x660265edc169bab511a40c0e049cc1e33774443d'; + stubKeyringClassWithAccount(MockKeyring, address); + await withController( + { keyringBuilders: [keyringBuilderFactory(MockKeyring)] }, + async ({ controller }) => { + await controller.addNewKeyring(MockKeyring.type); - expect(result).toStrictEqual(baseUserOp); - expect(mockKeyring.prepareUserOperation).toHaveBeenCalledTimes(1); - expect(mockKeyring.prepareUserOperation).toHaveBeenCalledWith( - addresses[0], - baseTxs, - ); - }, - ); + await expect( + controller.prepareUserOperation(address, []), + ).rejects.toThrow( + KeyringControllerError.UnsupportedPrepareUserOperation, + ); + }, + ); + }); }); + }); - it('should patch an user operation', async () => { - await withController( - { keyringBuilders: [keyringBuilderFactory(MockErc4337Keyring)] }, - async ({ controller }) => { - const mockKeyring = (await controller.addNewKeyring( - MockErc4337Keyring.type, - )) as EthKeyring; - - jest - .spyOn(mockKeyring, 'getAccounts') - .mockResolvedValueOnce(addresses); - - const userOp = { - sender: '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4', - nonce: '0x1', - initCode: '0x', - callData: '0x7064', - callGasLimit: '0x58a83', - verificationGasLimit: '0xe8c4', - preVerificationGas: '0xc57c', - maxFeePerGas: '0x87f0878c0', - maxPriorityFeePerGas: '0x1dcd6500', - paymasterAndData: '0x', - signature: '0x', - }; + describe('patchUserOperation', () => { + describe('when the keyring for the given address supports patchUserOperation', () => { + it('should patch an user operation', async () => { + const address = '0x660265edc169bab511a40c0e049cc1e33774443d'; + stubKeyringClassWithAccount(MockErc4337Keyring, address); + await withController( + { keyringBuilders: [keyringBuilderFactory(MockErc4337Keyring)] }, + async ({ controller }) => { + const mockKeyring = (await controller.addNewKeyring( + MockErc4337Keyring.type, + )) as EthKeyring; + const userOp = { + sender: '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4', + nonce: '0x1', + initCode: '0x', + callData: '0x7064', + callGasLimit: '0x58a83', + verificationGasLimit: '0xe8c4', + preVerificationGas: '0xc57c', + maxFeePerGas: '0x87f0878c0', + maxPriorityFeePerGas: '0x1dcd6500', + paymasterAndData: '0x', + signature: '0x', + }; + const patch = { + paymasterAndData: '0x1234', + }; + jest + .spyOn(mockKeyring, 'patchUserOperation') + .mockResolvedValueOnce(patch); - const patch = { - paymasterAndData: '0x1234', - }; + const result = await controller.patchUserOperation(address, userOp); - jest - .spyOn(mockKeyring, 'patchUserOperation') - .mockResolvedValueOnce(patch); + expect(result).toStrictEqual(patch); + expect(mockKeyring.patchUserOperation).toHaveBeenCalledTimes(1); + expect(mockKeyring.patchUserOperation).toHaveBeenCalledWith( + address, + userOp, + ); + }, + ); + }); + }); - const result = await controller.patchUserOperation( - addresses[0], - userOp, - ); + describe('when the keyring for the given address does not support patchUserOperation', () => { + it('should throw error', async () => { + const address = '0x660265edc169bab511a40c0e049cc1e33774443d'; + stubKeyringClassWithAccount(MockKeyring, address); + await withController( + { keyringBuilders: [keyringBuilderFactory(MockKeyring)] }, + async ({ controller }) => { + await controller.addNewKeyring(MockKeyring.type); + const userOp = { + sender: '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4', + nonce: '0x1', + initCode: '0x', + callData: '0x7064', + callGasLimit: '0x58a83', + verificationGasLimit: '0xe8c4', + preVerificationGas: '0xc57c', + maxFeePerGas: '0x87f0878c0', + maxPriorityFeePerGas: '0x1dcd6500', + paymasterAndData: '0x', + signature: '0x', + }; - expect(result).toStrictEqual(patch); - expect(mockKeyring.patchUserOperation).toHaveBeenCalledTimes(1); - expect(mockKeyring.patchUserOperation).toHaveBeenCalledWith( - addresses[0], - userOp, - ); - }, - ); + await expect( + controller.patchUserOperation(address, userOp), + ).rejects.toThrow( + KeyringControllerError.UnsupportedPatchUserOperation, + ); + }, + ); + }); }); + }); - it('should sign an user operation', async () => { - await withController( - { keyringBuilders: [keyringBuilderFactory(MockErc4337Keyring)] }, - async ({ controller }) => { - const mockKeyring = (await controller.addNewKeyring( - MockErc4337Keyring.type, - )) as EthKeyring; - - jest - .spyOn(mockKeyring, 'getAccounts') - .mockResolvedValueOnce(addresses); - - const userOp = { - sender: '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4', - nonce: '0x1', - initCode: '0x', - callData: '0x7064', - callGasLimit: '0x58a83', - verificationGasLimit: '0xe8c4', - preVerificationGas: '0xc57c', - maxFeePerGas: '0x87f0878c0', - maxPriorityFeePerGas: '0x1dcd6500', - paymasterAndData: '0x', - signature: '0x', - }; + describe('signUserOperation', () => { + describe('when the keyring for the given address supports signUserOperation', () => { + it('should sign an user operation', async () => { + const address = '0x660265edc169bab511a40c0e049cc1e33774443d'; + stubKeyringClassWithAccount(MockErc4337Keyring, address); + await withController( + { keyringBuilders: [keyringBuilderFactory(MockErc4337Keyring)] }, + async ({ controller }) => { + const mockKeyring = (await controller.addNewKeyring( + MockErc4337Keyring.type, + )) as EthKeyring; + const userOp = { + sender: '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4', + nonce: '0x1', + initCode: '0x', + callData: '0x7064', + callGasLimit: '0x58a83', + verificationGasLimit: '0xe8c4', + preVerificationGas: '0xc57c', + maxFeePerGas: '0x87f0878c0', + maxPriorityFeePerGas: '0x1dcd6500', + paymasterAndData: '0x', + signature: '0x', + }; + const signature = '0x1234'; + jest + .spyOn(mockKeyring, 'signUserOperation') + .mockResolvedValueOnce(signature); - const signature = '0x1234'; + const result = await controller.signUserOperation(address, userOp); - jest - .spyOn(mockKeyring, 'signUserOperation') - .mockResolvedValueOnce(signature); + expect(result).toStrictEqual(signature); + expect(mockKeyring.signUserOperation).toHaveBeenCalledTimes(1); + expect(mockKeyring.signUserOperation).toHaveBeenCalledWith( + address, + userOp, + ); + }, + ); + }); + }); - const result = await controller.signUserOperation( - addresses[0], - userOp, - ); + describe('when the keyring for the given address does not support signUserOperation', () => { + it('should throw error', async () => { + const address = '0x660265edc169bab511a40c0e049cc1e33774443d'; + stubKeyringClassWithAccount(MockKeyring, address); + await withController( + { keyringBuilders: [keyringBuilderFactory(MockKeyring)] }, + async ({ controller }) => { + await controller.addNewKeyring(MockKeyring.type); + const userOp = { + sender: '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4', + nonce: '0x1', + initCode: '0x', + callData: '0x7064', + callGasLimit: '0x58a83', + verificationGasLimit: '0xe8c4', + preVerificationGas: '0xc57c', + maxFeePerGas: '0x87f0878c0', + maxPriorityFeePerGas: '0x1dcd6500', + paymasterAndData: '0x', + signature: '0x', + }; - expect(result).toStrictEqual(signature); - expect(mockKeyring.signUserOperation).toHaveBeenCalledTimes(1); - expect(mockKeyring.signUserOperation).toHaveBeenCalledWith( - addresses[0], - userOp, - ); - }, - ); + await expect( + controller.signUserOperation(address, userOp), + ).rejects.toThrow( + KeyringControllerError.UnsupportedSignUserOperation, + ); + }, + ); + }); }); });