Lição: 147: Padrões de Contrato Atualizáveis
No mundo dos contratos inteligentes do Ethereum, a capacidade de atualizar seus contratos após a implantação é crucial. Esta lição irá explorar vários padrões para criar contratos atualizáveis em Solidity.
Por que Contratos Atualizáveis?
Os contratos inteligentes são imutáveis por natureza, o que significa que, uma vez implantados, seu código não pode ser alterado. Isso representa desafios significativos caso sejam encontrados bugs ou se você precisar introduzir novas funcionalidades. Contratos atualizáveis permitem que os desenvolvedores contornem essa limitação ao permitir que eles mudem as implementações, mantendo o mesmo endereço do contrato.
Padrões de Contrato Atualizáveis
Existem vários padrões para criar contratos atualizáveis, incluindo:
- Padrão Proxy
- EIP-1967
- Padrão Proxy Transparente
Vamos dar uma olhada mais de perto em cada padrão.
1. Padrão Proxy
O Padrão Proxy envolve a criação de um contrato (o proxy) que delega chamadas para outro contrato (a implementação). Isso permite que você mude o contrato de implementação sem mudar o proxy.
Aqui está um exemplo simples:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Implementacao {
uint public valor;
function definirValor(uint _valor) public {
valor = _valor;
}
}
contract Proxy {
address public implementacao;
constructor(address _implementacao) {
implementacao = _implementacao;
}
fallback() external {
(bool sucesso, ) = implementacao.delegatecall(msg.data);
require(sucesso, "Falha na chamada delegada");
}
}
Neste exemplo, o contrato Proxy
delega todas as chamadas para o contrato Implementacao
utilizando delegatecall
. Se quisermos atualizar o contrato, simplesmente implantamos uma nova implementação e atualizamos o endereço de implementacao
no Proxy
.
2. EIP-1967
O EIP-1967 introduz um padrão para contratos proxy atualizáveis. Ele especifica certos slots de armazenamento para armazenar o endereço da implementação. Isso ajuda a evitar problemas como colisões de armazenamento.
Aqui está uma ilustração simples:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Exemplificacao {
uint public valor;
function definirValor(uint _valor) public {
valor = _valor;
}
}
contract Proxy {
bytes32 private constant _SLOT_IMPLEMENTACAO =
keccak256("eip1967.proxy.implementacao") - 1;
constructor(address _implementacao) {
require(_implementacao != address(0), "Endereço de implementação inválido");
assembly {
sstore(_SLOT_IMPLEMENTACAO, _implementacao)
}
}
function _implementacao() internal view returns (address impl) {
assembly {
impl := sload(_SLOT_IMPLEMENTACAO)
}
}
fallback() external {
address _impl = _implementacao();
require(_impl != address(0), "Implementação não definida");
assembly {
let resultado := delegatecall(gas(), _impl, add(calldataload(0), 0x20), calldatasize(), 0, 0)
let tamanho := returndatasize()
returndatacopy(0, 0, tamanho)
switch resultado
case 0 { revert(0, tamanho) }
default { return(0, tamanho) }
}
}
}
Esse contrato utiliza keccak256
para criar um slot único para o endereço da implementação, seguindo os padrões do EIP-1967. Isso proporciona melhor segurança e previne colisões indesejadas.
3. Padrão Proxy Transparente
O Padrão Proxy Transparente é uma variação que separa os papéis de usuários e administradores. Usuários comuns interagem com o proxy para a implementação atual, enquanto administradores podem atualizar o contrato.
Aqui está uma implementação básica:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ProxyTransparente {
address public implementacao;
address public admin;
constructor(address _implementacao) {
implementacao = _implementacao;
admin = msg.sender;
}
modifier seAdmin() {
require(msg.sender == admin, "Não é um administrador");
_;
}
function atualizar(address _novaImplementacao) external seAdmin {
implementacao = _novaImplementacao;
}
fallback() external {
(bool sucesso, ) = implementacao.delegatecall(msg.data);
require(sucesso, "Falha na chamada delegada");
}
}
No ProxyTransparente
, apenas o administrador pode atualizar a implementação. Esse padrão aumenta a segurança garantindo que apenas partes confiáveis possam realizar atualizações.
Conclusão
A atualizabilidade em contratos inteligentes é um aspecto essencial do desenvolvimento no Ethereum. Ao usar padrões como Proxy, EIP-1967 e Proxy Transparente, os desenvolvedores podem criar sistemas que podem evoluir ao longo do tempo sem perder seu estado ou exigir uma reimplantação completa.
Ao implementar contratos atualizáveis, sempre considere cuidadosamente as implicações de segurança e siga as melhores práticas para evitar armadilhas comuns em interações complexas de contratos.