Skip to content

Commit

Permalink
[Multichain] fix: Zksync network selector [SW-152] (#4185)
Browse files Browse the repository at this point in the history
* fix: Show zksync network but disabled

* fix: Show message if zksync safe is open

* fix: Adjust tests for useReplayableNetworks
  • Loading branch information
usame-algan committed Sep 17, 2024
1 parent 8680d6b commit 1db148e
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 86 deletions.
54 changes: 27 additions & 27 deletions src/components/common/NetworkSelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,32 @@ export const getNetworkLink = (router: NextRouter, safeAddress: string, networkS
}

const UndeployedNetworkMenuItem = ({
chainId,
chainConfigs,
chain,
isSelected = false,
onSelect,
}: {
chainId: string
chainConfigs: ChainInfo[]
chain: ChainInfo & { available: boolean }
isSelected?: boolean
onSelect: (chain: ChainInfo) => void
}) => {
const chain = useMemo(() => chainConfigs.find((chain) => chain.chainId === chainId), [chainConfigs, chainId])

if (!chain) return null
const isDisabled = !chain.available

return (
<MenuItem value={chainId} sx={{ '&:hover': { backgroundColor: 'inherit' } }} onClick={() => onSelect(chain)}>
<MenuItem
value={chain.chainId}
sx={{ '&:hover': { backgroundColor: 'inherit' } }}
onClick={() => onSelect(chain)}
disabled={isDisabled}
>
<Box className={css.item}>
<ChainIndicator responsive={isSelected} chainId={chain.chainId} inline />
<PlusIcon className={css.plusIcon} />
{isDisabled ? (
<Typography variant="caption" component="span" className={css.comingSoon}>
Not available
</Typography>
) : (
<PlusIcon className={css.plusIcon} />
)}
</Box>
</MenuItem>
)
Expand Down Expand Up @@ -144,6 +151,8 @@ const UndeployedNetworks = ({
[availableNetworks],
)

const noAvailableNetworks = useMemo(() => availableNetworks.every((config) => !config.available), [availableNetworks])

const onSelect = (chain: ChainInfo) => {
setReplayOnChain(chain)
}
Expand All @@ -156,15 +165,16 @@ const UndeployedNetworks = ({
)
}

const errorMessage = safeCreationDataError
? 'Adding another network is not possible for this Safe.'
: isUnsupportedSafeCreationVersion
? 'This account was created from an outdated mastercopy. Adding another network is not possible.'
: ''
const errorMessage =
safeCreationDataError || (safeCreationData && noAvailableNetworks)
? 'Adding another network is not possible for this Safe.'
: isUnsupportedSafeCreationVersion
? 'This account was created from an outdated mastercopy. Adding another network is not possible.'
: ''

if (errorMessage) {
return (
<Box p="0px 16px">
<Box px={2} py={1}>
<Typography color="text.secondary" fontSize="14px" maxWidth={300}>
{errorMessage}
</Typography>
Expand Down Expand Up @@ -196,21 +206,11 @@ const UndeployedNetworks = ({
) : (
<>
{prodNets.map((chain) => (
<UndeployedNetworkMenuItem
chainConfigs={chains}
chainId={chain.chainId}
onSelect={onSelect}
key={chain.chainId}
/>
<UndeployedNetworkMenuItem chain={chain} onSelect={onSelect} key={chain.chainId} />
))}
{testNets.length > 0 && <TestnetDivider />}
{testNets.map((chain) => (
<UndeployedNetworkMenuItem
chainConfigs={chains}
chainId={chain.chainId}
onSelect={onSelect}
key={chain.chainId}
/>
<UndeployedNetworkMenuItem chain={chain} onSelect={onSelect} key={chain.chainId} />
))}
</>
)}
Expand Down
8 changes: 8 additions & 0 deletions src/components/common/NetworkSelector/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
.item {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-1);
width: 100%;
}
Expand All @@ -83,3 +84,10 @@
padding: var(--space-2) 0;
margin: 2px;
}

.comingSoon {
background-color: var(--color-border-light);
border-radius: 4px;
color: var(--color-text-primary);
padding: 4px 8px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const PROXY_FACTORY_111_DEPLOYMENTS = getProxyFactoryDeployments({ version: '1.1
const PROXY_FACTORY_130_DEPLOYMENTS = getProxyFactoryDeployments({ version: '1.3.0' })?.deployments
const PROXY_FACTORY_141_DEPLOYMENTS = getProxyFactoryDeployments({ version: '1.4.1' })?.deployments

describe('useReplayableNetworks', () => {
describe('useCompatibleNetworks', () => {
beforeAll(() => {
jest.spyOn(useChains, 'default').mockReturnValue({
configs: [
Expand Down Expand Up @@ -77,7 +77,7 @@ describe('useReplayableNetworks', () => {
expect(result.current).toHaveLength(0)
})

it('should return empty list for unknown masterCopies', () => {
it('should set available to false for unknown masterCopies', () => {
const callData = {
owners: [faker.finance.ethereumAddress()],
threshold: 1,
Expand Down Expand Up @@ -107,43 +107,10 @@ describe('useReplayableNetworks', () => {
setupData,
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
expect(result.current).toHaveLength(0)
})

it('should return empty list for unknown masterCopies', () => {
const callData = {
owners: [faker.finance.ethereumAddress()],
threshold: 1,
to: ZERO_ADDRESS,
data: EMPTY_DATA,
fallbackHandler: faker.finance.ethereumAddress(),
paymentToken: ZERO_ADDRESS,
payment: 0,
paymentReceiver: ECOSYSTEM_ID_ADDRESS,
}

const setupData = safeInterface.encodeFunctionData('setup', [
callData.owners,
callData.threshold,
callData.to,
callData.data,
callData.fallbackHandler,
callData.paymentToken,
callData.payment,
callData.paymentReceiver,
])

const creationData: ReplayedSafeProps = {
factoryAddress: faker.finance.ethereumAddress(),
masterCopy: faker.finance.ethereumAddress(),
saltNonce: '0',
setupData,
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
expect(result.current).toHaveLength(0)
expect(result.current.every((config) => config.available)).toEqual(false)
})

it('should return everything but zkSync for 1.4.1 Safes', () => {
it('should set everything to available except zkSync for 1.4.1 Safes', () => {
const callData = {
owners: [faker.finance.ethereumAddress()],
threshold: 1,
Expand Down Expand Up @@ -172,8 +139,9 @@ describe('useReplayableNetworks', () => {
setupData,
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
expect(result.current).toHaveLength(4)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '480'])
expect(result.current).toHaveLength(5)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, true])
}

{
Expand All @@ -184,12 +152,13 @@ describe('useReplayableNetworks', () => {
setupData,
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
expect(result.current).toHaveLength(4)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '480'])
expect(result.current).toHaveLength(5)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, true])
}
})

it('should return correct chains for 1.3.0 Safes', () => {
it('should mark already deployed chains as not available', () => {
const callData = {
owners: [faker.finance.ethereumAddress()],
threshold: 1,
Expand Down Expand Up @@ -221,8 +190,9 @@ describe('useReplayableNetworks', () => {
setupData,
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
expect(result.current).toHaveLength(4)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '480'])
expect(result.current).toHaveLength(5)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, true])
}

// 1.3.0, L2 and canonical
Expand All @@ -234,8 +204,9 @@ describe('useReplayableNetworks', () => {
setupData,
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
expect(result.current).toHaveLength(4)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '480'])
expect(result.current).toHaveLength(5)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, true])
}

// 1.3.0, L1 and EIP155 is not available on Worldchain
Expand All @@ -247,8 +218,9 @@ describe('useReplayableNetworks', () => {
setupData,
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
expect(result.current).toHaveLength(3)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100'])
expect(result.current).toHaveLength(5)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, false])
}

// 1.3.0, L2 and EIP155
Expand All @@ -260,12 +232,13 @@ describe('useReplayableNetworks', () => {
setupData,
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
expect(result.current).toHaveLength(3)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100'])
expect(result.current).toHaveLength(5)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, false])
}
})

it('should return empty list for 1.1.1 Safes', () => {
it('should set everything to not available for 1.1.1 Safes', () => {
const callData = {
owners: [faker.finance.ethereumAddress()],
threshold: 1,
Expand Down Expand Up @@ -295,6 +268,8 @@ describe('useReplayableNetworks', () => {
setupData,
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
expect(result.current).toHaveLength(0)
expect(result.current).toHaveLength(5)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480'])
expect(result.current.map((chain) => chain.available)).toEqual([false, false, false, false, false])
})
})
20 changes: 13 additions & 7 deletions src/features/multichain/hooks/useCompatibleNetworks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getSafeL2SingletonDeployments,
getSafeSingletonDeployments,
} from '@safe-global/safe-deployments'
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'

const SUPPORTED_VERSIONS: SafeVersion[] = ['1.4.1', '1.3.0']

Expand All @@ -25,7 +26,9 @@ const hasDeployment = (chainId: string, contractAddress: string, deployments: Si
* Returns all chains where the creations's masterCopy and factory are deployed.
* @param creation
*/
export const useCompatibleNetworks = (creation: ReplayedSafeProps | undefined) => {
export const useCompatibleNetworks = (
creation: ReplayedSafeProps | undefined,
): (ChainInfo & { available: boolean })[] => {
const { configs } = useChains()

if (!creation) {
Expand All @@ -50,10 +53,13 @@ export const useCompatibleNetworks = (creation: ReplayedSafeProps | undefined) =
getProxyFactoryDeployments({ version }),
).filter(Boolean) as SingletonDeploymentV2[]

return configs.filter(
(config) =>
(hasDeployment(config.chainId, masterCopy, allL1SingletonDeployments) ||
hasDeployment(config.chainId, masterCopy, allL2SingletonDeployments)) &&
hasDeployment(config.chainId, factoryAddress, allProxyFactoryDeployments),
)
return configs.map((config) => {
return {
...config,
available:
(hasDeployment(config.chainId, masterCopy, allL1SingletonDeployments) ||
hasDeployment(config.chainId, masterCopy, allL2SingletonDeployments)) &&
hasDeployment(config.chainId, factoryAddress, allProxyFactoryDeployments),
}
})
}

0 comments on commit 1db148e

Please sign in to comment.