From 85ad24f783ace7d65dd82cae7acc0badca5f9d69 Mon Sep 17 00:00:00 2001 From: poocart <7067483+poocart@users.noreply.github.com> Date: Wed, 25 Oct 2023 10:47:44 +0300 Subject: [PATCH] added send tests --- CHANGELOG.md | 5 + __mocks__/@etherspot/prime-sdk.js | 29 +- .../hooks/useEtherspotTransactions.test.js | 276 +++++++++++++++++- package-lock.json | 4 +- package.json | 2 +- ...EtherspotTransactionKitContextProvider.tsx | 2 +- 6 files changed, 307 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 747f388..b8c929b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [0.6.3] - 2023-10-24 + +### Added Changes +- Added missing `useEtherspotTransactions` hook tests for `send` method + ## [0.6.2] - 2023-10-24 ### Added Changes diff --git a/__mocks__/@etherspot/prime-sdk.js b/__mocks__/@etherspot/prime-sdk.js index 164713f..7ed9106 100644 --- a/__mocks__/@etherspot/prime-sdk.js +++ b/__mocks__/@etherspot/prime-sdk.js @@ -7,6 +7,7 @@ const otherAccountAddress = '0xAb4C67d8D7B248B2fA6B638C645466065fE8F1F1' export class PrimeSdk { sdkChainId; userOps = []; + nonce = ethers.BigNumber.from(1); constructor (provider, config) { this.sdkChainId = config.chainId; @@ -191,6 +192,7 @@ export class PrimeSdk { let maxFeePerGas = ethers.utils.parseUnits('1', 'gwei'); let maxPriorityFeePerGas = ethers.utils.parseUnits('1', 'gwei'); let callGasLimit = ethers.BigNumber.from('50000'); + let signature = '0x004'; if (paymaster?.url === 'someUrl') { maxFeePerGas = ethers.utils.parseUnits('2', 'gwei'); @@ -198,6 +200,10 @@ export class PrimeSdk { callGasLimit = ethers.BigNumber.from('75000'); } + if (paymaster?.url === 'someUnstableUrl') { + signature = '0x0'; + } + let finalGasLimit = ethers.BigNumber.from(callGasLimit); if (this.sdkChainId === 420) { @@ -218,7 +224,7 @@ export class PrimeSdk { return { sender: defaultAccountAddress, - nonce: ethers.BigNumber.from(1), + nonce: this.nonce, initCode: '0x001', callData: '0x002', callGasLimit: finalGasLimit, @@ -227,13 +233,32 @@ export class PrimeSdk { maxFeePerGas, maxPriorityFeePerGas, paymasterAndData: '0x003', - signature: '0x004', + signature, } } totalGasEstimated({ callGasLimit, verificationGasLimit, preVerificationGas }) { return callGasLimit.add(verificationGasLimit).add(preVerificationGas); } + + async send(userOp) { + if (this.sdkChainId === 696969) { + throw new Error('Transaction reverted: chain too hot'); + } + + if (userOp.signature === '0x0') { + throw new Error('Transaction reverted: invalid signature'); + } + + /** + * provide fake userOp hash by increasing nonce on each send + * and add SDK chain ID to make it more unique per userOp + */ + const userOpHash = this.nonce.add(this.sdkChainId).toHexString(); + this.nonce = this.nonce.add(1); + + return userOpHash; + } } export const isWalletProvider = EtherspotPrime.isWalletProvider; diff --git a/__tests__/hooks/useEtherspotTransactions.test.js b/__tests__/hooks/useEtherspotTransactions.test.js index c246925..9ba6fa0 100644 --- a/__tests__/hooks/useEtherspotTransactions.test.js +++ b/__tests__/hooks/useEtherspotTransactions.test.js @@ -168,7 +168,7 @@ describe('useEtherspotTransactions()', () => { .toThrow('No parent '); }); - it('estimates single grouped batches', async () => { + it('single grouped batches estimate returns cost and send returns userOp hash successfully', async () => { const wrapper = ({ children }) => (
@@ -204,10 +204,54 @@ describe('useEtherspotTransactions()', () => { const { result } = renderHook(() => useEtherspotTransactions(), { wrapper }); const estimated = await result.current.estimate(); + expect(ethers.BigNumber.isBigNumber(estimated[0].estimatedBatches[0].cost)).toBe(true); expect(estimated[0].estimatedBatches[0].cost.toString()).toBe('350000'); + + const sent = await result.current.send(); + expect(sent[0].sentBatches[0].userOpHash).toBe('0x7c'); }); - it('estimates multiple grouped batches with skipped and with no batches', async () => { + it('estimates and sends single grouped batches without calling estimate', async () => { + const wrapper = ({ children }) => ( + +
+ test + + + + + + + + + +
+ + {children} +
+ ); + + const { result } = renderHook(() => useEtherspotTransactions(), { wrapper }); + + const sent = await result.current.send(); + expect(sent[0].estimatedBatches[0].cost.toString()).toBe('350000'); + expect(sent[0].sentBatches[0].userOpHash).toBe('0x7c'); + }); + + it('estimates and sends multiple grouped batches with skipped and with no batches', async () => { const wrapper = ({ children }) => (
@@ -267,10 +311,17 @@ describe('useEtherspotTransactions()', () => { const estimated = await result.current.estimate(); expect(estimated[0].estimatedBatches[0].cost.toString()).toBe('350000'); expect(estimated[0].estimatedBatches[1].cost.toString()).toBe('200000'); + expect(estimated[1].estimatedBatches.length).toBe(0); // has skip prop expect(estimated[2].estimatedBatches[0].cost.toString()).toBe('250000'); + + const sent = await result.current.send(); + expect(sent[0].sentBatches[0].userOpHash).toBe('0x7c'); + expect(sent[0].sentBatches[1].userOpHash).toBe('0x7d'); + expect(sent[1].sentBatches.length).toBe(0); // has skip prop + expect(sent[2].sentBatches[0].userOpHash).toBe('0x46'); }); - it('estimates multiple grouped batches with paymaster', async () => { + it('estimates and sends multiple grouped batches with paymaster', async () => { const wrapper = ({ children }) => (
@@ -317,9 +368,13 @@ describe('useEtherspotTransactions()', () => { const estimated = await result.current.estimate(); expect(estimated[0].estimatedBatches[0].cost.toString()).toBe('350000'); expect(estimated[1].estimatedBatches[0].cost.toString()).toBe('325000'); + + const sent = await result.current.send(); + expect(sent[0].sentBatches[0].userOpHash).toBe('0x7c'); + expect(sent[1].sentBatches[0].userOpHash).toBe('0x46'); }); - it('estimates multiple grouped batches with matching chain IDs', async () => { + it('estimates and sends multiple grouped batches with matching chain IDs', async () => { const wrapper = ({ children }) => (
@@ -384,6 +439,12 @@ describe('useEtherspotTransactions()', () => { expect(estimated[0].estimatedBatches[1].cost.toString()).toBe('200000'); expect(estimated[1].estimatedBatches[0].cost.toString()).toBe('325000'); expect(estimated[2].estimatedBatches[0].cost.toString()).toBe('325000'); + + const sent = await result.current.send(); + expect(sent[0].sentBatches[0].userOpHash).toBe('0x7c'); + expect(sent[0].sentBatches[1].userOpHash).toBe('0x7e'); + expect(sent[1].sentBatches[0].userOpHash).toBe('0x46'); + expect(sent[2].sentBatches[0].userOpHash).toBe('0x7d'); }); it('estimates and calls onEstimated for each batch group', async () => { @@ -448,6 +509,68 @@ describe('useEtherspotTransactions()', () => { expect(onEstimated2.mock.calls[0][0]).toStrictEqual(estimated[1].estimatedBatches); }); + it('sends and calls onSent for each batch group', async () => { + const onSent1 = jest.fn((sent) => sent); + const onSent2 = jest.fn((sent) => sent); + + const wrapper = ({ children }) => ( + +
+ test + + + + + + + + + + + + +
+ + + + + + + {children} +
+ ); + + const { result } = renderHook(() => useEtherspotTransactions(), { wrapper }); + + const sent = await result.current.send(); + + expect(onSent1).toBeCalledTimes(1); + expect(onSent2).toBeCalledTimes(1); + expect(onSent1.mock.calls[0][0]).toStrictEqual(sent[0].sentBatches); + expect(onSent2.mock.calls[0][0]).toStrictEqual(sent[1].sentBatches); + }); + it('estimates and returns error messages for each batch group', async () => { const onEstimated1 = jest.fn((estimated) => estimated); const onEstimated2 = jest.fn((estimated) => estimated); @@ -503,7 +626,67 @@ describe('useEtherspotTransactions()', () => { expect(onEstimated2.mock.calls[0][0]).toStrictEqual(estimated[1].estimatedBatches); }); - it('estimates and returns error messages for batch group by ID', async () => { + it('estimates successfully and returns error messages on send for each batch group', async () => { + const onSent1 = jest.fn((sent) => sent); + const onSent2 = jest.fn((sent) => sent); + + const wrapper = ({ children }) => ( + +
+ test + + + + + + + + + +
+ + + + + + + {children} +
+ ); + + const { result } = renderHook(() => useEtherspotTransactions(), { wrapper }); + + const estimated = await result.current.estimate(); + + expect(estimated[0].estimatedBatches[0].cost.toString()).toBe('350000'); + expect(estimated[1].estimatedBatches[0].cost.toString()).toBe('250000'); + + const sent = await result.current.send(); + + expect(sent[0].sentBatches[0].errorMessage).toBe('Transaction reverted: chain too hot'); + expect(sent[1].sentBatches[0].errorMessage).toBe('Transaction reverted: invalid signature'); + expect(onSent1.mock.calls[0][0]).toStrictEqual(sent[0].sentBatches); + expect(onSent2.mock.calls[0][0]).toStrictEqual(sent[1].sentBatches); + }); + + it('estimates valid and returns error messages for invalid batch group by ID', async () => { const onEstimated1 = jest.fn((estimated) => estimated); const onEstimated2 = jest.fn((estimated) => estimated); const onEstimated3 = jest.fn((estimated) => estimated); @@ -585,4 +768,87 @@ describe('useEtherspotTransactions()', () => { expect(onEstimated2.mock.calls[0][0]).toStrictEqual(estimated2[0].estimatedBatches); expect(onEstimated3.mock.calls[0][0]).toStrictEqual(estimated2[1].estimatedBatches); }); + + it('sends valid and returns error messages for invalid batch group by ID', async () => { + const onSent1 = jest.fn((estimated) => estimated); + const onSent2 = jest.fn((estimated) => estimated); + const onSent3 = jest.fn((estimated) => estimated); + + const wrapper = ({ children }) => ( + +
+ test + + + + + + + + + +
+ + + + + + + + + + + + + + + + + {children} +
+ ); + + const { result } = renderHook(() => useEtherspotTransactions(), { wrapper }); + + const sent1 = await result.current.send(['test-id-1']); + expect(sent1.length).toBe(1); + expect(sent1[0].sentBatches[0].errorMessage).toBe('Transaction reverted: chain too hot'); + expect(onSent1.mock.calls[0][0]).toStrictEqual(sent1[0].sentBatches); + + const sent2 = await result.current.send(['test-id-2', 'test-id-3']); + expect(sent2.length).toBe(2); + expect(sent2[0].sentBatches[0].userOpHash).toBe('0x46'); + expect(sent2[1].sentBatches[0].userOpHash).toBe('0x47'); + expect(onSent2.mock.calls[0][0]).toStrictEqual(sent2[0].sentBatches); + expect(onSent3.mock.calls[0][0]).toStrictEqual(sent2[1].sentBatches); + }); }) diff --git a/package-lock.json b/package-lock.json index 6bdb7a7..255cfc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@etherspot/transaction-kit", - "version": "0.6.2", + "version": "0.6.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@etherspot/transaction-kit", - "version": "0.6.2", + "version": "0.6.3", "license": "MIT", "dependencies": { "@etherspot/eip1271-verification-util": "^0.1.2", diff --git a/package.json b/package.json index f70abc1..9a60e2b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@etherspot/transaction-kit", "description": "React Etherspot Transaction Kit", - "version": "0.6.2", + "version": "0.6.3", "main": "dist/cjs/index.js", "scripts": { "rollup:build": "NODE_OPTIONS=--max-old-space-size=8192 rollup -c", diff --git a/src/providers/EtherspotTransactionKitContextProvider.tsx b/src/providers/EtherspotTransactionKitContextProvider.tsx index 2a298d0..3208eec 100644 --- a/src/providers/EtherspotTransactionKitContextProvider.tsx +++ b/src/providers/EtherspotTransactionKitContextProvider.tsx @@ -114,7 +114,7 @@ const EtherspotTransactionKitContextProvider = ({ children }: EtherspotTransacti const userOpHash = await etherspotPrimeSdk.send(estimatedBatch.userOp); sentBatches.push({ ...estimatedBatch, userOpHash }); } catch (e) { - sentBatches.push({ ...estimatedBatch, errorMessage: (e instanceof Error && e.message) || 'Failed to estimate!' }); + sentBatches.push({ ...estimatedBatch, errorMessage: (e instanceof Error && e.message) || 'Failed to send!' }); } }