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.