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
相关产品推荐
相关产品推荐

