如何将预编译合约部署到Hardhat节点并仅用ABI/Bytecode升级代理合约
错误原因
OpenZeppelin 的 hardhat-upgrades 插件执行 upgradeProxy 时,默认会自动校验新老合约的存储布局兼容性,避免升级后出现存储冲突。这个校验逻辑依赖合约编译阶段生成的AST、存储布局元数据,这些数据默认存储在 cache 文件夹和 artifacts 文件夹下对应合约的JSON文件中,并不是仅靠ABI和Bytecode就能完成的。你删除合约源文件后重新执行编译命令,会清空旧的缓存和产物,插件找不到对应合约的元数据,就会抛出找不到合约的错误。
可行解决方案
方案1:跳过存储布局校验(适合已经自行确认存储兼容的场景)
如果你已经人工确认新合约的存储布局和旧合约完全兼容,不会出现变量覆盖、顺序错乱等问题,可以在调用 upgradeProxy 时显式开启跳过校验的参数,就不需要依赖本地缓存的元数据,也不需要保留源文件:
const NFTv2 = await ethers.getContractFactory(upgradedContract.abi, upgradedContract.bytecode); // 新增unsafeSkipStorageCheck参数跳过存储校验 contract = await upgrades.upgradeProxy(proxyContract.address, NFTv2, { unsafeSkipStorageCheck: true });
⚠️ 注意:该操作存在风险,必须自行确保新合约的存储变量定义顺序、类型和旧合约完全兼容,新增变量必须放在所有旧变量之后。
方案2:手动部署实现合约后执行升级
完全不依赖hardhat-upgrades插件的封装逻辑,只用ABI和Bytecode完成部署和升级,步骤如下:
- 部署新的实现合约
const NFTv2 = await ethers.getContractFactory(upgradedContract.abi, upgradedContract.bytecode); const newImpl = await NFTv2.deploy(); await newImpl.deployed();
- 根据你使用的代理类型调用升级方法:
- 如果是透明代理(Transparent Proxy):用代理管理员地址调用代理的
upgradeTo方法
// 这里proxyAbi是代理合约的ABI,包含upgradeTo方法 const proxy = await ethers.getContractAt(proxyAbi, proxyContract.address, adminSigner); await proxy.upgradeTo(newImpl.address);
- 如果是UUPS代理:调用代理的升级方法(通常需要权限校验)
// 这里的abi包含你合约里定义的升级方法,比如upgradeToAndCall const proxy = await ethers.getContractAt(upgradedContract.abi, proxyContract.address, adminSigner); await proxy.upgradeToAndCall(newImpl.address, "0x");
方案3:保留编译产物元数据
如果不想跳过校验,可以在第一次编译完新版本合约之后,备份artifacts/contracts/下对应合约的JSON文件,以及cache文件夹下的相关元数据,后续执行升级时不要执行npx hardhat clean或者删除源文件后重新编译,直接使用备份的产物即可,不需要保留.sol源文件在contracts目录下。
内容的提问来源于stack exchange,提问作者johnhckuo
相关产品推荐
相关产品推荐

