Recentemente, tenho revisitado o estudo de solidity para consolidar os detalhes e escrever um "WTF Solidity Simplificado", destinado aos iniciantes (os profissionais de programação podem buscar outros tutoriais). Atualizo de 1 a 3 aulas por semana.
Twitter: @0xAA_Science
Comunidade: Discord | Grupo no WeChat | Site oficial wtf.academy
Todo o código e tutoriais são disponibilizados no GitHub: github.com/AmazingAng/WTFSolidity
Nesta aula, vamos falar sobre contratos atualizáveis (Upgradeable Contracts). O contrato utilizado neste tutorial é uma simplificação de contratos da OpenZeppelin
e pode apresentar problemas de segurança, portanto, não deve ser utilizado em ambiente de produção.
Se você entendeu o conceito de contrato de proxy, será fácil compreender o contrato atualizável. Ele é um contrato de proxy que pode alterar o contrato lógico.
A seguir, vamos implementar um contrato atualizável simples, que inclui 3
contratos: contrato de proxy, contrato lógico antigo e contrato lógico novo.
Este contrato de proxy é mais simples do que o apresentado na aula anterior. Neste caso, não utilizamos assembly inline
no método fallback()
, mas simplesmente implementation.delegatecall(msg.data);
. Portanto, a função de retorno não possui valor, mas é suficiente para fins educacionais.
Ele possui 3
variáveis:
implementation
: endereço do contrato lógico.admin
: endereço do admin.words
: string que pode ser alterada por meio de funções do contrato lógico.
Ele possui 3
funções:
- Construtor: inicializa o admin e o endereço do contrato lógico.
fallback()
: função de fallback, que delega a chamada para o contrato lógico.upgrade()
: função de atualização que altera o endereço do contrato lógico e só pode ser chamada peloadmin
.
// SPDX-License-Identifier: MIT
// wtf.academy
pragma solidity ^0.8.21;
// Contrato atualizável simples, no qual o admin pode alterar o endereço do contrato lógico usando a função de atualização, modificando assim a lógica do contrato.
// Apenas para fins educacionais, não deve ser utilizado em ambiente de produção.
contract SimpleUpgrade {
address public implementation; // Endereço do contrato lógico
address public admin; // Endereço do admin
string public words; // String que pode ser alterada por meio de funções do contrato lógico
// Construtor, inicializa o admin e o endereço do contrato lógico
constructor(address _implementation){
admin = msg.sender;
implementation = _implementation;
}
// Função fallback, delega a chamada para o contrato lógico
fallback() external payable {
(bool success, bytes memory data) = implementation.delegatecall(msg.data);
}
// Função de atualização, altera o endereço do contrato lógico e só pode ser chamada pelo admin
function upgrade(address newImplementation) external {
require(msg.sender == admin);
implementation = newImplementation;
}
}
Este contrato lógico possui 3
variáveis de estado, mantendo a consistência com o contrato de proxy e evitando conflito de slots. Ele possui apenas a função foo()
, que altera o valor da variável words
do contrato de proxy para "old"
.
// Contrato lógico 1
contract Logic1 {
// Variáveis de estado que coincidem com as do contrato de proxy, evitando conflito de slots
address public implementation;
address public admin;
string public words; // String que pode ser alterada por meio de funções do contrato lógico
// Altera a variável de estado do contrato de proxy, seletor: 0xc2985578
function foo() public{
words = "old";
}
}
Este contrato lógico também possui 3
variáveis de estado, mantendo a consistência com o contrato de proxy. Ele possui apenas a função foo()
, que altera o valor da variável words
do contrato de proxy para "new"
.
// Contrato lógico 2
contract Logic2 {
// Variáveis de estado que coincidem com as do contrato de proxy, evitando conflito de slots
address public implementation;
address public admin;
string public words; // String que pode ser alterada por meio de funções do contrato lógico
// Altera a variável de estado do contrato de proxy, seletor: 0xc2985578
function foo() public{
words = "new";
}
}
- Implante os contratos lógicos antigos e novos,
Logic1
eLogic2
. - Implante o contrato atualizável
SimpleUpgrade
e defina o endereço deimplementation
para o contrato lógico antigo. - Utilize o seletor
0xc2985578
para chamar a funçãofoo()
do contrato lógico antigoLogic1
no contrato de proxy, alterando o valor dewords
para"old"
. - Chame a função
upgrade()
para definir o endereço deimplementation
para o contrato lógico novoLogic2
. - Utilize o seletor
0xc2985578
para chamar a funçãofoo()
do contrato lógico novoLogic2
no contrato de proxy, alterando o valor dewords
para"new"
.
Este tutorial apresentou um contrato atualizável simples, que adiciona a funcionalidade de atualização a contratos inteligentes que normalmente não são alteráveis. No entanto, este contrato possui um problema de conflito de seletores
, representando um risco de segurança. Nas próximas aulas, iremos abordar os contratos atualizáveis padrão, como o proxy transparente e o UUPS
.