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

Add zkSyncEraCreate2Address to generates a zkSync Era address #553

Merged
merged 2 commits into from
Oct 19, 2023
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
68 changes: 49 additions & 19 deletions packages/protocol-kit/src/contracts/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isAddress } from 'ethers'
import { isAddress, zeroPadValue } from 'ethers'
import { DEFAULT_SAFE_VERSION } from '@safe-global/protocol-kit/contracts/config'
import { EMPTY_DATA, ZERO_ADDRESS } from '@safe-global/protocol-kit/utils/constants'
import { createMemoizedFunction } from '@safe-global/protocol-kit/utils/memoized'
Expand All @@ -10,7 +10,6 @@ import {
} from '@safe-global/safe-core-sdk-types'
import { generateAddress2, keccak256, toBuffer } from 'ethereumjs-util'
import semverSatisfies from 'semver/functions/satisfies'
// import { utils as zkSyncUtils } from 'zksync-web3'

import {
getCompatibilityFallbackHandlerContract,
Expand Down Expand Up @@ -41,6 +40,9 @@ const ZKSYNC_SAFE_PROXY_DEPLOYED_BYTECODE: {
}
}

// keccak256(toUtf8Bytes('zksyncCreate2'))
const ZKSYNC_CREATE2_PREFIX = '0x2020dba91b30cc0006188af794c2fb30dd8520db7e2c088b7fc7c103c00ca494'

