Skip to content

Commit

Permalink
Feat(Multichain): use contract addresses from safe-deployments [SW-16…
Browse files Browse the repository at this point in the history
…9] (#4262)

* feat: use actual contract addresses from safe-deployments

* pr comments, use typechain

* remove unecessary undefined check for safeToL2SetupInterface
  • Loading branch information
jmealy authored Sep 30, 2024
1 parent 2531ac1 commit 5c7d23c
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 61 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.4",
"@safe-global/protocol-kit": "^4.1.0",
"@safe-global/safe-apps-sdk": "^9.1.0",
"@safe-global/safe-deployments": "^1.37.8",
"@safe-global/safe-deployments": "^1.37.10",
"@safe-global/safe-gateway-typescript-sdk": "3.22.3-beta.13",
"@safe-global/safe-modules-deployments": "^2.2.1",
"@sentry/react": "^7.91.0",
Expand Down
21 changes: 15 additions & 6 deletions src/components/new-safe/create/logic/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
relaySafeCreation,
getRedirect,
createNewUndeployedSafeWithoutSalt,
SAFE_TO_L2_SETUP_INTERFACE,
} from '@/components/new-safe/create/logic/index'
import { relayTransaction } from '@safe-global/safe-gateway-typescript-sdk'
import { toBeHex } from 'ethers'
Expand All @@ -28,13 +27,15 @@ import { type FEATURES as GatewayFeatures } from '@safe-global/safe-gateway-type
import { chainBuilder } from '@/tests/builders/chains'
import { type ReplayedSafeProps } from '@/store/slices'
import { faker } from '@faker-js/faker'
import { ECOSYSTEM_ID_ADDRESS, SAFE_TO_L2_SETUP_ADDRESS } from '@/config/constants'
import { ECOSYSTEM_ID_ADDRESS } from '@/config/constants'
import {
getFallbackHandlerDeployment,
getProxyFactoryDeployment,
getSafeL2SingletonDeployment,
getSafeSingletonDeployment,
getSafeToL2SetupDeployment,
} from '@safe-global/safe-deployments'
import { Safe_to_l2_setup__factory } from '@/types/contracts'

const provider = new JsonRpcProvider(undefined, { name: 'ethereum', chainId: 1 })

Expand All @@ -44,6 +45,10 @@ const latestSafeVersion = getLatestSafeVersion(
.build(),
)

const safeToL2SetupDeployment = getSafeToL2SetupDeployment()
const safeToL2SetupAddress = safeToL2SetupDeployment?.defaultAddress
const safeToL2SetupInterface = Safe_to_l2_setup__factory.createInterface()

