diff --git a/CHANGELOG.md b/CHANGELOG.md index 2567def..747f388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [0.6.2] - 2023-10-24 + +### Added Changes +- Added missing `useEtherspotTransactions` hook tests for `estimate` method +- Fixed `skip` prop to ignore batch group estimations +- Fixed batching for same chain ID SDK instance + ## [0.6.1] - 2023-10-24 ### Added Changes diff --git a/__mocks__/@etherspot/prime-sdk.js b/__mocks__/@etherspot/prime-sdk.js index b60feff..164713f 100644 --- a/__mocks__/@etherspot/prime-sdk.js +++ b/__mocks__/@etherspot/prime-sdk.js @@ -6,6 +6,7 @@ const otherAccountAddress = '0xAb4C67d8D7B248B2fA6B638C645466065fE8F1F1' export class PrimeSdk { sdkChainId; + userOps = []; constructor (provider, config) { this.sdkChainId = config.chainId; @@ -177,6 +178,62 @@ export class PrimeSdk { return { items: prices } } + + async clearUserOpsFromBatch() { + this.userOps = []; + } + + async addUserOpsToBatch(userOp) { + this.userOps.push(userOp); + } + + async estimate(paymaster) { + let maxFeePerGas = ethers.utils.parseUnits('1', 'gwei'); + let maxPriorityFeePerGas = ethers.utils.parseUnits('1', 'gwei'); + let callGasLimit = ethers.BigNumber.from('50000'); + + if (paymaster?.url === 'someUrl') { + maxFeePerGas = ethers.utils.parseUnits('2', 'gwei'); + maxPriorityFeePerGas = ethers.utils.parseUnits('3', 'gwei'); + callGasLimit = ethers.BigNumber.from('75000'); + } + + let finalGasLimit = ethers.BigNumber.from(callGasLimit); + + if (this.sdkChainId === 420) { + throw new Error('Transaction reverted: chain too high'); + } + + this.userOps.forEach((userOp) => { + if (userOp.to === '0xDEADBEEF') { + throw new Error('Transaction reverted: invalid address'); + } + finalGasLimit = finalGasLimit.add(callGasLimit); + if (userOp.data + && userOp.data !== '0x0' + && userOp.data !== '0xFFF') { + finalGasLimit = finalGasLimit.add(callGasLimit); + } + }); + + return { + sender: defaultAccountAddress, + nonce: ethers.BigNumber.from(1), + initCode: '0x001', + callData: '0x002', + callGasLimit: finalGasLimit, + verificationGasLimit: ethers.BigNumber.from('25000'), + preVerificationGas: ethers.BigNumber.from('75000'), + maxFeePerGas, + maxPriorityFeePerGas, + paymasterAndData: '0x003', + signature: '0x004', + } + } + + totalGasEstimated({ callGasLimit, verificationGasLimit, preVerificationGas }) { + return callGasLimit.add(verificationGasLimit).add(preVerificationGas); + } } export const isWalletProvider = EtherspotPrime.isWalletProvider; diff --git a/__tests__/hooks/useEtherspotTransactions.test.js b/__tests__/hooks/useEtherspotTransactions.test.js index 1fb4273..c246925 100644 --- a/__tests__/hooks/useEtherspotTransactions.test.js +++ b/__tests__/hooks/useEtherspotTransactions.test.js @@ -167,4 +167,422 @@ describe('useEtherspotTransactions()', () => { )) .toThrow('No parent '); }); + + it('estimates single grouped batches', async () => { + 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'); + }); + + it('estimates multiple grouped batches with skipped and with no batches', async () => { + const wrapper = ({ children }) => ( + +
+ test + + + + + + + + + + + + +
+ + test + + + + + + + + test + + + {children} +
+ ); + + const { result } = renderHook(() => useEtherspotTransactions(), { wrapper }); + + 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[2].estimatedBatches[0].cost.toString()).toBe('250000'); + }); + + it('estimates multiple grouped batches with paymaster', async () => { + 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('325000'); + }); + + it('estimates multiple grouped batches with matching chain IDs', async () => { + 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[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'); + }); + + it('estimates and calls onEstimated for each batch group', async () => { + const onEstimated1 = jest.fn((estimated) => estimated); + const onEstimated2 = jest.fn((estimated) => estimated); + + const wrapper = ({ children }) => ( + +
+ test + + + + + + + + + + + + +
+ + + + + + + {children} +
+ ); + + const { result } = renderHook(() => useEtherspotTransactions(), { wrapper }); + + const estimated = await result.current.estimate(); + + expect(onEstimated1).toBeCalledTimes(1); + expect(onEstimated2).toBeCalledTimes(1); + expect(onEstimated1.mock.calls[0][0]).toStrictEqual(estimated[0].estimatedBatches); + expect(onEstimated2.mock.calls[0][0]).toStrictEqual(estimated[1].estimatedBatches); + }); + + it('estimates and returns error messages for each batch group', async () => { + const onEstimated1 = jest.fn((estimated) => estimated); + const onEstimated2 = jest.fn((estimated) => estimated); + + const wrapper = ({ children }) => ( + +
+ test + + + + + + + + + +
+ + + + + + + {children} +
+ ); + + const { result } = renderHook(() => useEtherspotTransactions(), { wrapper }); + + const estimated = await result.current.estimate(); + + expect(estimated[0].estimatedBatches[0].errorMessage).toBe('Transaction reverted: chain too high'); + expect(estimated[1].estimatedBatches[0].errorMessage).toBe('Transaction reverted: invalid address'); + expect(onEstimated1.mock.calls[0][0]).toStrictEqual(estimated[0].estimatedBatches); + expect(onEstimated2.mock.calls[0][0]).toStrictEqual(estimated[1].estimatedBatches); + }); + + it('estimates and returns error messages for batch group by ID', async () => { + const onEstimated1 = jest.fn((estimated) => estimated); + const onEstimated2 = jest.fn((estimated) => estimated); + const onEstimated3 = jest.fn((estimated) => estimated); + + const wrapper = ({ children }) => ( + +
+ test + + + + + + + + + +
+ + + + + + + + + + + + + + + + + {children} +
+ ); + + const { result } = renderHook(() => useEtherspotTransactions(), { wrapper }); + + const estimated1 = await result.current.estimate(['test-id-1']); + expect(estimated1.length).toBe(1); + expect(estimated1[0].estimatedBatches[0].errorMessage).toBe('Transaction reverted: chain too high'); + expect(onEstimated1.mock.calls[0][0]).toStrictEqual(estimated1[0].estimatedBatches); + + const estimated2 = await result.current.estimate(['test-id-2', 'test-id-3']); + expect(estimated2.length).toBe(2); + expect(estimated2[0].estimatedBatches[0].cost.toString()).toBe('325000'); + expect(estimated2[1].estimatedBatches[0].cost.toString()).toBe('200000'); + expect(onEstimated2.mock.calls[0][0]).toStrictEqual(estimated2[0].estimatedBatches); + expect(onEstimated3.mock.calls[0][0]).toStrictEqual(estimated2[1].estimatedBatches); + }); }) diff --git a/package-lock.json b/package-lock.json index 286fe1f..6bdb7a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@etherspot/transaction-kit", - "version": "0.6.0", + "version": "0.6.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@etherspot/transaction-kit", - "version": "0.6.0", + "version": "0.6.2", "license": "MIT", "dependencies": { "@etherspot/eip1271-verification-util": "^0.1.2", diff --git a/package.json b/package.json index e7faa41..f70abc1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@etherspot/transaction-kit", "description": "React Etherspot Transaction Kit", - "version": "0.6.1", + "version": "0.6.2", "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 7f1a466..2a298d0 100644 --- a/src/providers/EtherspotTransactionKitContextProvider.tsx +++ b/src/providers/EtherspotTransactionKitContextProvider.tsx @@ -31,6 +31,8 @@ const EtherspotTransactionKitContextProvider = ({ children }: EtherspotTransacti return Promise.all(groupedBatchesToEstimate.map(async (groupedBatch): Promise => { const batches = (groupedBatch.batches ?? []) as IBatch[]; + if (groupedBatch.skip) return { ...groupedBatch, estimatedBatches: [] }; + const estimatedBatches: EstimatedBatch[] = []; // push estimations in same order @@ -47,7 +49,8 @@ const EtherspotTransactionKitContextProvider = ({ children }: EtherspotTransacti continue; } - const etherspotPrimeSdk = await getSdk(batchChainId); + // force new instance for each batch to not mix up user ops added to SDK state batch + const etherspotPrimeSdk = await getSdk(batchChainId, true); try { if (!forSending) await etherspotPrimeSdk.clearUserOpsFromBatch();