export interface PredictSafeAddressProps {
ethAdapter: EthAdapter
safeAccountConfig: SafeAccountConfig
Expand Down Expand Up @@ -202,29 +204,22 @@ export async function predictSafeAddress({
const input = ethAdapter.encodeParameters(['address'], [await safeContract.getAddress()])

const chainId = await ethAdapter.getChainId()
// zkSync Era counterfactual deployment is calculated differently
// https://era.zksync.io/docs/reference/architecture/differences-with-ethereum.html#create-create2
// if ([ZKSYNC_MAINNET, ZKSYNC_TESTNET].includes(chainId)) {
// const bytecodeHash = ZKSYNC_SAFE_PROXY_DEPLOYED_BYTECODE[safeVersion].deployedBytecodeHash
// return zkSyncUtils.create2Address(
// await safeProxyFactoryContract.getAddress(),
// bytecodeHash,
// salt,
// input
// )
// }
const from = await safeProxyFactoryContract.getAddress()

// On the zkSync Era chain, the counterfactual deployment address is calculated differently
const isZkSyncEraChain = [ZKSYNC_MAINNET, ZKSYNC_TESTNET].includes(chainId)
if (isZkSyncEraChain) {
const proxyAddress = zkSyncEraCreate2Address(from, safeVersion, salt, input)

return ethAdapter.getChecksummedAddress(proxyAddress)
}

const constructorData = toBuffer(input).toString('hex')

const initCode = proxyCreationCode + constructorData

const proxyAddress =
'0x' +
generateAddress2(
toBuffer(await safeProxyFactoryContract.getAddress()),
toBuffer(salt),
toBuffer(initCode)
).toString('hex')
'0x' + generateAddress2(toBuffer(from), toBuffer(salt), toBuffer(initCode)).toString('hex')

return ethAdapter.getChecksummedAddress(proxyAddress)
}
Expand All @@ -240,3 +235,38 @@ export const validateSafeDeploymentConfig = ({ saltNonce }: SafeDeploymentConfig
if (saltNonce && BigInt(saltNonce) < 0)
throw new Error('saltNonce must be greater than or equal to 0')
}

/**
* Generates a zkSync Era address. zkSync Era uses a distinct address derivation method compared to Ethereum
* see: https://era.zksync.io/docs/reference/architecture/differences-with-ethereum.html#address-derivation
*
* @param {string} from - The sender's address.
* @param {SafeVersion} safeVersion - The version of the safe.
* @param {Buffer} salt - The salt used for address derivation.
* @param {string} input - Additional input data for the derivation.
*
* @returns {string} The derived zkSync Era address.
*/
export function zkSyncEraCreate2Address(
from: string,
dasanra marked this conversation as resolved.
Show resolved Hide resolved
safeVersion: SafeVersion,
salt: Buffer,
input: string
): string {
const bytecodeHash = ZKSYNC_SAFE_PROXY_DEPLOYED_BYTECODE[safeVersion].deployedBytecodeHash
const inputHash = keccak256(toBuffer(input))

const addressBytes = keccak256(
toBuffer(
ZKSYNC_CREATE2_PREFIX +
zeroPadValue(from, 32).slice(2) +
salt.toString('hex') +
bytecodeHash.slice(2) +
inputHash.toString('hex')
)
)
.toString('hex')
.slice(24)

return addressBytes
}
9 changes: 6 additions & 3 deletions packages/protocol-kit/tests/e2e/contractManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Safe, { ContractNetworksConfig, PredictedSafeProps } from '@safe-global/p
import { ZERO_ADDRESS } from '@safe-global/protocol-kit/utils/constants'
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import { deployments, waffle } from 'hardhat'
import { deployments } from 'hardhat'
import { getContractNetworks } from './utils/setupContractNetworks'
import {
getCompatibilityFallbackHandler,
Expand All @@ -13,7 +13,8 @@ import {
getMultiSendCallOnly,
getSafeSingleton,
getSafeWithOwners,
getSignMessageLib
getSignMessageLib,
getSimulateTxAccessor
} from './utils/setupContracts'
import { getEthAdapter } from './utils/setupEthAdapter'
import { getAccounts } from './utils/setupTestNetwork'
Expand Down Expand Up @@ -108,7 +109,9 @@ describe('Safe contracts manager', () => {
signMessageLibAddress: ZERO_ADDRESS,
signMessageLibAbi: (await getSignMessageLib()).abi,
createCallAddress: ZERO_ADDRESS,
createCallAbi: (await getCreateCall()).abi
createCallAbi: (await getCreateCall()).abi,
simulateTxAccessorAddress: ZERO_ADDRESS,
simulateTxAccessorAbi: (await getSimulateTxAccessor()).abi
}
}
const [account1] = accounts
Expand Down
143 changes: 72 additions & 71 deletions packages/protocol-kit/tests/e2e/utilsContracts.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import chai from 'chai'
import { deployments, waffle } from 'hardhat'
import { deployments } from 'hardhat'

import { getAccounts } from './utils/setupTestNetwork'
import { getContractNetworks } from './utils/setupContractNetworks'
Expand Down Expand Up @@ -491,84 +491,85 @@ describe('Contract utils', () => {
}
)

// FIXME: This test will be fixed in the issue safe-core-sdk #546 .See: https://github.com/safe-global/safe-core-sdk/issues/546
// itif(true && safeVersionDeployed === '1.3.0')(
it.skip('returns the predicted address for Safes deployed on zkSync Era', async () => {
const { contractNetworks } = await setupTests()
itif(safeVersionDeployed === '1.3.0')(
'returns the predicted address for Safes deployed on zkSync Era',
async () => {
const { contractNetworks } = await setupTests()

const safeVersion = safeVersionDeployed
// Create EthAdapter instance
const ethAdapter = await getEthAdapter(getNetworkProvider('zksync'))
const chainId = await ethAdapter.getChainId()
const customContracts = contractNetworks[chainId]
const safeVersion = safeVersionDeployed
// Create EthAdapter instance
const ethAdapter = await getEthAdapter(getNetworkProvider('zksync'))
const chainId = await ethAdapter.getChainId()
const customContracts = contractNetworks[chainId]

// We check real deployments from zksync return the expected address.
// We check real deployments from zksync return the expected address.

// 1/1 Safe
const safeAccountConfig1: SafeAccountConfig = {
owners: ['0xc6b82bA149CFA113f8f48d5E3b1F78e933e16DfD'],
threshold: 1
}
const safeDeploymentConfig1: SafeDeploymentConfig = {
safeVersion,
saltNonce: '1691490995332'
}
const expectedSafeAddress1 = '0x4e19dA81a54eFbaBeb9AD50646f7643076475D65'
// 1/1 Safe
const safeAccountConfig1: SafeAccountConfig = {
owners: ['0xc6b82bA149CFA113f8f48d5E3b1F78e933e16DfD'],
threshold: 1
}
const safeDeploymentConfig1: SafeDeploymentConfig = {
safeVersion,
saltNonce: '1691490995332'
}
const expectedSafeAddress1 = '0x4e19dA81a54eFbaBeb9AD50646f7643076475D65'

const firstPredictedSafeAddress = await predictSafeAddress({
ethAdapter,
safeAccountConfig: safeAccountConfig1,
safeDeploymentConfig: safeDeploymentConfig1,
customContracts
})
const firstPredictedSafeAddress = await predictSafeAddress({
ethAdapter,
safeAccountConfig: safeAccountConfig1,
safeDeploymentConfig: safeDeploymentConfig1,
customContracts
})

// 1/2 Safe
const safeAccountConfig2: SafeAccountConfig = {
owners: [
'0x7E5E1C1FC6d625C1e60d78fDAB1CCE91e32261e4',
'0x6994Dc2544C1137b355488A9fc7b4F6EC2Bfeb5D'
],
threshold: 1
}
const safeDeploymentConfig2: SafeDeploymentConfig = {
safeVersion,
saltNonce: '1690771277826'
}
const expectedSafeAddress2 = '0x60c7F13dE7C8Fb88b3845e58859658bdc44243F8'
// 1/2 Safe
const safeAccountConfig2: SafeAccountConfig = {
owners: [
'0x7E5E1C1FC6d625C1e60d78fDAB1CCE91e32261e4',
'0x6994Dc2544C1137b355488A9fc7b4F6EC2Bfeb5D'
],
threshold: 1
}
const safeDeploymentConfig2: SafeDeploymentConfig = {
safeVersion,
saltNonce: '1690771277826'
}
const expectedSafeAddress2 = '0x60c7F13dE7C8Fb88b3845e58859658bdc44243F8'

const secondPredictedSafeAddress = await predictSafeAddress({
ethAdapter,
safeAccountConfig: safeAccountConfig2,
safeDeploymentConfig: safeDeploymentConfig2,
customContracts
})
const secondPredictedSafeAddress = await predictSafeAddress({
ethAdapter,
safeAccountConfig: safeAccountConfig2,
safeDeploymentConfig: safeDeploymentConfig2,
customContracts
})

// 2/3 Safe
const safeAccountConfig3: SafeAccountConfig = {
owners: [
'0x99999A3C4cB8427c44294Ad36895b6a3A047060d',
'0x1234561fEd41DD2D867a038bBdB857f291864225',
'0xe2c1F5DDcc99B0D70584fB4aD9D52b49cD4Cab03'
],
threshold: 2
}
const safeDeploymentConfig3: SafeDeploymentConfig = {
safeVersion,
saltNonce: '1690944491662'
}
const expectedSafeAddress3 = '0xD971FAA20db3ad4d51D453047ca03Ce4ec164CE2'
// 2/3 Safe
const safeAccountConfig3: SafeAccountConfig = {
owners: [
'0x99999A3C4cB8427c44294Ad36895b6a3A047060d',
'0x1234561fEd41DD2D867a038bBdB857f291864225',
'0xe2c1F5DDcc99B0D70584fB4aD9D52b49cD4Cab03'
],
threshold: 2
}
const safeDeploymentConfig3: SafeDeploymentConfig = {
safeVersion,
saltNonce: '1690944491662'
}
const expectedSafeAddress3 = '0xD971FAA20db3ad4d51D453047ca03Ce4ec164CE2'

const thirdPredictedSafeAddress = await predictSafeAddress({
ethAdapter,
safeAccountConfig: safeAccountConfig3,
safeDeploymentConfig: safeDeploymentConfig3,
customContracts
})
const thirdPredictedSafeAddress = await predictSafeAddress({
ethAdapter,
safeAccountConfig: safeAccountConfig3,
safeDeploymentConfig: safeDeploymentConfig3,
customContracts
})

// returns the same predicted address each call
chai.expect(firstPredictedSafeAddress).to.be.equal(expectedSafeAddress1)
chai.expect(secondPredictedSafeAddress).to.be.equal(expectedSafeAddress2)
chai.expect(thirdPredictedSafeAddress).to.be.equal(expectedSafeAddress3)
})
// returns the same predicted address each call
chai.expect(firstPredictedSafeAddress).to.be.equal(expectedSafeAddress1)
chai.expect(secondPredictedSafeAddress).to.be.equal(expectedSafeAddress2)
chai.expect(thirdPredictedSafeAddress).to.be.equal(expectedSafeAddress3)
}
)
})
})
Loading