Hardhat-deploy: 关于部署可升级合约的问题

创建于 2021-06-08  ·  9评论  ·  资料来源: wighawag/hardhat-deploy

部署 OpenZeppelin 可升级合约的正确方法是什么?
OpenZeppelin upgrdable 具有默认的 initialize() 函数,该函数由安全帽升级插件调用。
没有来自 hardhat-deploy 的自动调用,只有可以调用的升级后方法。

我应该先通过部署文件夹中的脚本部署合同,然后从部署文件夹外的某个脚本调用初始化函数吗? 或者有没有办法从部署文件夹本身合并调用初始化()函数?

最有用的评论

重新初始化代理功能

我在 hardhat-deploy 0.8.0 中添加了一个新选项(而不是仍然可用的methodName

https://github.com/wighawag/template-ethereum-contracts/blob/595c1b5ec9cdf1276f4d3a43b4825bcef78bd2cd/deploy/004_deploy_erc20_always_proxied_via_openzeppelin_proxy.ts#L12 -L26

如果您遇到任何问题,请告诉我

所有9条评论

hardhat-deploy 默认考虑部署脚本的方式是它们是幂等的。 这同样适用于代理部署。 这很适合开发/部署,因为您只需要考虑您想要成为什么状态,而 hardhat-deploy 将使一切都达到该状态,

所以为了回答你的问题,我通常做的是让我的初始化函数也是幂等的,所以如果它已经初始化,而不是抛出它,只需跳过它。 因此,我可以将该函数添加为 postUpgrade 函数并且一切正常。 在开发中,如果需要,我什至可以更改初始化函数以在升级时执行操作。

对于更复杂的场景,您可以在单独的部署脚本中组织每次升级。

有一个未记录的特性可能会发生变化: upgradeIndex参数让您可以指定需要按顺序执行的部署脚本。

这非常适合测试,因为您可以在升级历史记录之间的任何时间点测试 yoru 代理并确保一切正常。

感谢您的快速回复。
看来我设法使所有工作顺利进行。 我使用deploy() ,然后使用execute和 OpenZeppelin Initializable基本合约的initialize函数。 execute()返回 true,这使它在我升级合约时无法运行:

// deploy/01_deploy_skin_rewards.js

module.exports = async ({
  getNamedAccounts,
  deployments,
}) => {
  const {deploy} = deployments;
  const {deployer} = await getNamedAccounts();
  await deploy('SkinRewards', {
    from: deployer,
    proxy: {
      owner: deployer,
      proxyContract: 'OpenZeppelinTransparentProxy',
    },
    args: [],
    log: true,
  });
};
module.exports.tags = ['SkinRewards'];

接着

// deploy/02_init_skin_rewards.js

require('dotenv').config();
const RewardPeriods = ethers.BigNumber.from(process.env.REWARD_PERIODS);
module.exports = async ({getNamedAccounts, deployments}) => {
  const { deployer } = await getNamedAccounts();
  const SnookGame = await deployments.get('SnookGame');
  const SnookState = await deployments.get('SnookState');
  const SnookToken = await deployments.get('SnookToken');
  const SkillToken = await deployments.get('SkillToken');
  const Afterdeath = await deployments.get('Afterdeath');
  const Treasury = await deployments.get('Treasury');

  await deployments.execute(
    'SkinRewards',
    {from:deployer},
    'initialize',
    RewardPeriods,
    Treasury.address,
    SnookGame.address,
    SnookState.address,
    SnookToken.address,
    SkillToken.address,
    Afterdeath.address
  );
  deployments.log('Initialized SkinRewards');
  return true;
};
module.exports.tags = ['initSkinRewards'];
module.exports.id = 'initSkinRewards';

这种方法可以被认为是_好的做法_吗? 是你如何寻找的吗?

正如我上面提到的,在SkinRewards中,我继承了 OpenZeppelin 的Initializable合同,该合同定义了首先调用的initialize()函数。 所以我不知道如何在升级后运行它。 你可以解释吗? 谢谢

听起来不错,与使用methodName ("postUpgrade") 相比的缺点是您正在执行 2 tx 而不是 1

如果您使初始化函数具有幂等性,即可以通过确保进一步调用不会修改状态(如果已经初始化,则只需跳过),可以多次调用它而不会抛出,那么您可以通过methodName选项使用它. 第一个部署也将执行它。

请注意,因为您使用参数并且部署功能已设置,因此您可以简单地关闭代理选项并拥有工作的不可变合约, methodName选项使用args选项作为参数。
这意味着代理实现的构造函数也需要有这些参数。 如果你从不打算在没有代理的情况下使用合约,你可以对参数不做任何事情。

实际上,为了方便起见,如果实现合同的 arg 构造函数为零,它会完全忽略构造函数,但我不确定这是一个好主意。

下面是带有methodName选项的脚本:

// deploy/01_deploy_skin_rewards.js

module.exports = async ({
  getNamedAccounts,
  deployments,
}) => {
  const {deploy} = deployments;
  const {deployer} = await getNamedAccounts();
  await deploy('SkinRewards', {
    from: deployer,
    proxy: {
      methodName: 'initialize`,
      owner: deployer,
      proxyContract: 'OpenZeppelinTransparentProxy',
    },
    args: [
      RewardPeriods,
      Treasury.address,
      SnookGame.address,
      SnookState.address,
      SnookToken.address,
      SkillToken.address,
      Afterdeath.address
    ],
    log: true,
  });
};
module.exports.tags = ['SkinRewards'];

_如果您使初始化函数具有幂等性,即可以通过确保进一步调用不会修改状态(如果已经初始化,则只需跳过),可以多次调用它而不会抛出,那么您可以通过 methodName 选项使用它。 第一个部署也会执行它。_

这意味着我们不能使用来自https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/release-v4.0/contracts/proxy/utils/Initializable.solinitialize()函数原样.

@freebyte确实

我会考虑添加一个选项来获得这种行为

太好了谢谢!

另一个问题是关于使用已部署的合约。 特别是,我问的是Uniswap

从插件文档的这一部分,我看不到我如何提供已部署的 Uniswap 合约的地址。 Uniswap NPM 带有带有工件的build文件夹,但我如何让插件了解特定地址的合约? 我们不想在部署脚本中使用以太,对吧?

以下代码不是我们在部署脚本中想要的:

const { ethers } = require("hardhat");

const UniswapV2FactoryArtifact = require('@uniswap/v2-core/build/UniswapV2Factory.json');
const UniswapV2FactoryAddress = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f';

module.exports = async ({getNamedAccounts, deployments}) => {
  const {deployer} = await getNamedAccounts();
  const UniswapFactory = await ethers.getContractAt(UniswapV2FactoryArtifact.abi, UniswapV2FactoryAddress);
// continue...

如何将 Uniswap 部署到deployments var 中?

对于已经部署的合约,最好是在相应的部署文件夹中为它们创建文件
因此对于主网 uniswap 并假设您在 hardhat.config.js 中配置了主网网络,您可以创建文件“deployments/mainnet/UniswapFactory.json”。 至少它需要一个地址和 abi 字段

然后您可以在该网络上通过部署访问它

您指向的文档链接允许您访问工件(因此是 uniswap 构建文件夹)或部署脚本,但后者仅在相关项目在某处暴露这些部署脚本时才有效

我在我自己的项目中这样做,例如https://github.com/wighawag/universal-forwarder ,它允许用户简单地安装 npm 包,然后他们将整个项目部署在他们的测试或他们自己的网络上。 但是 uniswap 没有提供

伟大的。 非常感谢。

重新初始化代理功能

我在 hardhat-deploy 0.8.0 中添加了一个新选项(而不是仍然可用的methodName

https://github.com/wighawag/template-ethereum-contracts/blob/595c1b5ec9cdf1276f4d3a43b4825bcef78bd2cd/deploy/004_deploy_erc20_always_proxied_via_openzeppelin_proxy.ts#L12 -L26

如果您遇到任何问题,请告诉我

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

jsidorenko picture jsidorenko  ·  3评论

lcswillems picture lcswillems  ·  14评论

lepidotteri picture lepidotteri  ·  5评论

smartcontracts picture smartcontracts  ·  20评论

gitpusha picture gitpusha  ·  6评论