SwiftHTML & CSSSolidityDesenvolvimento de JogosSolana/Rust
03.12.2024

Aula 211: Contratos Atualizáveis com OpenZeppelin

No mundo da blockchain e dos contratos inteligentes, uma das dificuldades que os desenvolvedores enfrentam é a imutabilidade dos contratos já implantados. Uma vez que um contrato inteligente é implantado na blockchain Ethereum, seu código não pode ser alterado. Isso representa um problema ao tentar corrigir erros ou adicionar novas funcionalidades. Os contratos atualizáveis resolvem esse problema, permitindo que os desenvolvedores mudem a lógica de um contrato enquanto mantêm o mesmo endereço e estado.

Nesta aula, vamos explorar como implementar contratos atualizáveis usando a biblioteca OpenZeppelin, que fornece uma estrutura robusta para criar contratos inteligentes seguros e atualizáveis.

Pré-requisitos

Antes de mergulhar nos contratos atualizáveis, garanta que você tenha um entendimento sólido dos seguintes tópicos:

  • Sintaxe básica do Solidity
  • Desenvolvimento de contratos inteligentes
  • A blockchain Ethereum
  • A biblioteca OpenZeppelin

Você pode instalar os Contratos OpenZeppelin via npm:

npm install @openzeppelin/contracts

Além disso, você precisa instalar o plugin de Atualizações do OpenZeppelin:

npm install --save-dev @openzeppelin/hardhat-upgrades

O Que São Contratos Atualizáveis?

Os contratos atualizáveis se baseiam em um padrão de proxy, que desacopla o armazenamento de dados do contrato de sua lógica. Quando você chama uma função em um contrato, está, na verdade, interagindo com um contrato proxy que encaminha as chamadas para o contrato lógico. Isso permite que você atualize o contrato lógico sem mudar seu armazenamento ou endereço.

Implementando Contratos Atualizáveis

Vamos criar um contrato contador simples que pode ser atualizado posteriormente.

Passo 1: Criando o Contrato Lógico Inicial

Crie um arquivo chamado Counter.sol e escreva o seguinte código:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/proxy/utils/Initializable.sol";

contract Counter is Initializable {
    uint256 public count;

    function initialize() public initializer {
        count = 0;
    }

    function increment() public {
        count += 1;
    }

    function getCount() public view returns (uint256) {
        return count;
    }
}

Passo 2: Implantando o Contrato Usando Proxy

Agora, crie um script de implantação deploy.js que use Hardhat e o plugin de Atualizações do OpenZeppelin. Este script irá implantar o contrato usando um proxy.

No seu deploy.js, adicione o seguinte código:

// deploy.js
const { ethers, upgrades } = require("hardhat");

async function main() {
    const Counter = await ethers.getContractFactory("Counter");
    console.log("Implantando o Contador...");
    const instance = await upgrades.deployProxy(Counter, { initializer: 'initialize' });
    await instance.deployed();
    console.log("Contador implantado em:", instance.address);
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });

Passo 3: Atualizando o Contrato Lógico

Agora, vamos criar uma versão atualizada do contrato Contador que pode redefinir a contagem. Crie um novo arquivo chamado CounterV2.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./Counter.sol";

contract CounterV2 is Counter {

    function reset() public {
        count = 0;
    }
}

Neste código, estamos introduzindo uma nova função reset que permite redefinir a count para zero.

Passo 4: Implantando o Contrato Atualizado

Adicione outro script de implantação chamado upgrade.js para gerenciar a atualização:

// upgrade.js
const { ethers, upgrades } = require("hardhat");

async function main() {
    const counterAddress = "SEU_ENDEREÇO_DO_CONTRATO_IMPLANTADO"; // Substitua pelo endereço real
    const CounterV2 = await ethers.getContractFactory("CounterV2");
    console.log("Atualizando o Contador...");
    await upgrades.upgradeProxy(counterAddress, CounterV2);
    console.log("Contador atualizado.");
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });

Passo 5: Testando a Atualização

Certifique-se de testar seus contratos. Você pode fazer isso escrevendo um teste simples com Mocha, Chai ou qualquer outro framework de testes que preferir. Aqui está um exemplo de como você pode testá-lo:

const { expect } = require("chai");
const { ethers, upgrades } = require("hardhat");

describe("Counter", function () {
    let counter;
    let CounterV2;

    beforeEach(async function () {
        const Counter = await ethers.getContractFactory("Counter");
        counter = await upgrades.deployProxy(Counter, { initializer: 'initialize' });
        await counter.deployed();

        CounterV2 = await ethers.getContractFactory("CounterV2");
    });

    it("deve incrementar a contagem", async function () {
        await counter.increment();
        expect(await counter.getCount()).to.equal(1);
    });

    it("deve ser capaz de atualizar", async function () {
        await upgrades.upgradeProxy(counter.address, CounterV2);
        await counter.reset();
        expect(await counter.getCount()).to.equal(0);
    });
});

Conclusão

Nesta aula, aprendemos a implementar contratos atualizáveis usando a biblioteca OpenZeppelin. Criamos um contrato contador simples que pode ser atualizado para uma nova implementação enquanto preserva o estado existente. Esta é apenas uma forma de lidar com atualizações de contratos, e a OpenZeppelin oferece várias ferramentas e padrões que você pode aproveitar em seus próprios projetos.

Lembre-se de que, embora os contratos atualizáveis forneçam uma maneira poderosa de gerenciar mudanças em seus contratos inteligentes, eles também introduzem complexidade e potenciais vulnerabilidades de segurança. Sempre garanta que seus contratos sejam devidamente testados e auditados.

Video

Did you like this article? Rate it from 1 to 5:

Thank you for voting!