ZKSync Era中Create2预计算地址与实际部署地址不符求助
Create2预计算地址与实际部署地址不匹配问题排查(zkSync环境)
我编写了用于部署子合约的Factory智能合约,采用create2方式部署Child合约,但始终无法让预计算地址与实际部署地址匹配。期望子合约部署到0x9b0dFF8F40c7dEb76754730eC348d849b747aba7,但实际部署地址为0xdc6B8789E7eC924e137e4258F044c4766EbB2b6b。
合约代码
Factory.sol
import "./Child.sol"; contract Factory { event ContractDeployed(address indexed contractAddress); function deployContract(bytes32 _salt) public returns (address) { Child c = new Child{salt: _salt}(); address addr = address(c); emit ContractDeployed(addr); return addr; } }
Child.sol
contract Child { address public owner; constructor() { owner = msg.sender; } function getOwner() public view returns (address) { return owner; } }
部署脚本
import { Wallet, utils, Provider, Contract } from "zksync-web3"; import * as ethers from "ethers"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; import { Factory } from "../typechain-types/Factory"; import { ContractDeployer } from "../typechain-types/ContractDeployer"; import { bytecode as childBytecode } from "../artifacts-zk/contracts/Child.sol/Child.json"; import { abi as contractDeployerABI } from "../artifacts-zk/contracts/ContractDeployer.sol/ContractDeployer.json"; // yarn hardhat deploy-zksync --script // load env file import dotenv from "dotenv"; dotenv.config(); // load wallet private key from env file const PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY || ""; if (!PRIVATE_KEY) throw "⛔️ Private key not detected! Add it to the .env file!"; // An example of a deploy script that will deploy and call a simple contract. export default async function (hre: HardhatRuntimeEnvironment) { console.log(`Running deploy script for the Factory contract`); // Initialize the wallet. const provider = new Provider('http://localhost:3050'); const wallet = new Wallet(PRIVATE_KEY, provider); // Create deployer object and load the artifact of the contract you want to deploy. const deployer = new Deployer(hre, wallet); /* *** DEPLOY FACTORY *** */ const artifact = await deployer.loadArtifact("Factory"); const factory = await deployer.deploy(artifact, []) as Factory; // Show the contract info const contractAddress = factory.address; console.log(`${artifact.contractName} was deployed to ${contractAddress}`); console.log(' ') console.log('------------------------------------------------------------------------------------------') console.log(' ') const salt = '0x0000000000000000000000000000000000000000000000000000000000000001'; console.log(`factory.address: ${factory.address}`); console.log('bytecode keccak256: ' + ethers.utils.keccak256(childBytecode)); console.log('salt: ' + salt); console.log('input: 0x'); const contractDeployer = new Contract(utils.CONTRACT_DEPLOYER_ADDRESS, contractDeployerABI, wallet) as ContractDeployer; let calc_address1 = await contractDeployer.getNewAddressCreate2(factory.address, ethers.utils.keccak256(childBytecode), salt, '0x'); console.log(`Calculated create2 address from contract deployer: ${calc_address1}`); const calc_address2 = utils.create2Address(factory.address, ethers.utils.keccak256(childBytecode), salt, '0x'); console.log('Calculated create2 address from sdk: ' + calc_address2); let nonce = await wallet.getNonce(); let calc_address3 = await contractDeployer.getNewAddressCreate(factory.address, nonce); console.log(`Calculated create address from contract deployer: ${calc_address3}`); const calc_address4 = utils.createAddress(factory.address, nonce); console.log('Calculated create address from sdk: ' + calc_address4); let tx = await factory.deployContract(salt); let receipt = await tx.wait(); console.log(`Deployed address: ${receipt.events[2].args[0]}`); }
执行输出
Running deploy script for the Factory contract Factory was deployed to 0x506D7fee857E5aaea623916880975d133bb2b8e1 ------------------------------------------------------------------------------------------ factory.address: 0x506D7fee857E5aaea623916880975d133bb2b8e1 bytecode keccak256: 0x59d901a05c8205d98ffe460633af71ae85d2575403ff114fa174966955402589 salt: 0x0000000000000000000000000000000000000000000000000000000000000001 input: 0x Calculated create2 address from contract deployer: 0x9b0dFF8F40c7dEb76754730eC348d849b747aba7 Calculated create2 address from sdk: 0x9b0dFF8F40c7dEb76754730eC348d849b747aba7 Calculated create address from contract deployer: 0xE15D9682F868245dEAe7a243ca2CeD50A9BFc110 Calculated create address from sdk: 0xE15D9682F868245dEAe7a243ca2CeD50A9BFc110 Deployed address: 0xdc6B8789E7eC924e137e4258F044c4766EbB2b6b Done in 2.89s.
问题根源与修复方案
核心问题:zkSync的create2计算规则差异
zkSync中所有合约部署都需经过内置的ContractDeployer合约处理。当你在Factory中使用new Child{salt: _salt}()语法时,底层并非由Factory直接执行create2,而是Factory调用ContractDeployer完成部署,此时create2计算的sender不是Factory地址,而是临时中间地址,导致预计算地址完全错误。
修复步骤
修改Factory合约,显式调用ContractDeployer
直接调用zkSync内置的ContractDeployer合约的create2方法,确保预计算与实际部署的参数一致:修改后的Factory.sol:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./Child.sol"; import "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IContractDeployer.sol"; import "@matterlabs/zksync-contracts/l2/system-contracts/libraries/ContractHelper.sol"; contract Factory { event ContractDeployed(address indexed contractAddress); // zkSync内置ContractDeployer固定地址 address public constant CONTRACT_DEPLOYER_ADDRESS = 0x0000000000000000000000000000000000008006; function deployContract(bytes32 _salt) public returns (address) { // 使用zkSync提供的工具计算合约字节码哈希 bytes32 bytecodeHash = ContractHelper.hashBytecode(type(Child).creationCode); // 构造部署参数 IContractDeployer.DeploymentParams memory params = IContractDeployer.DeploymentParams({ bytecodeHash: bytecodeHash, salt: _salt, constructorCalldata: "" // Child构造函数无参数,calldata为空 }); // 调用ContractDeployer执行create2部署 address childAddr = IContractDeployer(CONTRACT_DEPLOYER_ADDRESS).create2(params); emit ContractDeployed(childAddr); return childAddr; } }验证预计算与实际地址一致性
修改合约后重新执行部署脚本,此时预计算地址会与实际部署地址完全匹配。
关键注意点
- 必须使用zkSync官方提供的
ContractHelper计算字节码哈希,确保和ContractDeployer内部使用的哈希一致。 - 若Child合约构造函数有参数,需将参数编码后传入
constructorCalldata,预计算时也要使用相同的calldata。 - zkSync的合约部署逻辑与以太坊原生存在差异,不能直接套用以太坊的create2使用方式。
内容的提问来源于stack exchange,提问作者Chance Chapman
相关产品推荐
相关产品推荐

