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

使用Hardhat和OpenZeppelin Upgrades加载代理合约触发CALL_EXCEPTION回退的原因

问题分析与解决办法

核心问题定位

升级版本的ERC1155合约通过loadFixture加载后调用balanceOf触发CALL_EXCEPTION,但直接在fixture中调用正常,非升级版无此问题——说明问题出在升级合约的部署/快照还原逻辑上,和合约业务逻辑无关。

可能的原因及修复步骤

1. Fixture中升级合约部署流程不规范

升级合约必须通过OpenZeppelin的deployProxy正确部署并执行初始化,若fixture里部署代码不标准,会导致loadFixture还原后代理合约未正确关联实现合约或未完成初始化。

修复示例代码:

const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");
const { upgrades } = require("hardhat");

async function deployNftFixture() {
  const [owner] = await ethers.getSigners();
  // 用deployProxy标准流程部署升级合约,指定初始化函数和参数
  const Nft1155Upgradeable = await ethers.getContractFactory("Nft1155Upgradeable");
  const nft = await upgrades.deployProxy(Nft1155Upgradeable, ["ipfs://your-base-uri/"], {
    initializer: "initialize"
  });
  await nft.deployed();
  
  return { nft, owner };
}

// 测试用例写法
describe("NFT测试", function () {
  it("直接调用fixture内合约正常", async function () {
    const { nft, owner } = await deployNftFixture();
    const balance = await nft.balanceOf(owner.address, 1);
    // 断言逻辑
  });

  it("loadFixture后调用正常", async function () {
    const { nft, owner } = await loadFixture(deployNftFixture);
    const balance = await nft.balanceOf(owner.address, 1);
    // 断言逻辑
  });
});

2. Hardhat快照与升级合约的兼容性问题

Hardhat的loadFixture依赖网络快照,而OpenZeppelin升级插件部署时会修改网络状态(比如存储代理的实现地址),快照还原可能导致代理状态异常。

修复方案:

  • 所有升级合约的部署、初始化逻辑都放在fixture内部完成,不要在fixture外修改合约状态
  • 执行npx hardhat clean清除编译缓存后重新测试
  • 确认依赖版本匹配:@openzeppelin/hardhat-upgrades和@openzeppelin/contracts-upgradeable版本需兼容(当前4.7.3的合约对应upgrades插件^1.20.0+版本即可,你的1.21.0版本没问题,可尝试重新安装依赖)

3. 合约初始化函数逻辑/权限问题

如果initialize函数执行失败,代理合约会处于未初始化状态,调用任何函数都会触发回退。

检查点:

  • 确保初始化函数正确调用了所有父合约的初始化方法:
function initialize(string memory uri) public initializer {
  __ERC1155_init(uri);
  __ERC1155Burnable_init();
  __ERC1155Supply_init();
  __AccessControlEnumerable_init();
  // 自定义初始化逻辑
}
  • 确认initialize函数仅被调用一次(initializer修饰符会阻止重复调用)

4. Ethers合约实例关联错误

loadFixture还原后,要确认合约实例关联的是代理合约地址而非实现合约地址。

验证代码:

it("验证合约地址正确性", async function () {
  const { nft } = await loadFixture(deployNftFixture);
  console.log("代理合约地址:", nft.address);
  // 获取实现合约地址,确认代理关联正常
  const implAddress = await upgrades.erc1967.getImplementationAddress(nft.address);
  console.log("实现合约地址:", implAddress);
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 22:55:17