Skip to content

Commit

Permalink
fix: check deployments of migration contracts, upgrade safe-deploymen…
Browse files Browse the repository at this point in the history
  • Loading branch information
schmanu authored Oct 23, 2024
1 parent 845aaf8 commit 9293eb1
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 101 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"@safe-global/api-kit": "^2.4.6",
"@safe-global/protocol-kit": "^4.1.1",
"@safe-global/safe-apps-sdk": "^9.1.0",
"@safe-global/safe-deployments": "^1.37.10",
"@safe-global/safe-deployments": "1.37.12",
"@safe-global/safe-client-gateway-sdk": "1.58.0-next-25bba61",
"@safe-global/safe-gateway-typescript-sdk": "3.22.3-beta.15",
"@safe-global/safe-modules-deployments": "^2.2.1",
Expand Down
45 changes: 28 additions & 17 deletions src/components/common/NetworkSelector/NetworkMultiSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Controller, useFormContext, useWatch } from 'react-hook-form'
import { useRouter } from 'next/router'
import { getNetworkLink } from '.'
import { SetNameStepFields } from '@/components/new-safe/create/steps/SetNameStep'
import { getSafeSingletonDeployments } from '@safe-global/safe-deployments'
import { getSafeSingletonDeployments, getSafeToL2SetupDeployments } from '@safe-global/safe-deployments'
import { getLatestSafeVersion } from '@/utils/chains'
import { hasCanonicalDeployment } from '@/services/contracts/deployments'
import { hasMultiChainCreationFeatures } from '@/features/multichain/utils/utils'
Expand Down Expand Up @@ -74,22 +74,33 @@ const NetworkMultiSelector = ({
}

// Check if required deployments are available
const optionHasCanonicalSingletonDeployment = hasCanonicalDeployment(
getSafeSingletonDeployments({
network: optionNetwork.chainId,
version: getLatestSafeVersion(firstSelectedNetwork),
}),
optionNetwork.chainId,
)
const selectedHasCanonicalSingletonDeployment = hasCanonicalDeployment(
getSafeSingletonDeployments({
network: firstSelectedNetwork.chainId,
version: getLatestSafeVersion(firstSelectedNetwork),
}),
firstSelectedNetwork.chainId,
)
const optionHasCanonicalSingletonDeployment =
hasCanonicalDeployment(
getSafeSingletonDeployments({
network: optionNetwork.chainId,
version: getLatestSafeVersion(firstSelectedNetwork),
}),
optionNetwork.chainId,
) &&
hasCanonicalDeployment(
getSafeToL2SetupDeployments({ network: optionNetwork.chainId, version: '1.4.1' }),
optionNetwork.chainId,
)

// Only 1.4.1 safes with canonical deployment addresses can be deployed as part of a multichain group
const selectedHasCanonicalSingletonDeployment =
hasCanonicalDeployment(
getSafeSingletonDeployments({
network: firstSelectedNetwork.chainId,
version: getLatestSafeVersion(firstSelectedNetwork),
}),
firstSelectedNetwork.chainId,
) &&
hasCanonicalDeployment(
getSafeToL2SetupDeployments({ network: firstSelectedNetwork.chainId, version: '1.4.1' }),
firstSelectedNetwork.chainId,
)

// Only 1.4.1 safes with canonical deployment addresses and SafeToL2Setup can be deployed as part of a multichain group
if (!selectedHasCanonicalSingletonDeployment) return !optionIsSelectedNetwork
return !optionHasCanonicalSingletonDeployment
},
Expand Down Expand Up @@ -128,7 +139,7 @@ const NetworkMultiSelector = ({
))
}
renderOption={(props, chain, { selected }) => (
<li key={chain.chainId} {...props}>
<li {...props} key={chain.chainId}>
<Checkbox data-testid="network-checkbox" size="small" checked={selected} />
<ChainIndicator chainId={chain.chainId} inline />
</li>
Expand Down
5 changes: 3 additions & 2 deletions src/components/new-safe/create/logic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,9 @@ export const createNewUndeployedSafeWithoutSalt = (
const safeToL2SetupAddress = safeToL2SetupDeployment?.networkAddresses[chain.chainId]
const safeToL2SetupInterface = Safe_to_l2_setup__factory.createInterface()

// Only do migration if the chain supports multiChain deployments.
const includeMigration = hasMultiChainCreationFeatures(chain) && semverSatisfies(safeVersion, '>=1.4.1')
// Only do migration if the chain supports multiChain deployments and has a SafeToL2Setup deployment
const includeMigration =
hasMultiChainCreationFeatures(chain) && semverSatisfies(safeVersion, '>=1.4.1') && Boolean(safeToL2SetupAddress)

const masterCopy = includeMigration ? safeL1Address : chain.l2 ? safeL2Address : safeL1Address

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ErrorMessage from '@/components/tx/ErrorMessage'
import { useSafeSDK } from '@/hooks/coreSDK/safeCoreSDK'
import { MigrateToL2Information } from '@/components/tx/SignOrExecuteForm/MigrateToL2Information'
import { Box } from '@mui/material'
import { isMultisigDetailedExecutionInfo } from '@/utils/transaction-guards'

export const MigrationToL2TxData = ({ txDetails }: { txDetails: TransactionDetails }) => {
const readOnlyProvider = useWeb3ReadOnly()
Expand Down Expand Up @@ -47,19 +48,24 @@ export const MigrationToL2TxData = ({ txDetails }: { txDetails: TransactionDetai
if (!execTxArgs || execTxArgs.length < 10) {
return undefined
}
return createTx({
to: execTxArgs[0],
value: execTxArgs[1].toString(),
data: execTxArgs[2],
operation: Number(execTxArgs[3]),
safeTxGas: execTxArgs[4].toString(),
baseGas: execTxArgs[5].toString(),
gasPrice: execTxArgs[6].toString(),
gasToken: execTxArgs[7].toString(),
refundReceiver: execTxArgs[8],
})
return createTx(
{
to: execTxArgs[0],
value: execTxArgs[1].toString(),
data: execTxArgs[2],
operation: Number(execTxArgs[3]),
safeTxGas: execTxArgs[4].toString(),
baseGas: execTxArgs[5].toString(),
gasPrice: execTxArgs[6].toString(),
gasToken: execTxArgs[7].toString(),
refundReceiver: execTxArgs[8],
},
isMultisigDetailedExecutionInfo(txDetails.detailedExecutionInfo)
? txDetails.detailedExecutionInfo.nonce
: undefined,
)
}
}, [readOnlyProvider, txDetails.txHash, chain, safe.version, sdk])
}, [txDetails.txHash, txDetails.detailedExecutionInfo, chain, sdk, readOnlyProvider, safe.version])

const [decodedRealTx, decodedRealTxError] = useDecodeTx(realSafeTx)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ describe('useCompatibleNetworks', () => {
beforeAll(() => {
jest.spyOn(useChains, 'default').mockReturnValue({
configs: [
chainBuilder().with({ chainId: '1' }).build(),
chainBuilder().with({ chainId: '10' }).build(), // This has the eip155 and then the canonical addresses
chainBuilder().with({ chainId: '100' }).build(), // This has the canonical and then the eip155 addresses
chainBuilder().with({ chainId: '324' }).build(), // ZkSync has different addresses for all versions
chainBuilder().with({ chainId: '480' }).build(), // Worldchain has 1.4.1 but not 1.1.1
chainBuilder().with({ chainId: '1', l2: false }).build(),
chainBuilder().with({ chainId: '10', l2: true }).build(), // This has the eip155 and then the canonical addresses
chainBuilder().with({ chainId: '100', l2: true }).build(), // This has the canonical and then the eip155 addresses
chainBuilder().with({ chainId: '324', l2: true }).build(), // ZkSync has different addresses for all versions
chainBuilder().with({ chainId: '480', l2: true }).build(), // Worldchain has 1.4.1 but not 1.1.1
chainBuilder().with({ chainId: '10200', l2: true }).build(), // Gnosis Chiado has no migration contracts
],
})
})
Expand Down Expand Up @@ -68,7 +69,7 @@ describe('useCompatibleNetworks', () => {
expect(result.current.every((config) => config.available)).toEqual(false)
})

it('should set everything to available except zkSync for 1.4.1 Safes', () => {
it('should set everything to available except zkSync and GnosisChain Chiado for 1.4.1 Safes', () => {
const callData = {
owners: [faker.finance.ethereumAddress()],
threshold: 1,
Expand All @@ -88,9 +89,9 @@ describe('useCompatibleNetworks', () => {
safeVersion: '1.4.1',
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
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])
expect(result.current).toHaveLength(6)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480', '10200'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, true, false])
}

{
Expand All @@ -102,9 +103,9 @@ describe('useCompatibleNetworks', () => {
safeVersion: '1.4.1',
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
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])
expect(result.current).toHaveLength(6)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480', '10200'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, true, false])
}
})

Expand All @@ -120,7 +121,7 @@ describe('useCompatibleNetworks', () => {
paymentReceiver: ECOSYSTEM_ID_ADDRESS,
}

// 1.3.0, L1 and canonical
// 1.3.0, L1 and canonical, not available on Chiado as no migration exists
{
const creationData: ReplayedSafeProps = {
factoryAddress: PROXY_FACTORY_130_DEPLOYMENTS?.canonical?.address!,
Expand All @@ -130,9 +131,9 @@ describe('useCompatibleNetworks', () => {
safeVersion: '1.3.0',
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
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])
expect(result.current).toHaveLength(6)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480', '10200'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, true, false])
}

// 1.3.0, L2 and canonical
Expand All @@ -145,12 +146,12 @@ describe('useCompatibleNetworks', () => {
safeVersion: '1.3.0',
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
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])
expect(result.current).toHaveLength(6)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480', '10200'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, true, true])
}

// 1.3.0, L1 and EIP155 is not available on Worldchain
// 1.3.0, L1 and EIP155 is not available on Worldchain and Chiado
{
const creationData: ReplayedSafeProps = {
factoryAddress: PROXY_FACTORY_130_DEPLOYMENTS?.eip155?.address!,
Expand All @@ -160,9 +161,9 @@ describe('useCompatibleNetworks', () => {
safeVersion: '1.3.0',
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
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])
expect(result.current).toHaveLength(6)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480', '10200'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, false, false])
}

// 1.3.0, L2 and EIP155
Expand All @@ -175,9 +176,9 @@ describe('useCompatibleNetworks', () => {
safeVersion: '1.3.0',
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
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])
expect(result.current).toHaveLength(6)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480', '10200'])
expect(result.current.map((chain) => chain.available)).toEqual([true, true, true, false, false, false])
}
})

Expand All @@ -201,8 +202,8 @@ describe('useCompatibleNetworks', () => {
safeVersion: '1.1.1',
}
const { result } = renderHook(() => useCompatibleNetworks(creationData))
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])
expect(result.current).toHaveLength(6)
expect(result.current.map((chain) => chain.chainId)).toEqual(['1', '10', '100', '324', '480', '10200'])
expect(result.current.map((chain) => chain.available)).toEqual([false, false, false, false, false, false])
})
})
64 changes: 53 additions & 11 deletions src/features/multichain/hooks/useCompatibleNetworks.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { type ReplayedSafeProps } from '@/features/counterfactual/store/undeployedSafesSlice'
import useChains from '@/hooks/useChains'
import { hasMatchingDeployment } from '@/services/contracts/deployments'
import { hasCanonicalDeployment, hasMatchingDeployment } from '@/services/contracts/deployments'
import { ZERO_ADDRESS } from '@safe-global/protocol-kit/dist/src/utils/constants'
import { type SafeVersion } from '@safe-global/safe-core-sdk-types'
import {
getCompatibilityFallbackHandlerDeployments,
getProxyFactoryDeployments,
getSafeL2SingletonDeployments,
getSafeSingletonDeployments,
getSafeToL2MigrationDeployments,
getSafeToL2SetupDeployments,
} from '@safe-global/safe-deployments'
import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'

