diff --git a/src/hooks/__tests__/useIsValidExecution.test.ts b/src/hooks/__tests__/useIsValidExecution.test.ts index 74a57d4bc9..2147887fbc 100644 --- a/src/hooks/__tests__/useIsValidExecution.test.ts +++ b/src/hooks/__tests__/useIsValidExecution.test.ts @@ -1,15 +1,11 @@ -import { ethers } from 'ethers' import { BigNumber } from '@ethersproject/bignumber' import type { SafeTransaction, SafeSignature } from '@safe-global/safe-core-sdk-types' -import { type SafeInfo } from '@gnosis.pm/safe-react-gateway-sdk' +import type Safe from '@safe-global/safe-core-sdk' -import * as safeContracts from '@/services/contracts/safeContracts' -import * as useWalletHook from '@/hooks/wallets/useWallet' -import * as useSafeInfoHook from '@/hooks/useSafeInfo' +import * as sdk from '@/hooks/coreSDK/safeCoreSDK' import { act, renderHook } from '@/tests/test-utils' import useIsValidExecution from '../useIsValidExecution' import type { EthersError } from '@/utils/ethers-utils' -import type { ConnectedWallet } from '../wallets/useOnboard' import type { EthersTxReplacedReason } from '@/utils/ethers-utils' const createSafeTx = (data = '0x'): SafeTransaction => { @@ -35,151 +31,24 @@ const createSafeTx = (data = '0x'): SafeTransaction => { } as SafeTransaction } -let mockTx: SafeTransaction - -const mockGas = BigNumber.from(1000) +// `isValidTransaction` has full test coverage in `safe-core-sdk` +// https://github.com/safe-global/safe-core-sdk/blob/main/packages/safe-core-sdk/tests/execution.test.ts#L37-L101 describe('useIsValidExecution', () => { beforeEach(() => { jest.resetAllMocks() - - jest.spyOn(useWalletHook, 'default').mockReturnValue({ - address: ethers.utils.hexZeroPad('0x123', 20), - } as ConnectedWallet) - - jest.spyOn(useSafeInfoHook, 'default').mockReturnValue({ - safe: { - owners: [ - { - value: ethers.utils.hexZeroPad('0x123', 20), - }, - ], - } as SafeInfo, - safeAddress: ethers.utils.hexZeroPad('0x456', 20), - safeLoaded: true, - safeLoading: false, - }) - - // Create a new tx for each test case - mockTx = createSafeTx() - }) - - it('should add a missing signature and return a boolean if the transaction is valid', async () => { - jest.spyOn(safeContracts, 'getSpecificGnosisSafeContractInstance').mockReturnValue({ - contract: { - // @ts-expect-error - callStatic: { - execTransaction: jest.fn((_1, _2, _3, _4, _5, _6, _7, _8, _9, signatures: string) => - Promise.resolve(signatures.includes(ethers.utils.hexZeroPad('0x123', 20))), - ), - }, - }, - }) - - const { result } = renderHook(() => useIsValidExecution(mockTx, mockGas)) - - let { isValidExecution, executionValidationError, isValidExecutionLoading } = result.current - - expect(isValidExecution).toEqual(undefined) - expect(executionValidationError).toBe(undefined) - expect(isValidExecutionLoading).toBe(true) - - await act(async () => { - await new Promise(process.nextTick) - }) - ;({ isValidExecution, executionValidationError, isValidExecutionLoading } = result.current) - - expect(isValidExecution).toBe(true) - expect(executionValidationError).toBe(undefined) - expect(isValidExecutionLoading).toBe(false) - }) - - it('should not add a signature if no owner is connected and return a boolean if the transaction is valid', async () => { - // Connect a different owner and add a different sig - jest.spyOn(useWalletHook, 'default').mockReturnValue({ - address: ethers.utils.hexZeroPad('0xabc', 20), - } as ConnectedWallet) - - jest.spyOn(safeContracts, 'getSpecificGnosisSafeContractInstance').mockReturnValue({ - contract: { - // @ts-expect-error - callStatic: { - execTransaction: jest.fn((_1, _2, _3, _4, _5, _6, _7, _8, _9, signatures: string) => - Promise.resolve( - signatures.includes(ethers.utils.hexZeroPad('0x123', 20)) && - !signatures.includes(ethers.utils.hexZeroPad('0xabc', 20)), - ), - ), - }, - }, - }) - - mockTx.addSignature({ - signer: ethers.utils.hexZeroPad('0x123', 20), - data: '0xEEE', - staticPart: () => '0xEEE', - dynamicPart: () => '', - }) - - const { result } = renderHook(() => useIsValidExecution(mockTx, mockGas)) - - let { isValidExecution, executionValidationError, isValidExecutionLoading } = result.current - - expect(isValidExecution).toEqual(undefined) - expect(executionValidationError).toBe(undefined) - expect(isValidExecutionLoading).toBe(true) - - await act(async () => { - await new Promise(process.nextTick) - }) - ;({ isValidExecution, executionValidationError, isValidExecutionLoading } = result.current) - - expect(isValidExecution).toBe(true) - expect(executionValidationError).toBe(undefined) - expect(isValidExecutionLoading).toBe(false) - }) - - it('should throw if the transaction is invalid', async () => { - jest.spyOn(safeContracts, 'getSpecificGnosisSafeContractInstance').mockReturnValue({ - contract: { - // @ts-expect-error - callStatic: { - execTransaction: jest.fn(() => Promise.reject('Some error')), - }, - }, - }) - - const { result } = renderHook(() => useIsValidExecution(mockTx, mockGas)) - - var { isValidExecution, executionValidationError, isValidExecutionLoading } = result.current - - expect(isValidExecution).toEqual(undefined) - expect(executionValidationError).toBe(undefined) - expect(isValidExecutionLoading).toBe(true) - - await act(async () => { - await new Promise(process.nextTick) - }) - - var { isValidExecution, executionValidationError, isValidExecutionLoading } = result.current - - expect(isValidExecution).toBe(undefined) - expect(executionValidationError).toBe('Some error') - expect(isValidExecutionLoading).toBe(false) }) it('should append the error code description to the error thrown', async () => { const error = new Error('Some error') as EthersError error.reason = 'GS026' as EthersTxReplacedReason - jest.spyOn(safeContracts, 'getSpecificGnosisSafeContractInstance').mockReturnValue({ - contract: { - // @ts-expect-error - callStatic: { - execTransaction: jest.fn(() => Promise.reject(error)), - }, - }, - }) + jest.spyOn(sdk, 'useSafeSDK').mockReturnValue({ + isValidTransaction: jest.fn().mockRejectedValue(error), + } as unknown as Safe) + + const mockTx = createSafeTx() + const mockGas = BigNumber.from(1000) const { result } = renderHook(() => useIsValidExecution(mockTx, mockGas)) diff --git a/src/hooks/useIsValidExecution.ts b/src/hooks/useIsValidExecution.ts index bbeca7726f..d9d333a886 100644 --- a/src/hooks/useIsValidExecution.ts +++ b/src/hooks/useIsValidExecution.ts @@ -1,14 +1,10 @@ -import { getSpecificGnosisSafeContractInstance } from '@/services/contracts/safeContracts' import type { SafeTransaction } from '@safe-global/safe-core-sdk-types' import type { BigNumber } from 'ethers' import type { EthersError } from '@/utils/ethers-utils' import useAsync from './useAsync' -import useSafeInfo from './useSafeInfo' -import useWallet from './wallets/useWallet' -import { encodeSignatures } from '@/services/tx/encodeSignatures' import ContractErrorCodes from '@/services/contracts/ContractErrorCodes' -import { sameAddress } from '@/utils/addresses' +import { useSafeSDK } from './coreSDK/safeCoreSDK' const isContractError = (error: T): error is T & { reason: keyof typeof ContractErrorCodes } => { return Object.keys(ContractErrorCodes).includes(error.reason) @@ -22,32 +18,14 @@ const useIsValidExecution = ( executionValidationError?: Error isValidExecutionLoading: boolean } => { - const wallet = useWallet() - const { safe } = useSafeInfo() + const safeSdk = useSafeSDK() const [isValidExecution, executionValidationError, isValidExecutionLoading] = useAsync(async () => { - if (!safeTx || !wallet?.address || !gasLimit) { + if (!safeTx || !safeSdk || !gasLimit) { return } - - const isSafeOwnerConnected = safe.owners.some((owner) => sameAddress(owner.value, wallet.address)) - - const { contract } = getSpecificGnosisSafeContractInstance(safe) - try { - return await contract.callStatic.execTransaction( - safeTx.data.to, - safeTx.data.value, - safeTx.data.data, - safeTx.data.operation, - safeTx.data.safeTxGas, - safeTx.data.baseGas, - safeTx.data.gasPrice, - safeTx.data.gasToken, - safeTx.data.refundReceiver, - encodeSignatures(safeTx, isSafeOwnerConnected ? wallet.address : undefined), - { from: wallet.address, gasLimit: gasLimit.toString() }, - ) + return await safeSdk.isValidTransaction(safeTx, { gasLimit: gasLimit.toString() }) } catch (_err) { const err = _err as EthersError @@ -58,7 +36,7 @@ const useIsValidExecution = ( throw err } - }, [safeTx, wallet?.address, gasLimit, safe]) + }, [safeTx, safeSdk, gasLimit]) return { isValidExecution, executionValidationError, isValidExecutionLoading } }