Skip to content

Commit

Permalink
fix(safe-core-sdk) predictSafeAddress on zksync era
Browse files Browse the repository at this point in the history
  • Loading branch information
dasanra committed Aug 17, 2023
1 parent b7632a8 commit dd3d06a
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 7 deletions.
3 changes: 2 additions & 1 deletion packages/safe-core-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@
"@safe-global/safe-deployments": "^1.25.0",
"ethereumjs-util": "^7.1.5",
"semver": "^7.3.8",
"web3-utils": "^1.8.1"
"web3-utils": "^1.8.1",
"zksync-web3": "^0.14.3"
},
"lint-staged": {
"src/**/!(*test).ts": [
Expand Down
39 changes: 36 additions & 3 deletions packages/safe-core-sdk/src/safeFactory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ 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 { SAFE_LAST_VERSION } from '../contracts/config'
import {
getCompatibilityFallbackHandlerContract,
Expand Down Expand Up @@ -67,6 +69,24 @@ interface SafeFactoryInitConfig {
contractNetworks?: ContractNetworksConfig
}

const ZKSYNC_MAINNET = 324
const ZKSYNC_TESTNET = 280
// For bundle size efficiency we store SafeProxy.sol/GnosisSafeProxy.sol zksync bytecode hash in hex.
// To get the values below we need to:
// 1. Compile Safe smart contracts for zksync
// 2. Get `deployedBytecode` from SafeProxy.json/GnosisSafeProxy.json
// 3. Use zksync-web3 SDK to get the bytecode hash
// const bytecodeHash = zkSyncUtils.hashBytecode(${deployedBytecode})
// 4. Use ethers to convert the array into hex
// const deployedBytecodeHash = ethers.utils.hexlify(bytecodeHash)
const ZKSYNC_SAFE_PROXY_DEPLOYED_BYTECODE: {
[version: string]: { deployedBytecodeHash: string }
} = {
'1.3.0': {
deployedBytecodeHash: '0x0100004124426fb9ebb25e27d670c068e52f9ba631bd383279a188be47e3f86d'
}
}

class SafeFactory {
#contractNetworks?: ContractNetworksConfig
#isL1SafeMasterCopy?: boolean
Expand Down Expand Up @@ -196,9 +216,22 @@ class SafeFactory {
)

const proxyCreationCode = await this.#safeProxyFactoryContract.proxyCreationCode()
const constructorData = toBuffer(
this.#ethAdapter.encodeParameters(['address'], [this.#gnosisSafeContract.getAddress()])
).toString('hex')

const input = this.#ethAdapter.encodeParameters(
['address'],
[this.#gnosisSafeContract.getAddress()]
)

const chainId = await this.#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 safeVersion = await this.#gnosisSafeContract.getVersion()
const bytecodeHash = ZKSYNC_SAFE_PROXY_DEPLOYED_BYTECODE[safeVersion].deployedBytecodeHash
return zkSyncUtils.create2Address(from, bytecodeHash, salt, input)
}

const constructorData = toBuffer(input).toString('hex')
const initCode = proxyCreationCode + constructorData

const proxyAddress =
Expand Down
76 changes: 75 additions & 1 deletion packages/safe-core-sdk/tests/safeFactory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
getSafeSingleton,
getSignMessageLib
} from './utils/setupContracts'
import { getEthAdapter } from './utils/setupEthAdapter'
import { getEthAdapter, getNetworkProvider } from './utils/setupEthAdapter'
import { getAccounts } from './utils/setupTestNetwork'

chai.use(chaiAsPromised)
Expand Down Expand Up @@ -255,6 +255,80 @@ describe('SafeProxyFactory', () => {
chai.expect(defaultCallbackHandler.address).to.be.eq(await safe.getFallbackHandler())
}
)

itif(safeVersionDeployed === '1.3.0')(
'returns the predicted address for Safes deployed on zkSync Era',
async () => {
const { contractNetworks } = await setupTests()

// Create EthAdapter instance
const ethAdapter = await getEthAdapter(getNetworkProvider('zksync'))
const safeFactory = await SafeFactory.create({
ethAdapter,
safeVersion: safeVersionDeployed,
contractNetworks
})

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

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

const firstPredictedSafeAddress = await safeFactory.predictSafeAddress({
safeAccountConfig: safeAccountConfig1,
safeDeploymentConfig: safeDeploymentConfig1
})

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

const secondPredictedSafeAddress = await safeFactory.predictSafeAddress({
safeAccountConfig: safeAccountConfig2,
safeDeploymentConfig: safeDeploymentConfig2
})

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

const thirdPredictedSafeAddress = await safeFactory.predictSafeAddress({
safeAccountConfig: safeAccountConfig3,
safeDeploymentConfig: safeDeploymentConfig3
})

// 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)
}
)
})

describe('deploySafe', async () => {
Expand Down
39 changes: 37 additions & 2 deletions packages/safe-core-sdk/tests/utils/setupEthAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ import { EthAdapter } from '@safe-global/safe-core-sdk-types'
import EthersAdapter, { EthersAdapterConfig } from '@safe-global/safe-ethers-lib'
import Web3Adapter, { Web3AdapterConfig } from '@safe-global/safe-web3-lib'
import { ethers, web3 } from 'hardhat'
import Web3 from 'web3'

export async function getEthAdapter(signerOrProvider: Signer | Provider): Promise<EthAdapter> {
type Network = 'mainnet' | 'goerli' | 'gnosis' | 'zksync'

export async function getEthAdapter(
signerOrProvider: Signer | Provider | Web3
): Promise<EthAdapter> {
let ethAdapter: EthAdapter
switch (process.env.ETH_LIB) {
case 'web3':
const signerAddress =
signerOrProvider instanceof Signer ? await signerOrProvider.getAddress() : undefined
const web3AdapterConfig: Web3AdapterConfig = { web3: web3 as any, signerAddress }
const web3Instance = signerOrProvider instanceof Web3 ? signerOrProvider : (web3 as any)
const web3AdapterConfig: Web3AdapterConfig = { web3: web3Instance, signerAddress }
ethAdapter = new Web3Adapter(web3AdapterConfig)
break
case 'ethers':
Expand All @@ -23,3 +29,32 @@ export async function getEthAdapter(signerOrProvider: Signer | Provider): Promis
}
return ethAdapter
}

export function getNetworkProvider(network: Network): Provider | Web3 {
let rpcUrl: string
switch (network) {
case 'zksync':
rpcUrl = 'https://mainnet.era.zksync.io'
break
case 'gnosis':
rpcUrl = 'https://rpc.gnosischain.com'
break
default:
rpcUrl = `https://${network}.infura.io/v3/${process.env.INFURA_KEY}`
break
}

let provider
switch (process.env.ETH_LIB) {
case 'web3':
provider = new Web3(rpcUrl)
break
case 'ethers':
provider = new ethers.providers.JsonRpcProvider(rpcUrl)
break
default:
throw new Error('Ethereum library not supported')
}

return provider
}
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15222,6 +15222,11 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==

zksync-web3@^0.14.3:
version "0.14.3"
resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.14.3.tgz#64ac2a16d597464c3fc4ae07447a8007631c57c9"
integrity sha512-hT72th4AnqyLW1d5Jlv8N2B/qhEnl2NePK2A3org7tAa24niem/UAaHMkEvmWI3SF9waYUPtqAtjpf+yvQ9zvQ==

zksync-web3@^0.8.1:
version "0.8.1"
resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.8.1.tgz#db289d8f6caf61f4d5ddc471fa3448d93208dc14"
Expand Down

0 comments on commit dd3d06a

Please sign in to comment.