Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(Multichain): use contract addresses from safe-deployments [SW-169] #4262

Merged
merged 4 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading