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

Hardhat测试中无法初始化OpenZeppelin Clone合约(VM异常错误)

问题:Hardhat中使用OpenZeppelin Clones创建克隆合约时初始化失败

我尝试用OpenZeppelin Clones库(EIP-1167)创建克隆合约,但在Hardhat测试里一直报VM异常,错误提示initialize did not return true,但代码在Remix中能正常运行,怀疑是Hardhat环境配置或测试逻辑问题。


相关代码与配置

实现合约的Initialize函数

contract Whoopy is Initializable, VRFConsumerBaseV2, KeeperCompatible {

function initialize(address _whoopyCreator) public initializer {

    VRFConsumerBaseV2.initialise(vrfCoordinatorV2);
    i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2);
    s_raffleState = RaffleState.CLOSED;
    s_lastTimeStamp = block.timestamp;
    whoopyCreator = _whoopyCreator;

    i_vrfCoordinator.addConsumer(subscriptionId, address(this));
    emit NewConsumerAdded(address(this)); 
}

CloneFactory合约

pragma solidity ^0.8.8;

import "@openzeppelin/contracts/proxy/Clones.sol";


contract WhoopyFactory {
    address public implementationContract;

    address[] public allClones;

    event NewClone(address indexed _instance);

    mapping(address => address) public whoopyList;

    constructor(address _implementation) {
        implementationContract = _implementation;
    }

    function createClone(address _whoopyCreator) payable external returns (address instance) {
        instance = Clones.clone(implementationContract);
        (bool success, ) = instance.call{value: msg.value}(abi.encodeWithSignature("initialize(address)", _whoopyCreator));
        require(success==true, "initialize did not return true");
        allClones.push(instance);
        whoopyList[_whoopyCreator] = instance;
        emit NewClone(instance);
        return instance;
    }

测试代码

const { assert, expect } = require("chai")
const { ethers, upgrades } = require("hardhat")
const { networkConfig } = require("../../helper-hardhat-config")
const fs = require("fs")
const path = require("path")
const { web3, Contract } = require("web3")

const getGas = async (tx) => {
    const receipt = await ethers.provider.getTransactionReceipt(tx.hash)
    return receipt.gasUsed.toString()
}

let whoopy,
    Whoopy,
    WhoopyFactory,
    wf,
    whoopyStandaloneGas,
    whoopyFactoryGas,
    clonedContract,
    accountConnectedClone,
    wCreator,
    wallet,
    addr1,
    addr2

describe("Whoopy + WhoopyFactory", function () {
    it("Initialises contract correctly", async function () {

provider = ethers.provider


        addr1 = new ethers.Wallet("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", provider)
        addr2 = new ethers.Wallet("0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", provider)
 


        Whoopy = await ethers.getContractFactory("Whoopy", addr1)
        whoopy = await Whoopy.deploy()
        await whoopy.deployed()


        const dir = path.resolve(
            __dirname,
            "/Users/boss/hardhat-smartcontract-lottery/artifacts/contracts/Whoopy.sol/Whoopy.json"
        )
        const file = fs.readFileSync(dir, "utf8")
        const json = JSON.parse(file)
        const abi = json.abi

        WhoopyFactory = await ethers.getContractFactory("WhoopyFactory", addr1)
        wf = await WhoopyFactory.deploy(whoopy.address)
        await wf.deployed()


        wf.connect(addr2)



        const tx = await wf.createClone("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", {
            value: ethers.utils.parseUnits("1", "ether"),
        })
        console.log(tx)



        const txReceipt = await tx.wait(1)
        console.log(txReceipt)

    })
})

错误信息

Error: VM Exception while processing transaction: reverted with reason string 'initialize did not return true'

Hardhat配置

require("@nomiclabs/hardhat-waffle")
require("@nomiclabs/hardhat-etherscan")
require("hardhat-deploy")
require("solidity-coverage")
require("hardhat-gas-reporter")
require("hardhat-contract-sizer")
require("dotenv").config()
require("@nomiclabs/hardhat-ethers");
// require('@openzeppelin/contracts');


const RINKEBY_RPC_URL = process.env.RINKEBY_RPC_URL
const PRIVATE_KEY = process.env.PRIVATE_KEY
const COINMARKETCAP_API_KEY = process.env.COINMARKETCAP_API_KEY
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  defaultNetwork: "hardhat",
  networks: {
    hardhat: {
      chainId: 31337,
      blockConfirmations: 1 
    },
    localhost: {
      chainId: 31337,
  },
    rinkeby: {
      chainId: 4, 
      blockConfirmations: 6,
      url: RINKEBY_RPC_URL,
      accounts: [PRIVATE_KEY]
    }

  },
  solidity: "0.8.8",
  gasReporter: {
    enabled: true,
    currency: "USD",
    outputFile: "gas-report.txt",
    noColors: true,
    // coinmarketcap: process.env.COINMARKETCAP_API_KEY,
},
  namedAccounts: {
    deployer: {
      default: 0,
    },
    player: {
      default: 1,
    }
  },
  mocha: {
    timeout: 900000,
  },
  etherscan: {
    apiKey: {
        rinkeby: ETHERSCAN_API_KEY,
        // kovan: ETHERSCAN_API_KEY,
        // polygon: POLYGONSCAN_API_KEY,
    },
},
};

