From dfa4e1f0a1df9d778366c92d1aca52bae8123979 Mon Sep 17 00:00:00 2001 From: schmanu Date: Tue, 24 May 2022 18:06:55 +0200 Subject: [PATCH 01/18] fix: show error when detecting invalid master copy address --- .../AppLayout/Sidebar/SafeHeader/index.tsx | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx index f943596be2..7ed15a5fe8 100644 --- a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx +++ b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx @@ -35,6 +35,12 @@ import { trackEvent } from 'src/utils/googleTagManager' import useSafeAddress from 'src/logic/currentSession/hooks/useSafeAddress' import { Box } from '@material-ui/core' import { currentSafe } from 'src/logic/safe/store/selectors' +import useAsync from 'src/logic/hooks/useAsync' +import { getMasterCopyAddressFromProxyAddress } from 'src/logic/contracts/safeContracts' +import semverSatisfies from 'semver/functions/satisfies' +import { getSafeL2SingletonDeployment, getSafeSingletonDeployment } from '@gnosis.pm/safe-deployments' +import { Tooltip } from 'src/components/layout/Tooltip' +import { sameAddress } from 'src/logic/wallets/ethAddresses' export const TOGGLE_SIDEBAR_BTN_TESTID = 'TOGGLE_SIDEBAR_BTN' @@ -76,6 +82,7 @@ const IconContainer = styled.div` display: flex; gap: 8px; justify-content: space-evenly; + align-items: center; margin: 14px 0; ` const StyledButton = styled(Button)` @@ -209,7 +216,7 @@ const SafeHeader = ({ onReceiveClick, onNewTransactionClick, }: Props): React.ReactElement => { - const { owners, threshold } = useSelector(currentSafe) + const { owners, threshold, currentVersion } = useSelector(currentSafe) const copyChainPrefix = useSelector(copyShortNameSelector) const { shortName } = useSafeAddress() @@ -220,6 +227,23 @@ const SafeHeader = ({ onNewTransactionClick() } + const chainInfo = getChainInfo() + + const [masterCopyError] = useAsync(async () => { + if (address && currentVersion) { + debugger + const masterCopyAddressFromProxy = await getMasterCopyAddressFromProxyAddress(address) + const { l2, chainId } = chainInfo + const useL2ContractVersion = l2 && semverSatisfies(currentVersion, '>=1.3.0') + const getDeployment = useL2ContractVersion ? getSafeL2SingletonDeployment : getSafeSingletonDeployment + const expectedMasterCopyAddress = getDeployment()?.networkAddresses[chainId] + + return sameAddress(masterCopyAddressFromProxy, expectedMasterCopyAddress) + ? null + : 'Invalid master copy address in proxy contract. This safe was not created through the web interface. There may be issues interacting with it.' + } + }, [address, chainInfo, currentVersion]) + if (!address || !hasSafeOpen) { return ( @@ -233,7 +257,6 @@ const SafeHeader = ({ ) } - const chainInfo = getChainInfo() return ( <> @@ -271,6 +294,13 @@ const SafeHeader = ({ + {masterCopyError && ( + + + + + + )} From 2798f6ab8fac22bdd321654f10690678b0e937da Mon Sep 17 00:00:00 2001 From: schmanu Date: Tue, 24 May 2022 18:41:01 +0200 Subject: [PATCH 02/18] refactor and test new master copy validation --- .../AppLayout/Sidebar/SafeHeader/index.tsx | 16 ++----- .../safe/utils/__tests__/safeVersion.test.ts | 48 ++++++++++++++++++- src/logic/safe/utils/safeVersion.ts | 20 +++++++- 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx index 7ed15a5fe8..61a1ccd9e7 100644 --- a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx +++ b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx @@ -36,11 +36,8 @@ import useSafeAddress from 'src/logic/currentSession/hooks/useSafeAddress' import { Box } from '@material-ui/core' import { currentSafe } from 'src/logic/safe/store/selectors' import useAsync from 'src/logic/hooks/useAsync' -import { getMasterCopyAddressFromProxyAddress } from 'src/logic/contracts/safeContracts' -import semverSatisfies from 'semver/functions/satisfies' -import { getSafeL2SingletonDeployment, getSafeSingletonDeployment } from '@gnosis.pm/safe-deployments' import { Tooltip } from 'src/components/layout/Tooltip' -import { sameAddress } from 'src/logic/wallets/ethAddresses' +import { isValidMasterCopy } from 'src/logic/safe/utils/safeVersion' export const TOGGLE_SIDEBAR_BTN_TESTID = 'TOGGLE_SIDEBAR_BTN' @@ -231,16 +228,9 @@ const SafeHeader = ({ const [masterCopyError] = useAsync(async () => { if (address && currentVersion) { - debugger - const masterCopyAddressFromProxy = await getMasterCopyAddressFromProxyAddress(address) - const { l2, chainId } = chainInfo - const useL2ContractVersion = l2 && semverSatisfies(currentVersion, '>=1.3.0') - const getDeployment = useL2ContractVersion ? getSafeL2SingletonDeployment : getSafeSingletonDeployment - const expectedMasterCopyAddress = getDeployment()?.networkAddresses[chainId] - - return sameAddress(masterCopyAddressFromProxy, expectedMasterCopyAddress) + ;(await isValidMasterCopy(chainInfo, address, currentVersion)) ? null - : 'Invalid master copy address in proxy contract. This safe was not created through the web interface. There may be issues interacting with it.' + : 'Invalid master copy address in proxy contract. This safe was not created through the web interface and there may be issues interacting with it.' } }, [address, chainInfo, currentVersion]) diff --git a/src/logic/safe/utils/__tests__/safeVersion.test.ts b/src/logic/safe/utils/__tests__/safeVersion.test.ts index 10cfad951c..ebe56a992a 100644 --- a/src/logic/safe/utils/__tests__/safeVersion.test.ts +++ b/src/logic/safe/utils/__tests__/safeVersion.test.ts @@ -1,5 +1,11 @@ +import { getSafeSingletonDeployment } from '@gnosis.pm/safe-deployments' import { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' -import { checkIfSafeNeedsUpdate, hasFeature } from 'src/logic/safe/utils/safeVersion' +import { emptyChainInfo } from 'src/config/cache/chains' +import { checkIfSafeNeedsUpdate, hasFeature, isValidMasterCopy } from 'src/logic/safe/utils/safeVersion' + +jest.mock('src/logic/contracts/safeContracts', () => ({ + getMasterCopyAddressFromProxyAddress: jest.fn(), +})) describe('Check safe version', () => { it('Calls checkIfSafeNeedUpdate, should return true if the safe version is bellow the target one', async () => { @@ -36,4 +42,44 @@ describe('Check safe version', () => { expect(hasFeature(FEATURES.SAFE_APPS, '1.1.1')).toBe(true) }) }) + + describe('isValidMasterCopy', () => { + const safeContracts = require('src/logic/contracts/safeContracts') + + it('returns true for L1 mastercopy in L1 chain', async () => { + safeContracts.getMasterCopyAddressFromProxyAddress.mockReturnValue( + getSafeSingletonDeployment()?.networkAddresses['1'], + ) + const isValid = await isValidMasterCopy( + { ...emptyChainInfo, chainId: '1', l2: false }, + '0x0000000000000000000000000000000000000001', + '1.3.0', + ) + expect(isValid).toBe(true) + }) + + it('returns false for L1 mastercopy in L2 chain and version >=1.3.0', async () => { + safeContracts.getMasterCopyAddressFromProxyAddress.mockReturnValue( + getSafeSingletonDeployment()?.networkAddresses['100'], + ) + const isValid = await isValidMasterCopy( + { ...emptyChainInfo, chainId: '100', l2: true }, + '0x0000000000000000000000000000000000000001', + '1.3.0', + ) + expect(isValid).toBe(false) + }) + + it('returns true for L1 mastercopy in L2 chain and version <1.3.0', async () => { + safeContracts.getMasterCopyAddressFromProxyAddress.mockReturnValue( + getSafeSingletonDeployment()?.networkAddresses['100'], + ) + const isValid = await isValidMasterCopy( + { ...emptyChainInfo, chainId: '100', l2: true }, + '0x0000000000000000000000000000000000000001', + '1.1.0', + ) + expect(isValid).toBe(true) + }) + }) }) diff --git a/src/logic/safe/utils/safeVersion.ts b/src/logic/safe/utils/safeVersion.ts index 0c853357f1..066cf5d919 100644 --- a/src/logic/safe/utils/safeVersion.ts +++ b/src/logic/safe/utils/safeVersion.ts @@ -1,13 +1,15 @@ import semverLessThan from 'semver/functions/lt' import semverSatisfies from 'semver/functions/satisfies' import semverValid from 'semver/functions/valid' -import { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' +import { ChainInfo, FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' import { GnosisSafe } from 'src/types/contracts/gnosis_safe.d' -import { getSafeMasterContract } from 'src/logic/contracts/safeContracts' +import { getMasterCopyAddressFromProxyAddress, getSafeMasterContract } from 'src/logic/contracts/safeContracts' import { LATEST_SAFE_VERSION } from 'src/utils/constants' import { Errors, logError } from 'src/logic/exceptions/CodedException' import { getChainInfo } from 'src/config' +import { getSafeL2SingletonDeployment, getSafeSingletonDeployment } from '@gnosis.pm/safe-deployments' +import { sameAddress } from 'src/logic/wallets/ethAddresses' const FEATURES_BY_VERSION: Record = { [FEATURES.SAFE_TX_GAS_OPTIONAL]: '>=1.3.0', @@ -86,3 +88,17 @@ export const getSafeVersionInfo = async (safeVersion: string): Promise => { + const masterCopyAddressFromProxy = await getMasterCopyAddressFromProxyAddress(safeAddress) + const { l2, chainId } = chainInfo + const useL2ContractVersion = l2 && semverSatisfies(safeVersion, '>=1.3.0') + const getDeployment = useL2ContractVersion ? getSafeL2SingletonDeployment : getSafeSingletonDeployment + const expectedMasterCopyAddress = getDeployment()?.networkAddresses[chainId] + + return sameAddress(masterCopyAddressFromProxy, expectedMasterCopyAddress) +} From ec594233b017d965418094715844166b76be0737 Mon Sep 17 00:00:00 2001 From: schmanu Date: Tue, 24 May 2022 18:48:45 +0200 Subject: [PATCH 03/18] fix: small error in rendering --- src/components/AppLayout/Sidebar/SafeHeader/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx index 61a1ccd9e7..3fec907b3a 100644 --- a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx +++ b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx @@ -228,7 +228,8 @@ const SafeHeader = ({ const [masterCopyError] = useAsync(async () => { if (address && currentVersion) { - ;(await isValidMasterCopy(chainInfo, address, currentVersion)) + const isValid = await isValidMasterCopy(chainInfo, address, currentVersion) + return isValid ? null : 'Invalid master copy address in proxy contract. This safe was not created through the web interface and there may be issues interacting with it.' } From ac7b9df3c8cfb38b2824c53ce201326d5f5d013a Mon Sep 17 00:00:00 2001 From: schmanu Date: Wed, 25 May 2022 14:25:54 +0200 Subject: [PATCH 04/18] change: use topbar alert instead of tooltip * refactor master copy validation to reuse safeContracts getSafeContractDeployment * change test + new testcase --- .../InvalidMasterCopyError/index.tsx | 55 ++++++++++++++++ .../AppLayout/Sidebar/SafeHeader/index.tsx | 21 +------ src/components/AppLayout/index.tsx | 5 ++ src/logic/contracts/safeContracts.ts | 8 ++- src/logic/exceptions/registry.ts | 1 + .../safe/utils/__tests__/safeVersion.test.ts | 62 ++++++++++--------- src/logic/safe/utils/safeVersion.ts | 18 +++--- 7 files changed, 112 insertions(+), 58 deletions(-) create mode 100644 src/components/AppLayout/InvalidMasterCopyError/index.tsx diff --git a/src/components/AppLayout/InvalidMasterCopyError/index.tsx b/src/components/AppLayout/InvalidMasterCopyError/index.tsx new file mode 100644 index 0000000000..d8922e738a --- /dev/null +++ b/src/components/AppLayout/InvalidMasterCopyError/index.tsx @@ -0,0 +1,55 @@ +import { Link } from '@gnosis.pm/safe-react-components' +import { useSelector } from 'react-redux' +import { getChainInfo } from 'src/config' +import { Errors, logError } from 'src/logic/exceptions/CodedException' +import useAsync from 'src/logic/hooks/useAsync' +import { currentSafe } from 'src/logic/safe/store/selectors' +import { isValidMasterCopy } from 'src/logic/safe/utils/safeVersion' +import MuiAlert from '@material-ui/lab/Alert' +import MuiAlertTitle from '@material-ui/lab/AlertTitle' +import { withStyles } from '@material-ui/core' + +const CLI_LINK = 'https://github.com/5afe/safe-cli' + +export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): React.ReactElement | null => { + const chainInfo = getChainInfo() + const { currentVersion, address } = useSelector(currentSafe) + + const [validMasterCopy, error] = useAsync(async () => { + if (address && currentVersion) { + return await isValidMasterCopy(chainInfo.chainId, address, currentVersion) + } + }, [address, chainInfo, currentVersion]) + + if (error) { + logError(Errors._620, error.message) + return null + } + + if (typeof validMasterCopy === 'undefined' || validMasterCopy) { + return null + } + + return ( + + + This safe is using an invalid master copy contract. The web interface might not work correctly. It's + recommended to use the{' '} + + command line interface + {' '} + instead. + + + ) +} + +const StyledAlert = withStyles({ + icon: { + marginLeft: 'auto', + }, + action: { + marginLeft: '0px', + marginRight: 'auto', + }, +})(MuiAlert) diff --git a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx index 3fec907b3a..058d7d2b3e 100644 --- a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx +++ b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx @@ -35,9 +35,6 @@ import { trackEvent } from 'src/utils/googleTagManager' import useSafeAddress from 'src/logic/currentSession/hooks/useSafeAddress' import { Box } from '@material-ui/core' import { currentSafe } from 'src/logic/safe/store/selectors' -import useAsync from 'src/logic/hooks/useAsync' -import { Tooltip } from 'src/components/layout/Tooltip' -import { isValidMasterCopy } from 'src/logic/safe/utils/safeVersion' export const TOGGLE_SIDEBAR_BTN_TESTID = 'TOGGLE_SIDEBAR_BTN' @@ -213,7 +210,7 @@ const SafeHeader = ({ onReceiveClick, onNewTransactionClick, }: Props): React.ReactElement => { - const { owners, threshold, currentVersion } = useSelector(currentSafe) + const { owners, threshold } = useSelector(currentSafe) const copyChainPrefix = useSelector(copyShortNameSelector) const { shortName } = useSafeAddress() @@ -226,15 +223,6 @@ const SafeHeader = ({ const chainInfo = getChainInfo() - const [masterCopyError] = useAsync(async () => { - if (address && currentVersion) { - const isValid = await isValidMasterCopy(chainInfo, address, currentVersion) - return isValid - ? null - : 'Invalid master copy address in proxy contract. This safe was not created through the web interface and there may be issues interacting with it.' - } - }, [address, chainInfo, currentVersion]) - if (!address || !hasSafeOpen) { return ( @@ -285,13 +273,6 @@ const SafeHeader = ({ - {masterCopyError && ( - - - - - - )} diff --git a/src/components/AppLayout/index.tsx b/src/components/AppLayout/index.tsx index 2657dca456..bab43deb52 100644 --- a/src/components/AppLayout/index.tsx +++ b/src/components/AppLayout/index.tsx @@ -11,6 +11,7 @@ import { MobileNotSupported } from './MobileNotSupported' import { SAFE_APP_LANDING_PAGE_ROUTE, SAFE_ROUTES, WELCOME_ROUTE } from 'src/routes/routes' import useDarkMode from 'src/logic/hooks/useDarkMode' import { screenSm } from 'src/theme/variables' +import { InvalidMasterCopyError } from 'src/components/AppLayout/InvalidMasterCopyError' const Container = styled.div` height: 100vh; @@ -23,6 +24,7 @@ const Container = styled.div` const HeaderWrapper = styled.nav` height: 52px; + min-height: 52px; width: 100%; z-index: 1299; @@ -134,6 +136,7 @@ const Layout: React.FC = ({ }): React.ReactElement => { const [mobileNotSupportedClosed, setMobileNotSupportedClosed] = useState(false) const [expanded, setExpanded] = useState(false) + const [showMasterCopyError, setShowMasterCopyError] = useState(true) const { pathname } = useLocation() useDarkMode() @@ -157,6 +160,8 @@ const Layout: React.FC = ({
+ {showMasterCopyError && setShowMasterCopyError(false)} />} + {showSideBar && ( diff --git a/src/logic/contracts/safeContracts.ts b/src/logic/contracts/safeContracts.ts index df6f6486e1..c3a4b4373c 100644 --- a/src/logic/contracts/safeContracts.ts +++ b/src/logic/contracts/safeContracts.ts @@ -6,6 +6,7 @@ import { getFallbackHandlerDeployment, getMultiSendCallOnlyDeployment, getSignMessageLibDeployment, + SingletonDeployment, } from '@gnosis.pm/safe-deployments' import Web3 from 'web3' import { AbiItem } from 'web3-utils' @@ -30,12 +31,17 @@ let safeMaster: GnosisSafe let fallbackHandler: CompatibilityFallbackHandler let multiSend: MultiSend -const getSafeContractDeployment = ({ safeVersion }: { safeVersion: string }) => { +export const getSafeContractDeployment = ({ + safeVersion, +}: { + safeVersion: string +}): SingletonDeployment | undefined => { // We check if version is prior to v1.0.0 as they are not supported but still we want to keep a minimum compatibility const useOldestContractVersion = semverSatisfies(safeVersion, '<1.0.0') // We have to check if network is L2 const networkId = _getChainId() const chainConfig = getChainById(networkId) + // We had L1 contracts in three L2 networks, xDai, EWC and Volta so even if network is L2 we have to check that safe version is after v1.3.0 const useL2ContractVersion = chainConfig.l2 && semverSatisfies(safeVersion, '>=1.3.0') const getDeployment = useL2ContractVersion ? getSafeL2SingletonDeployment : getSafeSingletonDeployment diff --git a/src/logic/exceptions/registry.ts b/src/logic/exceptions/registry.ts index b516287475..b55bbdb84a 100644 --- a/src/logic/exceptions/registry.ts +++ b/src/logic/exceptions/registry.ts @@ -31,6 +31,7 @@ enum ErrorCodes { _617 = '617: Error fetching safeTxGas', _618 = '618: Error fetching fee history', _619 = '619: Failed to prepare a multisend tx', + _620 = '620: Failed to load master copy information', _700 = '700: Failed to read from local/session storage', _701 = '701: Failed to write to local/session storage', _702 = '702: Failed to remove from local/session storage', diff --git a/src/logic/safe/utils/__tests__/safeVersion.test.ts b/src/logic/safe/utils/__tests__/safeVersion.test.ts index ebe56a992a..b1a274f4e3 100644 --- a/src/logic/safe/utils/__tests__/safeVersion.test.ts +++ b/src/logic/safe/utils/__tests__/safeVersion.test.ts @@ -1,12 +1,10 @@ -import { getSafeSingletonDeployment } from '@gnosis.pm/safe-deployments' +import { getSafeL2SingletonDeployment, getSafeSingletonDeployment } from '@gnosis.pm/safe-deployments' +import * as config from 'src/config' +import * as safeContracts from 'src/logic/contracts/safeContracts' import { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' import { emptyChainInfo } from 'src/config/cache/chains' import { checkIfSafeNeedsUpdate, hasFeature, isValidMasterCopy } from 'src/logic/safe/utils/safeVersion' -jest.mock('src/logic/contracts/safeContracts', () => ({ - getMasterCopyAddressFromProxyAddress: jest.fn(), -})) - describe('Check safe version', () => { it('Calls checkIfSafeNeedUpdate, should return true if the safe version is bellow the target one', async () => { const safeVersion = '1.0.0' @@ -47,38 +45,44 @@ describe('Check safe version', () => { const safeContracts = require('src/logic/contracts/safeContracts') it('returns true for L1 mastercopy in L1 chain', async () => { - safeContracts.getMasterCopyAddressFromProxyAddress.mockReturnValue( - getSafeSingletonDeployment()?.networkAddresses['1'], - ) - const isValid = await isValidMasterCopy( - { ...emptyChainInfo, chainId: '1', l2: false }, - '0x0000000000000000000000000000000000000001', - '1.3.0', - ) + jest.spyOn(config, '_getChainId').mockImplementation(() => '1') + jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '1', l2: false })) + + jest + .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') + .mockImplementation(() => getSafeSingletonDeployment()?.networkAddresses['1']) + const isValid = await isValidMasterCopy('1', '0x0000000000000000000000000000000000000001', '1.3.0') expect(isValid).toBe(true) }) it('returns false for L1 mastercopy in L2 chain and version >=1.3.0', async () => { - safeContracts.getMasterCopyAddressFromProxyAddress.mockReturnValue( - getSafeSingletonDeployment()?.networkAddresses['100'], - ) - const isValid = await isValidMasterCopy( - { ...emptyChainInfo, chainId: '100', l2: true }, - '0x0000000000000000000000000000000000000001', - '1.3.0', - ) + jest.spyOn(config, '_getChainId').mockImplementation(() => '100') + jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) + jest + .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') + .mockImplementation(() => getSafeSingletonDeployment()?.networkAddresses['100']) + const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.3.0') + expect(isValid).toBe(false) + }) + + it('returns false for L1 mastercopy in L2 chain and version contains meta info', async () => { + jest.spyOn(config, '_getChainId').mockImplementation(() => '100') + jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) + jest + .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') + .mockImplementation(() => getSafeL2SingletonDeployment({ version: '1.1.1' })?.networkAddresses['100']) + + const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.3.0+L2') expect(isValid).toBe(false) }) it('returns true for L1 mastercopy in L2 chain and version <1.3.0', async () => { - safeContracts.getMasterCopyAddressFromProxyAddress.mockReturnValue( - getSafeSingletonDeployment()?.networkAddresses['100'], - ) - const isValid = await isValidMasterCopy( - { ...emptyChainInfo, chainId: '100', l2: true }, - '0x0000000000000000000000000000000000000001', - '1.1.0', - ) + jest.spyOn(config, '_getChainId').mockImplementation(() => '100') + jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) + jest + .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') + .mockImplementation(() => getSafeSingletonDeployment({ version: '1.1.1' })?.networkAddresses['100']) + const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.1.1') expect(isValid).toBe(true) }) }) diff --git a/src/logic/safe/utils/safeVersion.ts b/src/logic/safe/utils/safeVersion.ts index 066cf5d919..15c82c241d 100644 --- a/src/logic/safe/utils/safeVersion.ts +++ b/src/logic/safe/utils/safeVersion.ts @@ -1,14 +1,18 @@ import semverLessThan from 'semver/functions/lt' import semverSatisfies from 'semver/functions/satisfies' import semverValid from 'semver/functions/valid' -import { ChainInfo, FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' +import semverClean from 'semver/functions/clean' +import { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' import { GnosisSafe } from 'src/types/contracts/gnosis_safe.d' -import { getMasterCopyAddressFromProxyAddress, getSafeMasterContract } from 'src/logic/contracts/safeContracts' +import { + getMasterCopyAddressFromProxyAddress, + getSafeContractDeployment, + getSafeMasterContract, +} from 'src/logic/contracts/safeContracts' import { LATEST_SAFE_VERSION } from 'src/utils/constants' import { Errors, logError } from 'src/logic/exceptions/CodedException' import { getChainInfo } from 'src/config' -import { getSafeL2SingletonDeployment, getSafeSingletonDeployment } from '@gnosis.pm/safe-deployments' import { sameAddress } from 'src/logic/wallets/ethAddresses' const FEATURES_BY_VERSION: Record = { @@ -90,15 +94,13 @@ export const getSafeVersionInfo = async (safeVersion: string): Promise => { const masterCopyAddressFromProxy = await getMasterCopyAddressFromProxyAddress(safeAddress) - const { l2, chainId } = chainInfo - const useL2ContractVersion = l2 && semverSatisfies(safeVersion, '>=1.3.0') - const getDeployment = useL2ContractVersion ? getSafeL2SingletonDeployment : getSafeSingletonDeployment - const expectedMasterCopyAddress = getDeployment()?.networkAddresses[chainId] + const expectedDeployment = getSafeContractDeployment({ safeVersion: semverClean(safeVersion) }) + const expectedMasterCopyAddress = expectedDeployment?.networkAddresses[chainId] return sameAddress(masterCopyAddressFromProxy, expectedMasterCopyAddress) } From e5127d34d10bfa5bfdfd2e1ab4acf961650abbf9 Mon Sep 17 00:00:00 2001 From: schmanu Date: Wed, 25 May 2022 15:33:59 +0200 Subject: [PATCH 05/18] reword error --- src/components/AppLayout/InvalidMasterCopyError/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AppLayout/InvalidMasterCopyError/index.tsx b/src/components/AppLayout/InvalidMasterCopyError/index.tsx index d8922e738a..7f9afd800a 100644 --- a/src/components/AppLayout/InvalidMasterCopyError/index.tsx +++ b/src/components/AppLayout/InvalidMasterCopyError/index.tsx @@ -33,8 +33,8 @@ export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): Re return ( - This safe is using an invalid master copy contract. The web interface might not work correctly. It's - recommended to use the{' '} + This safe is using an invalid master copy contract. The web interface might not work correctly. We recommend + using the{' '} command line interface {' '} From 8016673dcc69dcfda65c7296b7d9b40d6a1b1a5a Mon Sep 17 00:00:00 2001 From: schmanu Date: Wed, 25 May 2022 16:21:33 +0200 Subject: [PATCH 06/18] fix: reword error message --- src/components/AppLayout/InvalidMasterCopyError/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AppLayout/InvalidMasterCopyError/index.tsx b/src/components/AppLayout/InvalidMasterCopyError/index.tsx index 7f9afd800a..5541edf199 100644 --- a/src/components/AppLayout/InvalidMasterCopyError/index.tsx +++ b/src/components/AppLayout/InvalidMasterCopyError/index.tsx @@ -33,7 +33,7 @@ export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): Re return ( - This safe is using an invalid master copy contract. The web interface might not work correctly. We recommend + This Safe is created with an unsupported base contract. The web interface might not work correctly. We recommend using the{' '} command line interface From 55c2360089c65206d567920c8749c584e3901f68 Mon Sep 17 00:00:00 2001 From: katspaugh <381895+katspaugh@users.noreply.github.com> Date: Fri, 27 May 2022 08:48:52 +0200 Subject: [PATCH 07/18] Update src/components/AppLayout/InvalidMasterCopyError/index.tsx Co-authored-by: Aaron Cook --- src/components/AppLayout/InvalidMasterCopyError/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AppLayout/InvalidMasterCopyError/index.tsx b/src/components/AppLayout/InvalidMasterCopyError/index.tsx index 5541edf199..fa14d3ce9d 100644 --- a/src/components/AppLayout/InvalidMasterCopyError/index.tsx +++ b/src/components/AppLayout/InvalidMasterCopyError/index.tsx @@ -33,7 +33,7 @@ export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): Re return ( - This Safe is created with an unsupported base contract. The web interface might not work correctly. We recommend + This Safe was created with an unsupported base contract. The web interface might not work correctly. We recommend using the{' '} command line interface From 780971390ddf25c812cabb31ea322554f4675119 Mon Sep 17 00:00:00 2001 From: schmanu Date: Sun, 29 May 2022 19:29:03 +0200 Subject: [PATCH 08/18] chore: add more tests, fix prettier and lint warnings --- .../InvalidMasterCopyError/index.tsx | 4 ++-- src/logic/contracts/safeContracts.ts | 17 +++++++------- .../safe/utils/__tests__/safeVersion.test.ts | 22 ++++++++++++++++++- src/logic/safe/utils/safeVersion.ts | 5 ++++- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/components/AppLayout/InvalidMasterCopyError/index.tsx b/src/components/AppLayout/InvalidMasterCopyError/index.tsx index fa14d3ce9d..f5276f3628 100644 --- a/src/components/AppLayout/InvalidMasterCopyError/index.tsx +++ b/src/components/AppLayout/InvalidMasterCopyError/index.tsx @@ -33,8 +33,8 @@ export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): Re return ( - This Safe was created with an unsupported base contract. The web interface might not work correctly. We recommend - using the{' '} + This Safe was created with an unsupported base contract. The web interface might not work correctly. We + recommend using the{' '} command line interface {' '} diff --git a/src/logic/contracts/safeContracts.ts b/src/logic/contracts/safeContracts.ts index c3a4b4373c..89f16e33a6 100644 --- a/src/logic/contracts/safeContracts.ts +++ b/src/logic/contracts/safeContracts.ts @@ -23,6 +23,7 @@ import { CompatibilityFallbackHandler } from 'src/types/contracts/compatibility_ import { SignMessageLib } from 'src/types/contracts/sign_message_lib.d' import { MultiSend } from 'src/types/contracts/multi_send.d' import { getSafeInfo } from 'src/logic/safe/utils/safeInformation' +import { NonPayableTransactionObject } from 'src/types/contracts/types' export const SENTINEL_ADDRESS = '0x0000000000000000000000000000000000000001' @@ -200,7 +201,7 @@ export const getMasterCopyAddressFromProxyAddress = async (proxyAddress: string) return masterCopyAddress } -export const instantiateSafeContracts = () => { +export const instantiateSafeContracts = (): void => { const web3 = getWeb3() const chainId = _getChainId() @@ -217,24 +218,24 @@ export const instantiateSafeContracts = () => { multiSend = getMultiSendContractInstance(web3, chainId) } -export const getSafeMasterContract = () => { +export const getSafeMasterContract = (): GnosisSafe => { instantiateSafeContracts() return safeMaster } -export const getSafeMasterContractAddress = () => { +export const getSafeMasterContractAddress = (): string => { return safeMaster.options.address } -export const getFallbackHandlerContractAddress = () => { +export const getFallbackHandlerContractAddress = (): string => { return fallbackHandler.options.address } -export const getMultisendContract = () => { +export const getMultisendContract = (): MultiSend => { return multiSend } -export const getMultisendContractAddress = () => { +export const getMultisendContractAddress = (): string => { return multiSend.options.address } @@ -242,7 +243,7 @@ export const getSafeDeploymentTransaction = ( safeAccounts: string[], numConfirmations: number, safeCreationSalt: number, -) => { +): NonPayableTransactionObject => { const gnosisSafeData = safeMaster.methods .setup( safeAccounts, @@ -263,7 +264,7 @@ export const estimateGasForDeployingSafe = async ( numConfirmations: number, userAccount: string, safeCreationSalt: number, -) => { +): Promise => { const proxyFactoryData = getSafeDeploymentTransaction(safeAccounts, numConfirmations, safeCreationSalt).encodeABI() return calculateGasOf({ diff --git a/src/logic/safe/utils/__tests__/safeVersion.test.ts b/src/logic/safe/utils/__tests__/safeVersion.test.ts index b1a274f4e3..c7fcb87b15 100644 --- a/src/logic/safe/utils/__tests__/safeVersion.test.ts +++ b/src/logic/safe/utils/__tests__/safeVersion.test.ts @@ -76,7 +76,7 @@ describe('Check safe version', () => { expect(isValid).toBe(false) }) - it('returns true for L1 mastercopy in L2 chain and version <1.3.0', async () => { + it('returns true for L1 mastercopy in L2 chain and version 1.1.1', async () => { jest.spyOn(config, '_getChainId').mockImplementation(() => '100') jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) jest @@ -85,5 +85,25 @@ describe('Check safe version', () => { const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.1.1') expect(isValid).toBe(true) }) + + it('returns true for L1 mastercopy in L2 chain and version 1.2.0', async () => { + jest.spyOn(config, '_getChainId').mockImplementation(() => '100') + jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) + jest + .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') + .mockImplementation(() => getSafeSingletonDeployment({ version: '1.2.0' })?.networkAddresses['100']) + const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.2.0') + expect(isValid).toBe(true) + }) + + it('returns true for L1 mastercopy in L2 chain and version 1.0.0', async () => { + jest.spyOn(config, '_getChainId').mockImplementation(() => '100') + jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) + jest + .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') + .mockImplementation(() => getSafeSingletonDeployment({ version: '1.0.0' })?.networkAddresses['100']) + const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.0.0') + expect(isValid).toBe(true) + }) }) }) diff --git a/src/logic/safe/utils/safeVersion.ts b/src/logic/safe/utils/safeVersion.ts index 15c82c241d..e2e0159322 100644 --- a/src/logic/safe/utils/safeVersion.ts +++ b/src/logic/safe/utils/safeVersion.ts @@ -102,5 +102,8 @@ export const isValidMasterCopy = async ( const expectedDeployment = getSafeContractDeployment({ safeVersion: semverClean(safeVersion) }) const expectedMasterCopyAddress = expectedDeployment?.networkAddresses[chainId] - return sameAddress(masterCopyAddressFromProxy, expectedMasterCopyAddress) + return ( + typeof masterCopyAddressFromProxy !== 'undefined' && + sameAddress(masterCopyAddressFromProxy, expectedMasterCopyAddress) + ) } From 2233a1ac83c2c5ca2623344f5269a7dff3705938 Mon Sep 17 00:00:00 2001 From: schmanu Date: Wed, 8 Jun 2022 11:07:34 +0200 Subject: [PATCH 09/18] fix: layout of error message, add test case --- .../InvalidMasterCopyError/index.tsx | 30 +++++-------------- .../safe/utils/__tests__/safeVersion.test.ts | 12 +++++++- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/components/AppLayout/InvalidMasterCopyError/index.tsx b/src/components/AppLayout/InvalidMasterCopyError/index.tsx index f5276f3628..b41eed5336 100644 --- a/src/components/AppLayout/InvalidMasterCopyError/index.tsx +++ b/src/components/AppLayout/InvalidMasterCopyError/index.tsx @@ -6,8 +6,6 @@ import useAsync from 'src/logic/hooks/useAsync' import { currentSafe } from 'src/logic/safe/store/selectors' import { isValidMasterCopy } from 'src/logic/safe/utils/safeVersion' import MuiAlert from '@material-ui/lab/Alert' -import MuiAlertTitle from '@material-ui/lab/AlertTitle' -import { withStyles } from '@material-ui/core' const CLI_LINK = 'https://github.com/5afe/safe-cli' @@ -31,25 +29,13 @@ export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): Re } return ( - - - This Safe was created with an unsupported base contract. The web interface might not work correctly. We - recommend using the{' '} - - command line interface - {' '} - instead. - - + + This Safe was created with an unsupported base contract. The web interface might not work correctly. We recommend + using the{' '} + + command line interface + {' '} + instead. + ) } - -const StyledAlert = withStyles({ - icon: { - marginLeft: 'auto', - }, - action: { - marginLeft: '0px', - marginRight: 'auto', - }, -})(MuiAlert) diff --git a/src/logic/safe/utils/__tests__/safeVersion.test.ts b/src/logic/safe/utils/__tests__/safeVersion.test.ts index c7fcb87b15..f29fb1fedf 100644 --- a/src/logic/safe/utils/__tests__/safeVersion.test.ts +++ b/src/logic/safe/utils/__tests__/safeVersion.test.ts @@ -55,7 +55,7 @@ describe('Check safe version', () => { expect(isValid).toBe(true) }) - it('returns false for L1 mastercopy in L2 chain and version >=1.3.0', async () => { + it('returns false for L1 mastercopy in L2 chain and version 1.3.0', async () => { jest.spyOn(config, '_getChainId').mockImplementation(() => '100') jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) jest @@ -65,6 +65,16 @@ describe('Check safe version', () => { expect(isValid).toBe(false) }) + it('returns false for L2 mastercopy in L1 chain and version 1.3.0', async () => { + jest.spyOn(config, '_getChainId').mockImplementation(() => '1') + jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '1', l2: false })) + jest + .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') + .mockImplementation(() => getSafeL2SingletonDeployment({ version: '1.3.0' })?.networkAddresses['1']) + const isValid = await isValidMasterCopy('1', '0x0000000000000000000000000000000000000001', '1.3.0') + expect(isValid).toBe(false) + }) + it('returns false for L1 mastercopy in L2 chain and version contains meta info', async () => { jest.spyOn(config, '_getChainId').mockImplementation(() => '100') jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) From 32f2c9f92851d2d15b54da10adf202ad959784ed Mon Sep 17 00:00:00 2001 From: schmanu Date: Wed, 8 Jun 2022 16:17:40 +0200 Subject: [PATCH 10/18] change: determines available valid master copies from gateway sdk The infra team will change the endpoint such that only valid master copies will be returned by the sdk. --- .../InvalidMasterCopyError/index.tsx | 8 +- src/logic/contracts/safeContracts.ts | 6 +- .../safe/utils/__tests__/safeVersion.test.ts | 100 +++++++----------- src/logic/safe/utils/safeVersion.ts | 27 ++--- 4 files changed, 53 insertions(+), 88 deletions(-) diff --git a/src/components/AppLayout/InvalidMasterCopyError/index.tsx b/src/components/AppLayout/InvalidMasterCopyError/index.tsx index b41eed5336..89b98920ef 100644 --- a/src/components/AppLayout/InvalidMasterCopyError/index.tsx +++ b/src/components/AppLayout/InvalidMasterCopyError/index.tsx @@ -11,13 +11,13 @@ const CLI_LINK = 'https://github.com/5afe/safe-cli' export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): React.ReactElement | null => { const chainInfo = getChainInfo() - const { currentVersion, address } = useSelector(currentSafe) + const { address } = useSelector(currentSafe) const [validMasterCopy, error] = useAsync(async () => { - if (address && currentVersion) { - return await isValidMasterCopy(chainInfo.chainId, address, currentVersion) + if (address) { + return await isValidMasterCopy(chainInfo.chainId, address) } - }, [address, chainInfo, currentVersion]) + }, [address, chainInfo]) if (error) { logError(Errors._620, error.message) diff --git a/src/logic/contracts/safeContracts.ts b/src/logic/contracts/safeContracts.ts index 89f16e33a6..0dbe5db08b 100644 --- a/src/logic/contracts/safeContracts.ts +++ b/src/logic/contracts/safeContracts.ts @@ -32,11 +32,7 @@ let safeMaster: GnosisSafe let fallbackHandler: CompatibilityFallbackHandler let multiSend: MultiSend -export const getSafeContractDeployment = ({ - safeVersion, -}: { - safeVersion: string -}): SingletonDeployment | undefined => { +const getSafeContractDeployment = ({ safeVersion }: { safeVersion: string }): SingletonDeployment | undefined => { // We check if version is prior to v1.0.0 as they are not supported but still we want to keep a minimum compatibility const useOldestContractVersion = semverSatisfies(safeVersion, '<1.0.0') // We have to check if network is L2 diff --git a/src/logic/safe/utils/__tests__/safeVersion.test.ts b/src/logic/safe/utils/__tests__/safeVersion.test.ts index f29fb1fedf..2e68424580 100644 --- a/src/logic/safe/utils/__tests__/safeVersion.test.ts +++ b/src/logic/safe/utils/__tests__/safeVersion.test.ts @@ -1,8 +1,6 @@ import { getSafeL2SingletonDeployment, getSafeSingletonDeployment } from '@gnosis.pm/safe-deployments' -import * as config from 'src/config' -import * as safeContracts from 'src/logic/contracts/safeContracts' import { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' -import { emptyChainInfo } from 'src/config/cache/chains' +import * as GatewaySDK from '@gnosis.pm/safe-react-gateway-sdk' import { checkIfSafeNeedsUpdate, hasFeature, isValidMasterCopy } from 'src/logic/safe/utils/safeVersion' describe('Check safe version', () => { @@ -44,75 +42,55 @@ describe('Check safe version', () => { describe('isValidMasterCopy', () => { const safeContracts = require('src/logic/contracts/safeContracts') - it('returns true for L1 mastercopy in L1 chain', async () => { - jest.spyOn(config, '_getChainId').mockImplementation(() => '1') - jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '1', l2: false })) + it('returns false if address is not contained in result', async () => { + jest.spyOn(GatewaySDK, 'getMasterCopies').mockImplementation(() => + Promise.resolve([ + { + address: '0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552', + version: '1.3.0', + deployer: 'Gnosis', + deployedBlockNumber: 12504268, + lastIndexedBlockNumber: 14927028, + l2: false, + }, + ]), + ) jest .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') - .mockImplementation(() => getSafeSingletonDeployment()?.networkAddresses['1']) - const isValid = await isValidMasterCopy('1', '0x0000000000000000000000000000000000000001', '1.3.0') - expect(isValid).toBe(true) - }) + .mockImplementation(() => '0x0000000000000000000000000000000000000005') - it('returns false for L1 mastercopy in L2 chain and version 1.3.0', async () => { - jest.spyOn(config, '_getChainId').mockImplementation(() => '100') - jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) - jest - .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') - .mockImplementation(() => getSafeSingletonDeployment()?.networkAddresses['100']) - const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.3.0') + const isValid = await isValidMasterCopy('1', '0x0000000000000000000000000000000000000001') expect(isValid).toBe(false) }) - it('returns false for L2 mastercopy in L1 chain and version 1.3.0', async () => { - jest.spyOn(config, '_getChainId').mockImplementation(() => '1') - jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '1', l2: false })) - jest - .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') - .mockImplementation(() => getSafeL2SingletonDeployment({ version: '1.3.0' })?.networkAddresses['1']) - const isValid = await isValidMasterCopy('1', '0x0000000000000000000000000000000000000001', '1.3.0') - expect(isValid).toBe(false) - }) + it('returns true if address is contained in list', async () => { + jest.spyOn(GatewaySDK, 'getMasterCopies').mockImplementation(() => + Promise.resolve([ + { + address: '0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552', + version: '1.3.0', + deployer: 'Gnosis', + deployedBlockNumber: 12504268, + lastIndexedBlockNumber: 14927028, + l2: false, + }, + { + address: '0x3E5c63644E683549055b9Be8653de26E0B4CD36E', + version: '1.3.0+L2', + deployer: 'Gnosis', + deployedBlockNumber: 12504423, + lastIndexedBlockNumber: 14927028, + l2: true, + }, + ]), + ) - it('returns false for L1 mastercopy in L2 chain and version contains meta info', async () => { - jest.spyOn(config, '_getChainId').mockImplementation(() => '100') - jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) jest .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') - .mockImplementation(() => getSafeL2SingletonDeployment({ version: '1.1.1' })?.networkAddresses['100']) - - const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.3.0+L2') - expect(isValid).toBe(false) - }) + .mockImplementation(() => getSafeL2SingletonDeployment()?.networkAddresses['1']) - it('returns true for L1 mastercopy in L2 chain and version 1.1.1', async () => { - jest.spyOn(config, '_getChainId').mockImplementation(() => '100') - jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) - jest - .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') - .mockImplementation(() => getSafeSingletonDeployment({ version: '1.1.1' })?.networkAddresses['100']) - const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.1.1') - expect(isValid).toBe(true) - }) - - it('returns true for L1 mastercopy in L2 chain and version 1.2.0', async () => { - jest.spyOn(config, '_getChainId').mockImplementation(() => '100') - jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) - jest - .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') - .mockImplementation(() => getSafeSingletonDeployment({ version: '1.2.0' })?.networkAddresses['100']) - const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.2.0') - expect(isValid).toBe(true) - }) - - it('returns true for L1 mastercopy in L2 chain and version 1.0.0', async () => { - jest.spyOn(config, '_getChainId').mockImplementation(() => '100') - jest.spyOn(config, 'getChainById').mockImplementation(() => ({ ...emptyChainInfo, chainId: '100', l2: true })) - jest - .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') - .mockImplementation(() => getSafeSingletonDeployment({ version: '1.0.0' })?.networkAddresses['100']) - const isValid = await isValidMasterCopy('100', '0x0000000000000000000000000000000000000001', '1.0.0') + const isValid = await isValidMasterCopy('1', '0x0000000000000000000000000000000000000001') expect(isValid).toBe(true) }) }) diff --git a/src/logic/safe/utils/safeVersion.ts b/src/logic/safe/utils/safeVersion.ts index e2e0159322..081b77945a 100644 --- a/src/logic/safe/utils/safeVersion.ts +++ b/src/logic/safe/utils/safeVersion.ts @@ -1,15 +1,10 @@ import semverLessThan from 'semver/functions/lt' import semverSatisfies from 'semver/functions/satisfies' import semverValid from 'semver/functions/valid' -import semverClean from 'semver/functions/clean' -import { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' +import { FEATURES, getMasterCopies } from '@gnosis.pm/safe-react-gateway-sdk' import { GnosisSafe } from 'src/types/contracts/gnosis_safe.d' -import { - getMasterCopyAddressFromProxyAddress, - getSafeContractDeployment, - getSafeMasterContract, -} from 'src/logic/contracts/safeContracts' +import { getMasterCopyAddressFromProxyAddress, getSafeMasterContract } from 'src/logic/contracts/safeContracts' import { LATEST_SAFE_VERSION } from 'src/utils/constants' import { Errors, logError } from 'src/logic/exceptions/CodedException' import { getChainInfo } from 'src/config' @@ -93,17 +88,13 @@ export const getSafeVersionInfo = async (safeVersion: string): Promise => { +export const isValidMasterCopy = async (chainId: string, safeAddress: string): Promise => { + const supportedMasterCopies = await getMasterCopies(chainId) const masterCopyAddressFromProxy = await getMasterCopyAddressFromProxyAddress(safeAddress) - const expectedDeployment = getSafeContractDeployment({ safeVersion: semverClean(safeVersion) }) - const expectedMasterCopyAddress = expectedDeployment?.networkAddresses[chainId] - - return ( - typeof masterCopyAddressFromProxy !== 'undefined' && - sameAddress(masterCopyAddressFromProxy, expectedMasterCopyAddress) + if (!masterCopyAddressFromProxy) { + return false + } + return supportedMasterCopies.some((supportedMasterCopy) => + sameAddress(supportedMasterCopy.address, masterCopyAddressFromProxy), ) } From b428720ec7874a8085e8e574fda58e1c3bd9b65d Mon Sep 17 00:00:00 2001 From: schmanu Date: Thu, 9 Jun 2022 11:38:37 +0200 Subject: [PATCH 11/18] fix: Unused import --- src/logic/safe/utils/__tests__/safeVersion.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logic/safe/utils/__tests__/safeVersion.test.ts b/src/logic/safe/utils/__tests__/safeVersion.test.ts index 2e68424580..3b662d9577 100644 --- a/src/logic/safe/utils/__tests__/safeVersion.test.ts +++ b/src/logic/safe/utils/__tests__/safeVersion.test.ts @@ -1,4 +1,4 @@ -import { getSafeL2SingletonDeployment, getSafeSingletonDeployment } from '@gnosis.pm/safe-deployments' +import { getSafeL2SingletonDeployment } from '@gnosis.pm/safe-deployments' import { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' import * as GatewaySDK from '@gnosis.pm/safe-react-gateway-sdk' import { checkIfSafeNeedsUpdate, hasFeature, isValidMasterCopy } from 'src/logic/safe/utils/safeVersion' From a448216b7e2722963797d1240711a97570ba7da2 Mon Sep 17 00:00:00 2001 From: Usame Algan Date: Thu, 16 Jun 2022 14:03:57 +0200 Subject: [PATCH 12/18] fix: Store safe implementation object and use for master copy check --- .../AppLayout/InvalidMasterCopyError/index.tsx | 8 ++++---- src/logic/safe/store/actions/utils.ts | 1 + src/logic/safe/store/models/safe.ts | 7 +++++++ src/logic/safe/store/reducer/safe.ts | 6 ++++++ .../utils/__tests__/shouldSafeStoreBeUpdated.test.ts | 5 +++++ src/logic/safe/utils/safeVersion.ts | 11 ++++------- 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/components/AppLayout/InvalidMasterCopyError/index.tsx b/src/components/AppLayout/InvalidMasterCopyError/index.tsx index 89b98920ef..7887b6e9fd 100644 --- a/src/components/AppLayout/InvalidMasterCopyError/index.tsx +++ b/src/components/AppLayout/InvalidMasterCopyError/index.tsx @@ -11,13 +11,13 @@ const CLI_LINK = 'https://github.com/5afe/safe-cli' export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): React.ReactElement | null => { const chainInfo = getChainInfo() - const { address } = useSelector(currentSafe) + const { implementation } = useSelector(currentSafe) const [validMasterCopy, error] = useAsync(async () => { - if (address) { - return await isValidMasterCopy(chainInfo.chainId, address) + if (implementation.value) { + return await isValidMasterCopy(chainInfo.chainId, implementation.value) } - }, [address, chainInfo]) + }, [chainInfo.chainId, implementation.value]) if (error) { logError(Errors._620, error.message) diff --git a/src/logic/safe/store/actions/utils.ts b/src/logic/safe/store/actions/utils.ts index 559a5e3376..c4afc92a55 100644 --- a/src/logic/safe/store/actions/utils.ts +++ b/src/logic/safe/store/actions/utils.ts @@ -99,6 +99,7 @@ export const extractRemoteSafeInfo = async (remoteSafeInfo: SafeInfo): Promise

({ modules: [], spendingLimits: [], balances: [], + implementation: { + value: '', + name: null, + logoUri: null, + }, loaded: false, nonce: 0, recurringUser: undefined, diff --git a/src/logic/safe/store/reducer/safe.ts b/src/logic/safe/store/reducer/safe.ts index e7d43e9fb4..8351f34446 100644 --- a/src/logic/safe/store/reducer/safe.ts +++ b/src/logic/safe/store/reducer/safe.ts @@ -51,6 +51,8 @@ const updateSafeProps = (prevSafe, safe) => { List.isList(safe[key]) ? record.set(key, safe[key]) : record.update(key, (current) => current.merge(safe[key])) + } else { + record.set(key, safe[key]) } } else { // Temp fix @@ -73,10 +75,14 @@ const safeReducer = handleActions( const safe = action.payload const safeAddress = safe.address + console.log(safe) + mergeNewTagsInSafe(state, safe, safeAddress) const shouldUpdate = shouldSafeStoreBeUpdated(safe, state.getIn(['safes', safeAddress]) as SafeRecordProps) + console.log(shouldUpdate) + return shouldUpdate ? state.updateIn(['safes', safeAddress], makeSafe({ address: safeAddress }), (prevSafe) => updateSafeProps(prevSafe, safe), diff --git a/src/logic/safe/utils/__tests__/shouldSafeStoreBeUpdated.test.ts b/src/logic/safe/utils/__tests__/shouldSafeStoreBeUpdated.test.ts index 6b6a60787b..fd5ec47f99 100644 --- a/src/logic/safe/utils/__tests__/shouldSafeStoreBeUpdated.test.ts +++ b/src/logic/safe/utils/__tests__/shouldSafeStoreBeUpdated.test.ts @@ -35,6 +35,11 @@ const getMockedOldSafe = ({ { tokenAddress: mockedActiveTokenAddress1, tokenBalance: '100' }, { tokenAddress: mockedActiveTokenAddress2, tokenBalance: '10' }, ], + implementation: { + value: '', + name: null, + logoUri: null, + }, loaded: true, nonce: nonce || 2, recurringUser: recurringUser || false, diff --git a/src/logic/safe/utils/safeVersion.ts b/src/logic/safe/utils/safeVersion.ts index 081b77945a..b3821d8864 100644 --- a/src/logic/safe/utils/safeVersion.ts +++ b/src/logic/safe/utils/safeVersion.ts @@ -4,7 +4,7 @@ import semverValid from 'semver/functions/valid' import { FEATURES, getMasterCopies } from '@gnosis.pm/safe-react-gateway-sdk' import { GnosisSafe } from 'src/types/contracts/gnosis_safe.d' -import { getMasterCopyAddressFromProxyAddress, getSafeMasterContract } from 'src/logic/contracts/safeContracts' +import { getSafeMasterContract } from 'src/logic/contracts/safeContracts' import { LATEST_SAFE_VERSION } from 'src/utils/constants' import { Errors, logError } from 'src/logic/exceptions/CodedException' import { getChainInfo } from 'src/config' @@ -88,13 +88,10 @@ export const getSafeVersionInfo = async (safeVersion: string): Promise => { +export const isValidMasterCopy = async (chainId: string, masterCopyAddress: string): Promise => { const supportedMasterCopies = await getMasterCopies(chainId) - const masterCopyAddressFromProxy = await getMasterCopyAddressFromProxyAddress(safeAddress) - if (!masterCopyAddressFromProxy) { - return false - } + return supportedMasterCopies.some((supportedMasterCopy) => - sameAddress(supportedMasterCopy.address, masterCopyAddressFromProxy), + sameAddress(supportedMasterCopy.address, masterCopyAddress), ) } From ad75c7e64cd33ddcb20db0fc854aeed902cc357b Mon Sep 17 00:00:00 2001 From: Usame Algan Date: Thu, 16 Jun 2022 16:09:07 +0200 Subject: [PATCH 13/18] chore: Remove console logs --- src/logic/safe/store/reducer/safe.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/logic/safe/store/reducer/safe.ts b/src/logic/safe/store/reducer/safe.ts index 8351f34446..3cf3304411 100644 --- a/src/logic/safe/store/reducer/safe.ts +++ b/src/logic/safe/store/reducer/safe.ts @@ -75,14 +75,10 @@ const safeReducer = handleActions( const safe = action.payload const safeAddress = safe.address - console.log(safe) - mergeNewTagsInSafe(state, safe, safeAddress) const shouldUpdate = shouldSafeStoreBeUpdated(safe, state.getIn(['safes', safeAddress]) as SafeRecordProps) - console.log(shouldUpdate) - return shouldUpdate ? state.updateIn(['safes', safeAddress], makeSafe({ address: safeAddress }), (prevSafe) => updateSafeProps(prevSafe, safe), From 693f860415bccf8e88e2d1658525f01be87fa614 Mon Sep 17 00:00:00 2001 From: schmanu Date: Thu, 16 Jun 2022 16:24:45 +0200 Subject: [PATCH 14/18] fix: unit test for isValidMasterCopy --- .../safe/utils/__tests__/safeVersion.test.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/logic/safe/utils/__tests__/safeVersion.test.ts b/src/logic/safe/utils/__tests__/safeVersion.test.ts index 3b662d9577..44ce634b55 100644 --- a/src/logic/safe/utils/__tests__/safeVersion.test.ts +++ b/src/logic/safe/utils/__tests__/safeVersion.test.ts @@ -1,4 +1,3 @@ -import { getSafeL2SingletonDeployment } from '@gnosis.pm/safe-deployments' import { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' import * as GatewaySDK from '@gnosis.pm/safe-react-gateway-sdk' import { checkIfSafeNeedsUpdate, hasFeature, isValidMasterCopy } from 'src/logic/safe/utils/safeVersion' @@ -40,8 +39,6 @@ describe('Check safe version', () => { }) describe('isValidMasterCopy', () => { - const safeContracts = require('src/logic/contracts/safeContracts') - it('returns false if address is not contained in result', async () => { jest.spyOn(GatewaySDK, 'getMasterCopies').mockImplementation(() => Promise.resolve([ @@ -56,11 +53,7 @@ describe('Check safe version', () => { ]), ) - jest - .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') - .mockImplementation(() => '0x0000000000000000000000000000000000000005') - - const isValid = await isValidMasterCopy('1', '0x0000000000000000000000000000000000000001') + const isValid = await isValidMasterCopy('1', '0x0000000000000000000000000000000000000005') expect(isValid).toBe(false) }) @@ -86,11 +79,7 @@ describe('Check safe version', () => { ]), ) - jest - .spyOn(safeContracts, 'getMasterCopyAddressFromProxyAddress') - .mockImplementation(() => getSafeL2SingletonDeployment()?.networkAddresses['1']) - - const isValid = await isValidMasterCopy('1', '0x0000000000000000000000000000000000000001') + const isValid = await isValidMasterCopy('1', '0x3E5c63644E683549055b9Be8653de26E0B4CD36E') expect(isValid).toBe(true) }) }) From 3c9d5b877c9953a18435f8035a78b1f118946ab1 Mon Sep 17 00:00:00 2001 From: Usame Algan Date: Thu, 16 Jun 2022 17:10:25 +0200 Subject: [PATCH 15/18] chore: Test InvalidMasterCopyError component --- .../InvalidMasterCopyError.test.tsx | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/components/AppLayout/InvalidMasterCopyError/InvalidMasterCopyError.test.tsx diff --git a/src/components/AppLayout/InvalidMasterCopyError/InvalidMasterCopyError.test.tsx b/src/components/AppLayout/InvalidMasterCopyError/InvalidMasterCopyError.test.tsx new file mode 100644 index 0000000000..357058e996 --- /dev/null +++ b/src/components/AppLayout/InvalidMasterCopyError/InvalidMasterCopyError.test.tsx @@ -0,0 +1,45 @@ +import { InvalidMasterCopyError } from './' +import { render, waitFor } from 'src/utils/test-utils' +import * as safeVersion from 'src/logic/safe/utils/safeVersion' +import * as useAsync from 'src/logic/hooks/useAsync' + +describe('InvalidMasterCopyError', () => { + it('returns null if valid master copy', async () => { + const mockOnClose = jest.fn() + + jest.spyOn(safeVersion, 'isValidMasterCopy') + jest.spyOn(useAsync, 'default').mockImplementationOnce(() => [true, undefined, false]) + + const { container } = render() + + await waitFor(() => { + expect(container.firstChild).toBeNull() + }) + }) + + it('returns null if error', async () => { + const mockOnClose = jest.fn() + + jest.spyOn(safeVersion, 'isValidMasterCopy') + jest.spyOn(useAsync, 'default').mockImplementationOnce(() => [undefined, new Error(), false]) + + const { container } = render() + + await waitFor(() => { + expect(container.firstChild).toBeNull() + }) + }) + + it('displays an error message if not a valid master copy', async () => { + const mockOnClose = jest.fn() + + jest.spyOn(safeVersion, 'isValidMasterCopy') + jest.spyOn(useAsync, 'default').mockImplementationOnce(() => [false, undefined, false]) + + const { getByText } = render() + + await waitFor(() => { + expect(getByText(/This Safe was created with an unsupported base contract/)).toBeInTheDocument() + }) + }) +}) From 306373282e413ce979e9aa3afc0beccb743cd209 Mon Sep 17 00:00:00 2001 From: schmanu Date: Thu, 16 Jun 2022 17:11:58 +0200 Subject: [PATCH 16/18] fix: unit test for safeInformation --- src/logic/safe/store/actions/__tests__/utils.test.ts | 12 ++++++++++++ .../safe/store/actions/mocks/safeInformation.ts | 8 ++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/logic/safe/store/actions/__tests__/utils.test.ts b/src/logic/safe/store/actions/__tests__/utils.test.ts index 8666f2c0c7..a506d77f01 100644 --- a/src/logic/safe/store/actions/__tests__/utils.test.ts +++ b/src/logic/safe/store/actions/__tests__/utils.test.ts @@ -99,6 +99,12 @@ describe('extractRemoteSafeInfo', () => { 'SAFE_TX_GAS_OPTIONAL', 'SPENDING_LIMIT', ] as FEATURES[], + implementation: { + value: '0x3E5c63644E683549055b9Be8653de26E0B4CD36E', + name: 'Gnosis Safe: Mastercopy 1.3.0', + logoUri: + 'https://safe-transaction-assets.staging.gnosisdev.com/contracts/logos/0x3E5c63644E683549055b9Be8653de26E0B4CD36E.png', + }, } const remoteSafeInfo = await extractRemoteSafeInfo(remoteSafeInfoWithoutModules as any) @@ -131,6 +137,12 @@ describe('extractRemoteSafeInfo', () => { 'SAFE_TX_GAS_OPTIONAL', 'SPENDING_LIMIT', ] as FEATURES[], + implementation: { + value: '0x3E5c63644E683549055b9Be8653de26E0B4CD36E', + name: 'Gnosis Safe: Mastercopy 1.3.0', + logoUri: + 'https://safe-transaction-assets.staging.gnosisdev.com/contracts/logos/0x3E5c63644E683549055b9Be8653de26E0B4CD36E.png', + }, } const remoteSafeInfo = await extractRemoteSafeInfo(remoteSafeInfoWithModules as any) diff --git a/src/logic/safe/store/actions/mocks/safeInformation.ts b/src/logic/safe/store/actions/mocks/safeInformation.ts index f219869b19..0e900632c0 100644 --- a/src/logic/safe/store/actions/mocks/safeInformation.ts +++ b/src/logic/safe/store/actions/mocks/safeInformation.ts @@ -29,7 +29,7 @@ export const remoteSafeInfoWithModules = { implementation: { value: '0x3E5c63644E683549055b9Be8653de26E0B4CD36E', name: 'Gnosis Safe: Mastercopy 1.3.0', - logoUrl: + logoUri: 'https://safe-transaction-assets.staging.gnosisdev.com/contracts/logos/0x3E5c63644E683549055b9Be8653de26E0B4CD36E.png', }, guard: { @@ -43,7 +43,7 @@ export const remoteSafeInfoWithModules = { fallbackHandler: { value: '0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4', name: 'Gnosis Safe: Default Callback Handler 1.3.0', - logoUrl: + logoUri: 'https://safe-transaction-assets.staging.gnosisdev.com/contracts/logos/0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4.png', }, version: '1.3.0', @@ -79,14 +79,14 @@ export const remoteSafeInfoWithoutModules = { implementation: { value: '0x3E5c63644E683549055b9Be8653de26E0B4CD36E', name: 'Gnosis Safe: Mastercopy 1.3.0', - logoUrl: + logoUri: 'https://safe-transaction-assets.staging.gnosisdev.com/contracts/logos/0x3E5c63644E683549055b9Be8653de26E0B4CD36E.png', }, modules: [], fallbackHandler: { value: '0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4', name: 'Gnosis Safe: Default Callback Handler 1.3.0', - logoUrl: + logoUri: 'https://safe-transaction-assets.staging.gnosisdev.com/contracts/logos/0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4.png', }, version: '1.3.0', From 20c7abe0a0e5289a86144f213d9e9deb4ceaba03 Mon Sep 17 00:00:00 2001 From: Manuel Gellfart Date: Thu, 16 Jun 2022 17:36:02 +0200 Subject: [PATCH 17/18] change: noopener & noreferrer for CLI Link Co-authored-by: Aaron Cook --- src/components/AppLayout/InvalidMasterCopyError/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AppLayout/InvalidMasterCopyError/index.tsx b/src/components/AppLayout/InvalidMasterCopyError/index.tsx index 7887b6e9fd..1f30d5e375 100644 --- a/src/components/AppLayout/InvalidMasterCopyError/index.tsx +++ b/src/components/AppLayout/InvalidMasterCopyError/index.tsx @@ -32,7 +32,7 @@ export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): Re This Safe was created with an unsupported base contract. The web interface might not work correctly. We recommend using the{' '} - + command line interface {' '} instead. From 4f466ef4c98d8636d0ac750eb120c9619fa29e3f Mon Sep 17 00:00:00 2001 From: schmanu Date: Fri, 17 Jun 2022 11:15:18 +0200 Subject: [PATCH 18/18] fix: move close-state into InvalidMasterCopyError, new testcase, comment --- .../InvalidMasterCopyError.test.tsx | 31 +++++++++++++------ .../InvalidMasterCopyError/index.tsx | 10 ++++-- .../AppLayout/Sidebar/SafeHeader/index.tsx | 3 +- src/components/AppLayout/index.tsx | 3 +- src/logic/safe/store/reducer/safe.ts | 1 + 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/components/AppLayout/InvalidMasterCopyError/InvalidMasterCopyError.test.tsx b/src/components/AppLayout/InvalidMasterCopyError/InvalidMasterCopyError.test.tsx index 357058e996..0d92fc05a2 100644 --- a/src/components/AppLayout/InvalidMasterCopyError/InvalidMasterCopyError.test.tsx +++ b/src/components/AppLayout/InvalidMasterCopyError/InvalidMasterCopyError.test.tsx @@ -1,16 +1,14 @@ import { InvalidMasterCopyError } from './' -import { render, waitFor } from 'src/utils/test-utils' +import { render, waitFor, fireEvent } from 'src/utils/test-utils' import * as safeVersion from 'src/logic/safe/utils/safeVersion' import * as useAsync from 'src/logic/hooks/useAsync' describe('InvalidMasterCopyError', () => { it('returns null if valid master copy', async () => { - const mockOnClose = jest.fn() - jest.spyOn(safeVersion, 'isValidMasterCopy') jest.spyOn(useAsync, 'default').mockImplementationOnce(() => [true, undefined, false]) - const { container } = render() + const { container } = render() await waitFor(() => { expect(container.firstChild).toBeNull() @@ -18,12 +16,10 @@ describe('InvalidMasterCopyError', () => { }) it('returns null if error', async () => { - const mockOnClose = jest.fn() - jest.spyOn(safeVersion, 'isValidMasterCopy') jest.spyOn(useAsync, 'default').mockImplementationOnce(() => [undefined, new Error(), false]) - const { container } = render() + const { container } = render() await waitFor(() => { expect(container.firstChild).toBeNull() @@ -31,15 +27,30 @@ describe('InvalidMasterCopyError', () => { }) it('displays an error message if not a valid master copy', async () => { - const mockOnClose = jest.fn() - jest.spyOn(safeVersion, 'isValidMasterCopy') jest.spyOn(useAsync, 'default').mockImplementationOnce(() => [false, undefined, false]) - const { getByText } = render() + const { getByText } = render() await waitFor(() => { expect(getByText(/This Safe was created with an unsupported base contract/)).toBeInTheDocument() }) }) + + it('hides the error message on close', async () => { + jest.spyOn(safeVersion, 'isValidMasterCopy') + jest.spyOn(useAsync, 'default').mockImplementation(() => [false, undefined, false]) + + const { getByText, queryByText, getByRole } = render() + + await waitFor(() => { + expect(getByText(/This Safe was created with an unsupported base contract/)).toBeInTheDocument() + }) + + fireEvent.click(getByRole('button')) + + await waitFor(() => { + expect(queryByText(/This Safe was created with an unsupported base contract/)).not.toBeInTheDocument() + }) + }) }) diff --git a/src/components/AppLayout/InvalidMasterCopyError/index.tsx b/src/components/AppLayout/InvalidMasterCopyError/index.tsx index 1f30d5e375..92d1423225 100644 --- a/src/components/AppLayout/InvalidMasterCopyError/index.tsx +++ b/src/components/AppLayout/InvalidMasterCopyError/index.tsx @@ -6,12 +6,14 @@ import useAsync from 'src/logic/hooks/useAsync' import { currentSafe } from 'src/logic/safe/store/selectors' import { isValidMasterCopy } from 'src/logic/safe/utils/safeVersion' import MuiAlert from '@material-ui/lab/Alert' +import { useState } from 'react' const CLI_LINK = 'https://github.com/5afe/safe-cli' -export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): React.ReactElement | null => { +export const InvalidMasterCopyError = (): React.ReactElement | null => { const chainInfo = getChainInfo() const { implementation } = useSelector(currentSafe) + const [showMasterCopyError, setShowMasterCopyError] = useState(true) const [validMasterCopy, error] = useAsync(async () => { if (implementation.value) { @@ -19,6 +21,10 @@ export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): Re } }, [chainInfo.chainId, implementation.value]) + if (!showMasterCopyError) { + return null + } + if (error) { logError(Errors._620, error.message) return null @@ -29,7 +35,7 @@ export const InvalidMasterCopyError = ({ onClose }: { onClose: () => void }): Re } return ( - + setShowMasterCopyError(false)}> This Safe was created with an unsupported base contract. The web interface might not work correctly. We recommend using the{' '} diff --git a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx index 058d7d2b3e..ff231714f3 100644 --- a/src/components/AppLayout/Sidebar/SafeHeader/index.tsx +++ b/src/components/AppLayout/Sidebar/SafeHeader/index.tsx @@ -221,8 +221,6 @@ const SafeHeader = ({ onNewTransactionClick() } - const chainInfo = getChainInfo() - if (!address || !hasSafeOpen) { return ( @@ -236,6 +234,7 @@ const SafeHeader = ({ ) } + const chainInfo = getChainInfo() return ( <> diff --git a/src/components/AppLayout/index.tsx b/src/components/AppLayout/index.tsx index bab43deb52..625d8a090f 100644 --- a/src/components/AppLayout/index.tsx +++ b/src/components/AppLayout/index.tsx @@ -136,7 +136,6 @@ const Layout: React.FC = ({ }): React.ReactElement => { const [mobileNotSupportedClosed, setMobileNotSupportedClosed] = useState(false) const [expanded, setExpanded] = useState(false) - const [showMasterCopyError, setShowMasterCopyError] = useState(true) const { pathname } = useLocation() useDarkMode() @@ -160,7 +159,7 @@ const Layout: React.FC = ({

- {showMasterCopyError && setShowMasterCopyError(false)} />} + {showSideBar && ( diff --git a/src/logic/safe/store/reducer/safe.ts b/src/logic/safe/store/reducer/safe.ts index 3cf3304411..09e54e3894 100644 --- a/src/logic/safe/store/reducer/safe.ts +++ b/src/logic/safe/store/reducer/safe.ts @@ -52,6 +52,7 @@ const updateSafeProps = (prevSafe, safe) => { ? record.set(key, safe[key]) : record.update(key, (current) => current.merge(safe[key])) } else { + // TODO: temporary fix if type is AddressEx because it's neither a Map, nor has a size property record.set(key, safe[key]) } } else {