Hardhat-deploy: Pregunta sobre la implementación de contratos actualizables

Creado en 8 jun. 2021  ·  9Comentarios  ·  Fuente: wighawag/hardhat-deploy

¿Cuál es la forma correcta de implementar contratos actualizables de OpenZeppelin?
OpenZeppelin upgrdable tiene una función de inicialización () predeterminada que es llamada por el complemento de actualizaciones de casco.
No hay una llamada automática desde hardhat-deploy, solo se puede llamar al método posterior a la actualización.

¿Debo implementar los contratos primero a través de los scripts en la carpeta de implementación y luego llamar a las funciones de inicialización desde algún script fuera de la carpeta de implementación? ¿O hay alguna forma de incorporar la llamada a la función initialize() desde la propia carpeta de implementación?

Comentario más útil

Vuelva a inicializar la función para proxies

Agregué nuevas opciones (en lugar de methodName que aún está disponible) en hardhat-deploy 0.8.0

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

Avísame si tienes algún problema.

Todos 9 comentarios

La forma en que hardhat-deploy considera implementar el script de forma predeterminada es que son idempotentes. Lo mismo se aplica a la implementación de proxy. Esto lo hace agradable para el desarrollo/implementación, ya que solo necesita pensar en qué estado desea estar y hardhat-deploy hará todo lo posible para alcanzar ese estado,

Entonces, para responder a su pregunta, lo que generalmente hago es hacer que mi función de inicialización también sea idempotente, así que en lugar de lanzarla, simplemente omita si ya está inicializada. Por lo tanto, puedo agregar esa función como la función postUpgrade y todo funciona bien. En desarrollo, incluso puedo cambiar la función de inicialización para hacer cosas cuando actualizo si es necesario.

Para escenarios más complejos, puede organizar cada actualización en un script de implementación independiente.

Hay una característica no documentada que está sujeta a cambios: el parámetro upgradeIndex que le permite especificar el script de implementación que debe ejecutarse en orden.

Esto es excelente para la prueba, ya que puede probar su proxy en cualquier punto entre el historial de actualización y asegurarse de que todo funcione.

Gracias por su respuesta rápida.
Parece que me las arreglé para que todo funcionara sin problemas. Uso deploy() y luego execute con la función initialize del contrato base de OpenZeppelin Initializable . El execute() devuelve verdadero y esto hace que no se ejecute cuando actualizo los contratos:

// 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'];

y luego

// 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';

¿Se puede considerar este método como una _buena práctica_? ¿Es como lo buscaste?

Como mencioné anteriormente, en SkinRewards heredo del contrato Initializable de OpenZeppelin que define la función initialize() que se llamará primero. Así que no veo cómo puedo ejecutarlo después de la actualización. ¿Puedes explicar? Gracias

Eso suena bien, la desventaja en comparación con el uso de methodName ("postUpgrade") es que está ejecutando 2 tx en lugar de 1

Si hace que su función de inicialización sea idempotente, es decir, se puede llamar varias veces sin lanzar asegurándose de que la llamada adicional no modifique el estado (simplemente omitiendo si ya se ha inicializado), entonces puede usarla a través de las opciones methodName . la primera implementación también lo ejecutará.

Tenga en cuenta que debido a que usa argumentos y la función de implementación está configurada para que simplemente pueda desactivar la opción de proxy y tener un contrato inmutable que funcione, la opción methodName usa las opciones args para el argumento.
Esto significa que el constructor de la implementación del proxy también necesita tener estos argumentos. Si nunca tiene la intención de utilizar el contrato sin poder, simplemente no puede hacer nada con los argumentos.

En realidad, por conveniencia, si el contrato de implementación tiene una construcción de cero argumentos, ignora al constructor por completo, pero no estoy seguro de que sea una buena idea.

Aquí estaría el script con la opción 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'];

_Si hace que su función de inicialización sea idempotente, es decir, se puede llamar varias veces sin lanzar asegurándose de que la llamada adicional no modifique el estado (simplemente omitiendo si ya se ha inicializado), entonces puede usarla a través de las opciones de nombre de método. la primera implementación también lo ejecutará._

Esto significa que no podemos usar la función initialize() TAL CUAL de https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/release-v4.0/contracts/proxy/utils/Initializable.sol .

@freebyte de hecho

Consideraré agregar una opción para obtener este tipo de comportamiento.

¡Genial, gracias!

Otra pregunta es sobre el uso de contratos implementados. Particularmente, estoy preguntando sobre Uniswap .

De esta parte de los documentos del complemento, no veo cómo proporciono la dirección del contrato Uniswap implementado. Uniswap NPM viene con la carpeta build con los artefactos, pero ¿cómo hago para que el complemento sepa sobre el contrato en una dirección específica? No queremos usar éteres en el script de implementación, ¿verdad?

El siguiente código NO es lo que queremos en el script de implementación:

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...

¿Cómo obtengo la implementación de Uniswap en deployments var?

para contratos ya implementados, lo mejor es crear archivos para ellos en la carpeta de implementaciones correspondiente
así que para mainnet uniswap y suponiendo que tiene una red mainnet configurada en hardhat.config.js, puede crear el archivo "deployments/mainnet/UniswapFactory.json". Como mínimo necesita una dirección y un campo abi

entonces puede acceder a él a través de implementaciones cuando esté en esa red

El enlace del documento que señala le permite acceder a los artefactos (por lo tanto, la carpeta de compilación uniswap) o implementar el script, pero este último solo funciona si el proyecto en cuestión expuso estos scripts de implementación en alguna parte

Lo hago en mi propio proyecto, como https://github.com/wighawag/universal-forwarder , que permite al usuario simplemente instalar el paquete npm e implementar todo el proyecto en su prueba o en su propia red. Pero Uniswap no proporciona eso.

Estupendo. Muchas gracias.

Vuelva a inicializar la función para proxies

Agregué nuevas opciones (en lugar de methodName que aún está disponible) en hardhat-deploy 0.8.0

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

Avísame si tienes algún problema.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

smartcontracts picture smartcontracts  ·  20Comentarios

gitpusha picture gitpusha  ·  6Comentarios

gitpusha picture gitpusha  ·  10Comentarios

tennox picture tennox  ·  4Comentarios

jaypaik picture jaypaik  ·  13Comentarios