You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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地址,而是临时中间地址,导致预计算地址完全错误。

修复步骤

  1. 修改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;
        }
    }
    
  2. 验证预计算与实际地址一致性
    修改合约后重新执行部署脚本,此时预计算地址会与实际部署地址完全匹配。

关键注意点

  • 必须使用zkSync官方提供的ContractHelper计算字节码哈希,确保和ContractDeployer内部使用的哈希一致。
  • 若Child合约构造函数有参数,需将参数编码后传入constructorCalldata,预计算时也要使用相同的calldata。
  • zkSync的合约部署逻辑与以太坊原生存在差异,不能直接套用以太坊的create2使用方式。

内容的提问来源于stack exchange,提问作者Chance Chapman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 01:03:09