排查方向与解决方案

1. 实现合约依赖参数未初始化

Whoopy合约继承了VRFConsumerBaseV2,但vrfCoordinatorV2和subscriptionId变量未在部署或初始化时赋值,导致克隆合约执行initialize时,调用VRFConsumerBaseV2.initialise或i_vrfCoordinator.addConsumer失败。

解决:
修改initialize函数,传入VRF相关参数:

function initialize(address _whoopyCreator, address _vrfCoordinatorV2, uint64 _subscriptionId) public initializer {
    VRFConsumerBaseV2.initialise(_vrfCoordinatorV2);
    i_vrfCoordinator = VRFCoordinatorV2Interface(_vrfCoordinatorV2);
    subscriptionId = _subscriptionId;
    s_raffleState = RaffleState.CLOSED;
    s_lastTimeStamp = block.timestamp;
    whoopyCreator = _whoopyCreator;

    i_vrfCoordinator.addConsumer(subscriptionId, address(this));
    emit NewConsumerAdded(address(this)); 
}

同时更新工厂合约的createClone函数,传递这些参数。

2. 测试中合约连接未生效

代码中wf.connect(addr2)仅返回连接后的实例,但未赋值给变量,后续调用createClone仍使用原wf实例(连接addr1),可能引发权限问题。

解决:

const wfConnected = wf.connect(addr2)
const tx = await wfConnected.createClone("0x70997970C51812dc3A010C7d01b50e0d17dc79C8", {
    value: ethers.utils.parseUnits("1", "ether"),
})

3. Hardhat本地网络缺少VRF模拟合约

本地网络未部署VRFCoordinatorV2Mock,导致调用i_vrfCoordinator.addConsumer时地址无效,触发回滚。

解决:
在测试前部署模拟合约,生成有效订阅ID:

// 部署VRF模拟合约
const VRFCoordinatorV2Mock = await ethers.getContractFactory("VRFCoordinatorV2Mock")
const vrfMock = await VRFCoordinatorV2Mock.deploy(0, 0)
await vrfMock.deployed()

// 创建订阅
const txSub = await vrfMock.createSubscription()
const subReceipt = await txSub.wait()
const subscriptionId = subReceipt.events[0].args.subId

// 后续克隆初始化时传入vrfMock.address和subscriptionId

4. 初始化调用方式存在编码风险

使用abi.encodeWithSignature可能出现参数编码错误,建议改用合约接口直接调用:

修改工厂合约:

function createClone(address _whoopyCreator, address _vrfCoordinatorV2, uint64 _subscriptionId) payable external returns (address instance) {
    instance = Clones.clone(implementationContract);
    Whoopy(instance).initialize{value: msg.value}(_whoopyCreator, _vrfCoordinatorV2, _subscriptionId);
    allClones.push(instance);
    whoopyList[_whoopyCreator] = instance;
    emit NewClone(instance);
    return instance;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 07:50:28