describe('create/logic', () => {
describe('createNewSafeViaRelayer', () => {
const owner1 = toBeHex('0x1', 20)
Expand Down Expand Up @@ -285,6 +290,10 @@ describe('create/logic', () => {
owners: [faker.finance.ethereumAddress()],
threshold: 1,
}
const safeL2SingletonDeployment = getSafeL2SingletonDeployment({
version: '1.4.1',
network: '137',
})?.defaultAddress
expect(
createNewUndeployedSafeWithoutSalt(
'1.4.1',
Expand All @@ -300,10 +309,10 @@ describe('create/logic', () => {
safeAccountConfig: {
...safeSetup,
fallbackHandler: getFallbackHandlerDeployment({ version: '1.4.1', network: '137' })?.defaultAddress,
to: SAFE_TO_L2_SETUP_ADDRESS,
data: SAFE_TO_L2_SETUP_INTERFACE.encodeFunctionData('setupToL2', [
getSafeL2SingletonDeployment({ version: '1.4.1', network: '137' })?.defaultAddress,
]),
to: safeToL2SetupAddress,
data:
safeL2SingletonDeployment &&
safeToL2SetupInterface.encodeFunctionData('setupToL2', [safeL2SingletonDeployment]),
paymentReceiver: ECOSYSTEM_ID_ADDRESS,
},
safeVersion: '1.4.1',
Expand Down
17 changes: 10 additions & 7 deletions src/components/new-safe/create/logic/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SafeVersion } from '@safe-global/safe-core-sdk-types'
import { Interface, type Eip1193Provider, type Provider } from 'ethers'
import { type Eip1193Provider, type Provider } from 'ethers'
import semverSatisfies from 'semver/functions/satisfies'

import { getSafeInfo, type SafeInfo, type ChainInfo, relayTransaction } from '@safe-global/safe-gateway-typescript-sdk'
Expand All @@ -19,12 +19,13 @@ import {
getProxyFactoryDeployment,
getSafeL2SingletonDeployment,
getSafeSingletonDeployment,
getSafeToL2SetupDeployment,
} from '@safe-global/safe-deployments'
import { ECOSYSTEM_ID_ADDRESS, SAFE_TO_L2_SETUP_ADDRESS } from '@/config/constants'
import { ECOSYSTEM_ID_ADDRESS } from '@/config/constants'
import type { ReplayedSafeProps, UndeployedSafeProps } from '@/store/slices'
import { activateReplayedSafe, isPredictedSafeProps } from '@/features/counterfactual/utils'
import { getSafeContractDeployment } from '@/services/contracts/deployments'
import { Safe__factory, Safe_proxy_factory__factory } from '@/types/contracts'
import { Safe__factory, Safe_proxy_factory__factory, Safe_to_l2_setup__factory } from '@/types/contracts'
import { createWeb3 } from '@/hooks/wallets/web3'
import { hasMultiChainCreationFeatures } from '@/components/welcome/MyAccounts/utils/multiChainSafe'

Expand Down Expand Up @@ -90,8 +91,6 @@ export const computeNewSafeAddress = async (
})
}

export const SAFE_TO_L2_SETUP_INTERFACE = new Interface(['function setupToL2(address l2Singleton)'])

export const encodeSafeSetupCall = (safeAccountConfig: ReplayedSafeProps['safeAccountConfig']) => {
return Safe__factory.createInterface().encodeFunctionData('setup', [
safeAccountConfig.owners,
Expand Down Expand Up @@ -226,6 +225,10 @@ export const createNewUndeployedSafeWithoutSalt = (
throw new Error('No Safe deployment found')
}

const safeToL2SetupDeployment = getSafeToL2SetupDeployment({ version: '1.4.1', network: chain.chainId })
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')

Expand All @@ -238,8 +241,8 @@ export const createNewUndeployedSafeWithoutSalt = (
threshold: safeAccountConfig.threshold,
owners: safeAccountConfig.owners,
fallbackHandler: fallbackHandlerAddress,
to: includeMigration ? SAFE_TO_L2_SETUP_ADDRESS : ZERO_ADDRESS,
data: includeMigration ? SAFE_TO_L2_SETUP_INTERFACE.encodeFunctionData('setupToL2', [safeL2Address]) : EMPTY_DATA,
to: includeMigration && safeToL2SetupAddress ? safeToL2SetupAddress : ZERO_ADDRESS,
data: includeMigration ? safeToL2SetupInterface.encodeFunctionData('setupToL2', [safeL2Address]) : EMPTY_DATA,
paymentReceiver: ECOSYSTEM_ID_ADDRESS,
},
safeVersion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import accordionCss from '@/styles/accordion.module.css'
import CodeIcon from '@mui/icons-material/Code'
import DecodedData from '@/components/transactions/TxDetails/TxData/DecodedData'
import { sameAddress } from '@/utils/addresses'
import { SAFE_TO_L2_MIGRATION_ADDRESS } from '@/config/constants'
import { getSafeToL2MigrationDeployment } from '@safe-global/safe-deployments'
import { useCurrentChain } from '@/hooks/useChains'

type SingleTxDecodedProps = {
tx: InternalTransaction
Expand All @@ -20,20 +21,24 @@ type SingleTxDecodedProps = {
}

export const SingleTxDecoded = ({ tx, txData, actionTitle, variant, expanded, onChange }: SingleTxDecodedProps) => {
const chain = useCurrentChain()
const isNativeTransfer = tx.value !== '0' && (!tx.data || isEmptyHexData(tx.data))
const method = tx.dataDecoded?.method || (isNativeTransfer ? 'native transfer' : 'contract interaction')

const addressInfo = txData.addressInfoIndex?.[tx.to]
const name = addressInfo?.name

const safeToL2MigrationDeployment = getSafeToL2MigrationDeployment()
const safeToL2MigrationAddress = chain && safeToL2MigrationDeployment?.networkAddresses[chain.chainId]

const singleTxData = {
to: { value: tx.to },
value: tx.value,
operation: tx.operation,
dataDecoded: tx.dataDecoded,
hexData: tx.data ?? undefined,
addressInfoIndex: txData.addressInfoIndex,
trustedDelegateCallTarget: sameAddress(tx.to, SAFE_TO_L2_MIGRATION_ADDRESS), // We only trusted a nested Migration
trustedDelegateCallTarget: sameAddress(tx.to, safeToL2MigrationAddress), // We only trusted a nested Migration
}

return (
Expand Down
7 changes: 0 additions & 7 deletions src/config/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Interface } from 'ethers'
import chains from './chains'

export const IS_PRODUCTION = process.env.NEXT_PUBLIC_IS_PRODUCTION === 'true'
Expand Down Expand Up @@ -110,11 +109,5 @@ export const REDEFINE_ARTICLE = 'https://safe.mirror.xyz/rInLWZwD_sf7enjoFerj6FI

export const CHAINALYSIS_OFAC_CONTRACT = '0x40c57923924b5c5c5455c48d93317139addac8fb'

// TODO: Get from safe-deployments once available
export const SAFE_TO_L2_MIGRATION_ADDRESS = '0x7Baec386CAF8e02B0BB4AFc98b4F9381EEeE283C'
export const SAFE_TO_L2_INTERFACE = new Interface(['function migrateToL2(address l2Singleton)'])

export const ECOSYSTEM_ID_ADDRESS =
process.env.NEXT_PUBLIC_ECOSYSTEM_ID_ADDRESS || '0x0000000000000000000000000000000000000000'

export const SAFE_TO_L2_SETUP_ADDRESS = '0x80E0d1577aD3d982BF2F49aAB00BfA161AA763c4'
8 changes: 6 additions & 2 deletions src/features/counterfactual/ActivateAccountFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ import { sameAddress } from '@/utils/addresses'
import { useEstimateSafeCreationGas } from '@/components/new-safe/create/useEstimateSafeCreationGas'
import useIsWrongChain from '@/hooks/useIsWrongChain'
import NetworkWarning from '@/components/new-safe/create/NetworkWarning'
import { SAFE_TO_L2_SETUP_ADDRESS } from '@/config/constants'
import CheckWallet from '@/components/common/CheckWallet'
import { getSafeToL2SetupDeployment } from '@safe-global/safe-deployments'

const useActivateAccount = (undeployedSafe: UndeployedSafe | undefined) => {
const chain = useCurrentChain()
Expand Down Expand Up @@ -84,7 +84,7 @@ const ActivateAccountFlow = () => {

const safeAccountConfig =
undeployedSafe && isPredictedSafeProps(undeployedSafe?.props) ? undeployedSafe?.props.safeAccountConfig : undefined
const isMultichainSafe = sameAddress(safeAccountConfig?.to, SAFE_TO_L2_SETUP_ADDRESS)

const ownerAddresses = undeployedSafeSetup?.owners || []
const [minRelays] = useLeastRemainingRelays(ownerAddresses)

Expand All @@ -96,6 +96,10 @@ const ActivateAccountFlow = () => {

const { owners, threshold, safeVersion } = undeployedSafeSetup

const safeToL2SetupDeployment = getSafeToL2SetupDeployment({ version: '1.4.1', network: chain?.chainId })
const safeToL2SetupAddress = safeToL2SetupDeployment?.defaultAddress
const isMultichainSafe = sameAddress(safeAccountConfig?.to, safeToL2SetupAddress)

const onSubmit = (txHash?: string) => {
trackEvent({ ...TX_EVENTS.CREATE, label: TX_TYPES.activate_without_tx })
trackEvent({ ...TX_EVENTS.EXECUTE, label: TX_TYPES.activate_without_tx })
Expand Down
55 changes: 30 additions & 25 deletions src/utils/__tests__/transactions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,22 @@ import {
getMultiSendDeployment,
getSafeL2SingletonDeployment,
getSafeSingletonDeployment,
getSafeToL2MigrationDeployment,
} from '@safe-global/safe-deployments'
import type Safe from '@safe-global/protocol-kit'
import { encodeMultiSendData } from '@safe-global/protocol-kit'
import { Multi_send__factory } from '@/types/contracts'
import { Multi_send__factory, Safe_to_l2_migration__factory } from '@/types/contracts'
import { faker } from '@faker-js/faker'
import { getAndValidateSafeSDK } from '@/services/tx/tx-sender/sdk'
import { decodeMultiSendData } from '@safe-global/protocol-kit/dist/src/utils'
import { checksumAddress } from '../addresses'
import { SAFE_TO_L2_MIGRATION_ADDRESS, SAFE_TO_L2_INTERFACE } from '@/config/constants'

jest.mock('@/services/tx/tx-sender/sdk')

const safeToL2MigrationDeployment = getSafeToL2MigrationDeployment()
const safeToL2MigrationAddress = safeToL2MigrationDeployment?.defaultAddress
const safeToL2MigrationInterface = Safe_to_l2_migration__factory.createInterface()

describe('transactions', () => {
const mockGetAndValidateSdk = getAndValidateSafeSDK as jest.MockedFunction<typeof getAndValidateSafeSDK>

Expand Down Expand Up @@ -312,20 +316,21 @@ describe('transactions', () => {
})

it('should not modify tx if the tx already migrates', () => {
const safeL2SingletonDeployment = getSafeL2SingletonDeployment()?.defaultAddress

const safeTx = safeTxBuilder()
.with({
data: safeTxDataBuilder()
.with({
nonce: 0,
to: SAFE_TO_L2_MIGRATION_ADDRESS,
data: SAFE_TO_L2_INTERFACE.encodeFunctionData('migrateToL2', [
getSafeL2SingletonDeployment()?.defaultAddress,
]),
to: safeToL2MigrationAddress,
data:
safeL2SingletonDeployment &&
safeToL2MigrationInterface.encodeFunctionData('migrateToL2', [safeL2SingletonDeployment]),
})
.build(),
})
.build()

const safeInfo = extendedSafeInfoBuilder()
.with({
implementationVersionState: ImplementationVersionState.UNKNOWN,
Expand All @@ -335,34 +340,32 @@ describe('transactions', () => {
},
})
.build()

expect(
prependSafeToL2Migration(safeTx, safeInfo, chainBuilder().with({ l2: true, chainId: '10' }).build()),
).resolves.toEqual(safeTx)

const multiSendSafeTx = safeTxBuilder()
.with({
data: safeTxDataBuilder()
.with({
nonce: 0,
to: getMultiSendDeployment()?.defaultAddress,
data: Multi_send__factory.createInterface().encodeFunctionData('multiSend', [
encodeMultiSendData([
{
value: '0',
operation: 1,
to: SAFE_TO_L2_MIGRATION_ADDRESS,
data: SAFE_TO_L2_INTERFACE.encodeFunctionData('migrateToL2', [
getSafeL2SingletonDeployment()?.defaultAddress,
]),
},
data:
safeToL2MigrationAddress &&
safeL2SingletonDeployment &&
Multi_send__factory.createInterface().encodeFunctionData('multiSend', [
encodeMultiSendData([
{
value: '0',
operation: 1,
to: safeToL2MigrationAddress,
data: safeToL2MigrationInterface.encodeFunctionData('migrateToL2', [safeL2SingletonDeployment]),
},
]),
]),
]),
})
.build(),
})
.build()

expect(
prependSafeToL2Migration(multiSendSafeTx, safeInfo, chainBuilder().with({ l2: true, chainId: '10' }).build()),
).resolves.toEqual(multiSendSafeTx)
Expand Down Expand Up @@ -402,14 +405,16 @@ describe('transactions', () => {
expect(modifiedTx?.data.to).toEqual(getMultiSendDeployment()?.defaultAddress)
const decodedMultiSend = decodeMultiSendData(modifiedTx!.data.data)
expect(decodedMultiSend).toHaveLength(2)
const safeL2SingletonDeployment = getSafeL2SingletonDeployment()?.defaultAddress

expect(decodedMultiSend).toEqual([
{
to: SAFE_TO_L2_MIGRATION_ADDRESS,
to: safeToL2MigrationAddress,
value: '0',
operation: 1,
data: SAFE_TO_L2_INTERFACE.encodeFunctionData('migrateToL2', [
getSafeL2SingletonDeployment()?.defaultAddress,
]),
data:
safeL2SingletonDeployment &&
safeToL2MigrationInterface.encodeFunctionData('migrateToL2', [safeL2SingletonDeployment]),
},
{
to: checksumAddress(safeTx.data.to),
Expand Down
21 changes: 16 additions & 5 deletions src/utils/transaction-guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ import { sameAddress } from '@/utils/addresses'
import type { NamedAddress } from '@/components/new-safe/create/types'
import type { RecoveryQueueItem } from '@/features/recovery/services/recovery-state'
import { ethers } from 'ethers'
import { SAFE_TO_L2_MIGRATION_ADDRESS, SAFE_TO_L2_INTERFACE } from '@/config/constants'
import { getSafeToL2MigrationDeployment } from '@safe-global/safe-deployments'
import { Safe_to_l2_migration__factory } from '@/types/contracts'

export const isTxQueued = (value: TransactionStatus): boolean => {
return [TransactionStatus.AWAITING_CONFIRMATIONS, TransactionStatus.AWAITING_EXECUTION].includes(value)
Expand Down Expand Up @@ -88,8 +89,12 @@ export const isModuleDetailedExecutionInfo = (value?: DetailedExecutionInfo): va
}

export const isMigrateToL2TxData = (value: TransactionData | undefined): boolean => {
if (sameAddress(value?.to.value, SAFE_TO_L2_MIGRATION_ADDRESS)) {
const migrateToL2Selector = SAFE_TO_L2_INTERFACE.getFunction('migrateToL2')?.selector
const safeToL2MigrationDeployment = getSafeToL2MigrationDeployment()
const safeToL2MigrationAddress = safeToL2MigrationDeployment?.defaultAddress
const safeToL2MigrationInterface = Safe_to_l2_migration__factory.createInterface()

if (sameAddress(value?.to.value, safeToL2MigrationAddress)) {
const migrateToL2Selector = safeToL2MigrationInterface?.getFunction('migrateToL2')?.selector
return migrateToL2Selector && value?.hexData ? value.hexData?.startsWith(migrateToL2Selector) : false
}
return false
Expand All @@ -100,12 +105,15 @@ export const isMigrateToL2MultiSend = (decodedData: DecodedDataResponse | undefi
const innerTxs = decodedData.parameters[0].valueDecoded
const firstInnerTx = innerTxs[0]
if (firstInnerTx) {
const safeToL2MigrationDeployment = getSafeToL2MigrationDeployment()
const safeToL2MigrationAddress = safeToL2MigrationDeployment?.defaultAddress

return (
firstInnerTx.dataDecoded?.method === 'migrateToL2' &&
firstInnerTx.dataDecoded.parameters.length === 1 &&
firstInnerTx.dataDecoded?.parameters?.[0]?.type === 'address' &&
typeof firstInnerTx.dataDecoded?.parameters[0].value === 'string' &&
sameAddress(firstInnerTx.to, SAFE_TO_L2_MIGRATION_ADDRESS)
sameAddress(firstInnerTx.to, safeToL2MigrationAddress)
)
}
}
Expand Down Expand Up @@ -149,7 +157,10 @@ export const isOrderTxInfo = (value: TransactionInfo): value is Order => {
}

export const isMigrateToL2TxInfo = (value: TransactionInfo): value is Custom => {
return isCustomTxInfo(value) && sameAddress(value.to.value, SAFE_TO_L2_MIGRATION_ADDRESS)
const safeToL2MigrationDeployment = getSafeToL2MigrationDeployment()
const safeToL2MigrationAddress = safeToL2MigrationDeployment?.defaultAddress

return isCustomTxInfo(value) && sameAddress(value.to.value, safeToL2MigrationAddress)
}

export const isSwapOrderTxInfo = (value: TransactionInfo): value is SwapOrder => {
Expand Down
Loading

0 comments on commit 5c7d23c

Please sign in to comment.