From c4571ae5141ecba24d49a2c859bafe7824984c54 Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Tue, 13 Oct 2020 09:32:29 -0300 Subject: [PATCH 1/9] Adds FEATURES enum Renames features to disabledFeatures --- src/config/index.ts | 8 ++++---- src/config/networks/network.d.ts | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/config/index.ts b/src/config/index.ts index 2560cb2af2..a8d630bc95 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -24,7 +24,7 @@ const getCurrentEnvironment = (): string => { type NetworkSpecificConfiguration = EnvironmentSettings & { network: NetworkSettings, - features?: SafeFeatures, + disabledFeatures?: SafeFeatures, } const configuration = (): NetworkSpecificConfiguration => { @@ -37,7 +37,7 @@ const configuration = (): NetworkSpecificConfiguration => { return { ...configFile.environment.production, network: configFile.network, - features: configFile.features, + disabledFeatures: configFile.disabledFeatures, } } @@ -49,7 +49,7 @@ const configuration = (): NetworkSpecificConfiguration => { return { ...networkBaseConfig, network: configFile.network, - features: configFile.features, + disabledFeatures: configFile.disabledFeatures, } } @@ -77,7 +77,7 @@ export const getNetworkExplorerInfo = (): { name: string; url: string; apiUrl: s apiUrl: getConfig()?.networkExplorerApiUrl, }) -export const getNetworkConfigFeatures = (): SafeFeatures | undefined => getConfig()?.features +export const getNetworkConfigDisabledFeatures = (): SafeFeatures => getConfig()?.disabledFeatures || [] export const getNetworkInfo = (): NetworkSettings => getConfig()?.network diff --git a/src/config/networks/network.d.ts b/src/config/networks/network.d.ts index 77345ea9e8..86e41ed433 100644 --- a/src/config/networks/network.d.ts +++ b/src/config/networks/network.d.ts @@ -1,4 +1,10 @@ // matches src/logic/tokens/store/model/token.ts `TokenProps` type + +export enum FEATURES { + ERC721 = 'ERC721', + ERC1155 = 'ERC1155', +} + type Token = { address: string name: string @@ -33,11 +39,7 @@ export type NetworkSettings = { // something around this to display or not some critical sections in the app, depending on the network support // I listed the ones that may conflict with the network. // If non is present, all the sections are available. -export type SafeFeatures = { - safeApps?: boolean, - collectibles?: boolean, - contractInteraction?: boolean -} +export type SafeFeatures = FEATURES[] type GasPrice = { gasPrice: number @@ -67,6 +69,6 @@ type SafeEnvironments = { export interface NetworkConfig { network: NetworkSettings - features?: SafeFeatures + disabledFeatures?: SafeFeatures environment: SafeEnvironments } From 678fd2905c4eb3bfc87cc72b86a951eb34bee85c Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Tue, 13 Oct 2020 09:33:06 -0300 Subject: [PATCH 2/9] Uses FEATURES enum instead of hardcoded contract features --- src/logic/safe/store/models/safe.ts | 3 ++- .../Balances/SendModal/screens/ChooseTxType/index.tsx | 3 ++- src/routes/safe/components/Balances/index.tsx | 7 ++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/logic/safe/store/models/safe.ts b/src/logic/safe/store/models/safe.ts index 0311d3be2c..bd38c747fe 100644 --- a/src/logic/safe/store/models/safe.ts +++ b/src/logic/safe/store/models/safe.ts @@ -1,4 +1,5 @@ import { List, Map, Record, RecordOf, Set } from 'immutable' +import { FEATURES } from 'src/config/networks/network.d' export type SafeOwner = { name: string @@ -24,7 +25,7 @@ export type SafeRecordProps = { recurringUser?: boolean currentVersion: string needsUpdate: boolean - featuresEnabled: Array + featuresEnabled: Array } const makeSafe = Record({ diff --git a/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx index 371dfb7a0f..80fe28cda4 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx @@ -17,6 +17,7 @@ import ContractInteractionIcon from 'src/routes/safe/components/Transactions/Txs import Collectible from '../assets/collectibles.svg' import Token from '../assets/token.svg' +import { FEATURES } from 'src/config/networks/network.d' type ActiveScreen = 'sendFunds' | 'sendCollectible' | 'contractInteraction' @@ -29,7 +30,7 @@ interface ChooseTxTypeProps { const ChooseTxType = ({ onClose, recipientAddress, setActiveScreen }: ChooseTxTypeProps): React.ReactElement => { const classes = useStyles() const featuresEnabled = useSelector(safeFeaturesEnabledSelector) - const erc721Enabled = featuresEnabled?.includes('ERC721') + const erc721Enabled = featuresEnabled?.includes(FEATURES.ERC721) const [disableContractInteraction, setDisableContractInteraction] = React.useState(!!recipientAddress) React.useEffect(() => { diff --git a/src/routes/safe/components/Balances/index.tsx b/src/routes/safe/components/Balances/index.tsx index c72c001919..49bd4d8b59 100644 --- a/src/routes/safe/components/Balances/index.tsx +++ b/src/routes/safe/components/Balances/index.tsx @@ -17,13 +17,14 @@ import SendModal from 'src/routes/safe/components/Balances/SendModal' import CurrencyDropdown from 'src/routes/safe/components/CurrencyDropdown' import { safeFeaturesEnabledSelector, - safeParamAddressFromStateSelector, safeNameSelector, + safeParamAddressFromStateSelector, } from 'src/logic/safe/store/selectors' import { wrapInSuspense } from 'src/utils/wrapInSuspense' import { useFetchTokens } from 'src/logic/safe/hooks/useFetchTokens' -import { Route, Switch, NavLink, Redirect } from 'react-router-dom' +import { NavLink, Redirect, Route, Switch } from 'react-router-dom' +import { FEATURES } from 'src/config/networks/network.d' const Collectibles = React.lazy(() => import('src/routes/safe/components/Balances/Collectibles')) const Coins = React.lazy(() => import('src/routes/safe/components/Balances/Coins')) @@ -58,7 +59,7 @@ const Balances = (): React.ReactElement => { useFetchTokens(address as string) useEffect(() => { - const erc721Enabled = Boolean(featuresEnabled?.includes('ERC721')) + const erc721Enabled = Boolean(featuresEnabled?.includes(FEATURES.ERC721)) setState((prevState) => ({ ...prevState, From f69439ddfb6d9b83b2b30f7e3416b29602dd2eca Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Tue, 13 Oct 2020 09:50:56 -0300 Subject: [PATCH 3/9] Refactor enabledFeatures function, now checks that there are not disabled features by config before return the enabledFeatures result --- src/logic/safe/store/actions/fetchSafe.ts | 4 ++++ src/logic/safe/utils/safeVersion.ts | 18 +++++++++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/logic/safe/store/actions/fetchSafe.ts b/src/logic/safe/store/actions/fetchSafe.ts index a398ffb733..e74bd1e355 100644 --- a/src/logic/safe/store/actions/fetchSafe.ts +++ b/src/logic/safe/store/actions/fetchSafe.ts @@ -123,9 +123,13 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: Dispatch modules: buildModulesLinkedList(modules?.array, modules?.next), nonce: Number(remoteNonce), threshold: Number(remoteThreshold), + featuresEnabled: localSafe?.currentVersion + ? enabledFeatures(localSafe?.currentVersion) + : localSafe?.featuresEnabled, }), ) + if (!remoteOwners) return // If the remote owners does not contain a local address, we remove that local owner localOwners.forEach((localAddress) => { const remoteOwnerIndex = remoteOwners.findIndex((remoteAddress) => sameAddress(remoteAddress, localAddress)) diff --git a/src/logic/safe/utils/safeVersion.ts b/src/logic/safe/utils/safeVersion.ts index de6fc60b03..9d6f32fa69 100644 --- a/src/logic/safe/utils/safeVersion.ts +++ b/src/logic/safe/utils/safeVersion.ts @@ -5,13 +5,15 @@ import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d' import { getGnosisSafeInstanceAt, getSafeMasterContract } from 'src/logic/contracts/safeContracts' import { LATEST_SAFE_VERSION } from 'src/utils/constants' +import { getNetworkConfigDisabledFeatures } from 'src/config' +import { FEATURES } from 'src/config/networks/network.d' -export const FEATURES = [ - { name: 'ERC721', validVersion: '>=1.1.1' }, - { name: 'ERC1155', validVersion: '>=1.1.1' }, +const FEATURES_BY_VERSION = [ + { name: FEATURES.ERC721, validVersion: '>=1.1.1' }, + { name: FEATURES.ERC1155, validVersion: '>=1.1.1' }, ] -type Feature = typeof FEATURES[number] +type Feature = typeof FEATURES_BY_VERSION[number] export const safeNeedsUpdate = (currentVersion?: string, latestVersion?: string): boolean => { if (!currentVersion || !latestVersion) { @@ -27,13 +29,15 @@ export const safeNeedsUpdate = (currentVersion?: string, latestVersion?: string) export const getCurrentSafeVersion = (gnosisSafeInstance: GnosisSafe): Promise => gnosisSafeInstance.methods.VERSION().call() -export const enabledFeatures = (version: string): string[] => - FEATURES.reduce((acc: string[], feature: Feature) => { - if (semverSatisfies(version, feature.validVersion)) { +export const enabledFeatures = (version: string): FEATURES[] => { + const disabledFeatures = getNetworkConfigDisabledFeatures() + return FEATURES_BY_VERSION.reduce((acc: FEATURES[], feature: Feature) => { + if (semverSatisfies(version, feature.validVersion) && !disabledFeatures.includes(feature.name)) { acc.push(feature.name) } return acc }, []) +} interface SafeVersionInfo { current: string From fccbe43dc361e1ae3dce74dbe018dda4fe17a58f Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Tue, 13 Oct 2020 10:06:00 -0300 Subject: [PATCH 4/9] Adds SAFE_APPS and CONTRACT_INTERACTION to FEATURES enum --- src/config/networks/network.d.ts | 2 ++ src/logic/safe/utils/safeVersion.ts | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/config/networks/network.d.ts b/src/config/networks/network.d.ts index 86e41ed433..ed01359698 100644 --- a/src/config/networks/network.d.ts +++ b/src/config/networks/network.d.ts @@ -3,6 +3,8 @@ export enum FEATURES { ERC721 = 'ERC721', ERC1155 = 'ERC1155', + SAFE_APPS = 'SAFE_APPS', + CONTRACT_INTERACTION = 'CONTRACT_INTERACTION' } type Token = { diff --git a/src/logic/safe/utils/safeVersion.ts b/src/logic/safe/utils/safeVersion.ts index 9d6f32fa69..2c4b91d36a 100644 --- a/src/logic/safe/utils/safeVersion.ts +++ b/src/logic/safe/utils/safeVersion.ts @@ -8,9 +8,16 @@ import { LATEST_SAFE_VERSION } from 'src/utils/constants' import { getNetworkConfigDisabledFeatures } from 'src/config' import { FEATURES } from 'src/config/networks/network.d' -const FEATURES_BY_VERSION = [ +type FeatureConfigByVersion = { + name: FEATURES + validVersion?: string +} + +const FEATURES_BY_VERSION: FeatureConfigByVersion[] = [ { name: FEATURES.ERC721, validVersion: '>=1.1.1' }, { name: FEATURES.ERC1155, validVersion: '>=1.1.1' }, + { name: FEATURES.SAFE_APPS }, + { name: FEATURES.CONTRACT_INTERACTION }, ] type Feature = typeof FEATURES_BY_VERSION[number] @@ -29,10 +36,14 @@ export const safeNeedsUpdate = (currentVersion?: string, latestVersion?: string) export const getCurrentSafeVersion = (gnosisSafeInstance: GnosisSafe): Promise => gnosisSafeInstance.methods.VERSION().call() +const checkFeatureEnabledByVersion = (featureConfig: FeatureConfigByVersion, version: string) => { + return featureConfig.validVersion ? semverSatisfies(version, featureConfig.validVersion) : true +} + export const enabledFeatures = (version: string): FEATURES[] => { const disabledFeatures = getNetworkConfigDisabledFeatures() return FEATURES_BY_VERSION.reduce((acc: FEATURES[], feature: Feature) => { - if (semverSatisfies(version, feature.validVersion) && !disabledFeatures.includes(feature.name)) { + if (!disabledFeatures.includes(feature.name) && checkFeatureEnabledByVersion(feature, version)) { acc.push(feature.name) } return acc From ed3152178e153f985e254cfdc44f5e0c93ae0090 Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Tue, 13 Oct 2020 10:23:28 -0300 Subject: [PATCH 5/9] Remove SAFE_APPS from sidebar if disabled --- .../AppLayout/Sidebar/useSidebarItems.tsx | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/components/AppLayout/Sidebar/useSidebarItems.tsx b/src/components/AppLayout/Sidebar/useSidebarItems.tsx index 58439e79a4..28f17c3bd3 100644 --- a/src/components/AppLayout/Sidebar/useSidebarItems.tsx +++ b/src/components/AppLayout/Sidebar/useSidebarItems.tsx @@ -4,8 +4,13 @@ import { useRouteMatch } from 'react-router-dom' import { ListItemType } from 'src/components/List' import ListIcon from 'src/components/List/ListIcon' import { SAFELIST_ADDRESS } from 'src/routes/routes' +import { FEATURES } from 'src/config/networks/network.d' +import { useSelector } from 'react-redux' +import { safeFeaturesEnabledSelector } from 'src/logic/safe/store/selectors' const useSidebarItems = (): ListItemType[] => { + const featuresEnabled = useSelector(safeFeaturesEnabledSelector) + const safeAppsEnabled = Boolean(featuresEnabled?.includes(FEATURES.SAFE_APPS)) const matchSafe = useRouteMatch({ path: `${SAFELIST_ADDRESS}`, strict: false }) const matchSafeWithAddress = useRouteMatch<{ safeAddress: string }>({ path: `${SAFELIST_ADDRESS}/:safeAddress` }) const matchSafeWithAction = useRouteMatch({ path: `${SAFELIST_ADDRESS}/:safeAddress/:safeAction` }) as { @@ -13,11 +18,35 @@ const useSidebarItems = (): ListItemType[] => { params: Record } - const sidebarItems = useMemo((): ListItemType[] => { + return useMemo((): ListItemType[] => { if (!matchSafe || !matchSafeWithAddress) { return [] } + const safeSidebar = safeAppsEnabled + ? [ + { + label: 'Apps', + icon: , + selected: matchSafeWithAction?.params.safeAction === 'apps', + href: `${matchSafeWithAddress?.url}/apps`, + }, + { + label: 'Settings', + icon: , + selected: matchSafeWithAction?.params.safeAction === 'settings', + href: `${matchSafeWithAddress?.url}/settings`, + }, + ] + : [ + { + label: 'Settings', + icon: , + selected: matchSafeWithAction?.params.safeAction === 'settings', + href: `${matchSafeWithAddress?.url}/settings`, + }, + ] + return [ { label: 'ASSETS', @@ -37,22 +66,9 @@ const useSidebarItems = (): ListItemType[] => { selected: matchSafeWithAction?.params.safeAction === 'address-book', href: `${matchSafeWithAddress?.url}/address-book`, }, - { - label: 'Apps', - icon: , - selected: matchSafeWithAction?.params.safeAction === 'apps', - href: `${matchSafeWithAddress?.url}/apps`, - }, - { - label: 'Settings', - icon: , - selected: matchSafeWithAction?.params.safeAction === 'settings', - href: `${matchSafeWithAddress?.url}/settings`, - }, + ...safeSidebar, ] - }, [matchSafe, matchSafeWithAction, matchSafeWithAddress]) - - return sidebarItems + }, [matchSafe, matchSafeWithAction, matchSafeWithAddress, safeAppsEnabled]) } export { useSidebarItems } From 5099c0477a5b3bd0f438648239d286ddb2d77791 Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Tue, 13 Oct 2020 10:37:38 -0300 Subject: [PATCH 6/9] Redirect user from /apps if safe apps are disabled --- src/routes/safe/container/index.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/routes/safe/container/index.tsx b/src/routes/safe/container/index.tsx index 2ca98cbf4b..2b3cbe6797 100644 --- a/src/routes/safe/container/index.tsx +++ b/src/routes/safe/container/index.tsx @@ -5,9 +5,10 @@ import { GenericModal } from '@gnosis.pm/safe-react-components' import NoSafe from 'src/components/NoSafe' import { providerNameSelector } from 'src/logic/wallets/store/selectors' -import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' +import { safeFeaturesEnabledSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { wrapInSuspense } from 'src/utils/wrapInSuspense' import { SAFELIST_ADDRESS } from 'src/routes/routes' +import { FEATURES } from 'src/config/networks/network.d' export const BALANCES_TAB_BTN_TEST_ID = 'balances-tab-btn' export const SETTINGS_TAB_BTN_TEST_ID = 'settings-tab-btn' @@ -34,7 +35,9 @@ const Container = (): React.ReactElement => { const safeAddress = useSelector(safeParamAddressFromStateSelector) const provider = useSelector(providerNameSelector) + const featuresEnabled = useSelector(safeFeaturesEnabledSelector) const matchSafeWithAddress = useRouteMatch<{ safeAddress: string }>({ path: `${SAFELIST_ADDRESS}/:safeAddress` }) + const safeAppsEnabled = Boolean(featuresEnabled?.includes(FEATURES.SAFE_APPS)) if (!safeAddress) { return @@ -67,7 +70,17 @@ const Container = (): React.ReactElement => { path={`${matchSafeWithAddress?.path}/transactions`} render={() => wrapInSuspense(, null)} /> - wrapInSuspense(, null)} /> + { + if (!safeAppsEnabled) { + history.push(`${matchSafeWithAddress?.url}/balances`) + } + return wrapInSuspense(, null) + }} + /> + Date: Tue, 13 Oct 2020 10:46:10 -0300 Subject: [PATCH 7/9] Disables contract interaction from config --- .../SendModal/screens/ChooseTxType/index.tsx | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx index 80fe28cda4..ebbdc9fe19 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/ChooseTxType/index.tsx @@ -31,6 +31,7 @@ const ChooseTxType = ({ onClose, recipientAddress, setActiveScreen }: ChooseTxTy const classes = useStyles() const featuresEnabled = useSelector(safeFeaturesEnabledSelector) const erc721Enabled = featuresEnabled?.includes(FEATURES.ERC721) + const contractInteractionEnabled = featuresEnabled?.includes(FEATURES.CONTRACT_INTERACTION) const [disableContractInteraction, setDisableContractInteraction] = React.useState(!!recipientAddress) React.useEffect(() => { @@ -100,22 +101,24 @@ const ChooseTxType = ({ onClose, recipientAddress, setActiveScreen }: ChooseTxTy Send collectible )} - + {contractInteractionEnabled && ( + + )} From 9913353eab092d224a12c40acc914780cf32a4cb Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Tue, 13 Oct 2020 12:12:24 -0300 Subject: [PATCH 8/9] Remove !remoteOwners --- src/logic/safe/store/actions/fetchSafe.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/logic/safe/store/actions/fetchSafe.ts b/src/logic/safe/store/actions/fetchSafe.ts index e74bd1e355..e688ce222b 100644 --- a/src/logic/safe/store/actions/fetchSafe.ts +++ b/src/logic/safe/store/actions/fetchSafe.ts @@ -129,7 +129,6 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: Dispatch }), ) - if (!remoteOwners) return // If the remote owners does not contain a local address, we remove that local owner localOwners.forEach((localAddress) => { const remoteOwnerIndex = remoteOwners.findIndex((remoteAddress) => sameAddress(remoteAddress, localAddress)) From 9ba23c4ffc2ea0a6455c2017435d871aa5aa0e77 Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Tue, 13 Oct 2020 12:14:43 -0300 Subject: [PATCH 9/9] Move settings to a constant --- .../AppLayout/Sidebar/useSidebarItems.tsx | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/components/AppLayout/Sidebar/useSidebarItems.tsx b/src/components/AppLayout/Sidebar/useSidebarItems.tsx index 28f17c3bd3..4cf8e76bbb 100644 --- a/src/components/AppLayout/Sidebar/useSidebarItems.tsx +++ b/src/components/AppLayout/Sidebar/useSidebarItems.tsx @@ -23,6 +23,13 @@ const useSidebarItems = (): ListItemType[] => { return [] } + const settingsItem = { + label: 'Settings', + icon: , + selected: matchSafeWithAction?.params.safeAction === 'settings', + href: `${matchSafeWithAddress?.url}/settings`, + } + const safeSidebar = safeAppsEnabled ? [ { @@ -31,21 +38,9 @@ const useSidebarItems = (): ListItemType[] => { selected: matchSafeWithAction?.params.safeAction === 'apps', href: `${matchSafeWithAddress?.url}/apps`, }, - { - label: 'Settings', - icon: , - selected: matchSafeWithAction?.params.safeAction === 'settings', - href: `${matchSafeWithAddress?.url}/settings`, - }, - ] - : [ - { - label: 'Settings', - icon: , - selected: matchSafeWithAction?.params.safeAction === 'settings', - href: `${matchSafeWithAddress?.url}/settings`, - }, + settingsItem, ] + : [settingsItem] return [ {