Expand All @@ -27,21 +30,60 @@ export const useCompatibleNetworks = (

const { masterCopy, factoryAddress, safeAccountConfig } = creation

const { fallbackHandler } = safeAccountConfig
const { fallbackHandler, to } = safeAccountConfig

return configs.map((config) => {
const isL1MasterCopy = hasMatchingDeployment(
getSafeSingletonDeployments,
masterCopy,
config.chainId,
SUPPORTED_VERSIONS,
)
const isL2MasterCopy = hasMatchingDeployment(
getSafeL2SingletonDeployments,
masterCopy,
config.chainId,
SUPPORTED_VERSIONS,
)
const masterCopyExists = isL1MasterCopy || isL2MasterCopy

const proxyFactoryExists = hasMatchingDeployment(
getProxyFactoryDeployments,
factoryAddress,
config.chainId,
SUPPORTED_VERSIONS,
)
const fallbackHandlerExists = hasMatchingDeployment(
getCompatibilityFallbackHandlerDeployments,
fallbackHandler,
config.chainId,
SUPPORTED_VERSIONS,
)

// We only need to check that it is nonzero as useSafeCreationData already validates that it is the setupToL2 call otherwise
const includesSetupToL2 = to !== ZERO_ADDRESS

// If the creation includes the setupToL2 call, the contract needs to be deployed on the chain
const areSetupToL2ConditionsMet =
!includesSetupToL2 ||
hasCanonicalDeployment(getSafeToL2SetupDeployments({ network: config.chainId, version: '1.4.1' }), config.chainId)

// If the masterCopy is L1 on a L2 chain, includes the setupToL2 Call or the Migration contract exists
const isMigrationRequired = isL1MasterCopy && !includesSetupToL2 && config.l2
const isMigrationPossible = hasCanonicalDeployment(
getSafeToL2MigrationDeployments({ network: config.chainId, version: '1.4.1' }),
config.chainId,
)
const areMigrationConditionsMet = !isMigrationRequired || isMigrationPossible

return {
...config,
available:
(hasMatchingDeployment(getSafeSingletonDeployments, masterCopy, config.chainId, SUPPORTED_VERSIONS) ||
hasMatchingDeployment(getSafeL2SingletonDeployments, masterCopy, config.chainId, SUPPORTED_VERSIONS)) &&
hasMatchingDeployment(getProxyFactoryDeployments, factoryAddress, config.chainId, SUPPORTED_VERSIONS) &&
hasMatchingDeployment(
getCompatibilityFallbackHandlerDeployments,
fallbackHandler,
config.chainId,
SUPPORTED_VERSIONS,
),
masterCopyExists &&
proxyFactoryExists &&
fallbackHandlerExists &&
areSetupToL2ConditionsMet &&
areMigrationConditionsMet,
}
})
}
Loading

0 comments on commit 9293eb1

Please sign in to comment.