Lição: 146: Contratos Proxy e Proxies Mínimos
No desenvolvimento de contratos inteligentes, um desafio comum surge quando precisamos atualizar contratos sem perder estado ou dados. Para alcançar isso, podemos utilizar contratos proxy e proxies mínimos (às vezes referidos como “contratos clone”). Esta aula explicará os conceitos de contratos proxy, como eles funcionam e fornecerá exemplos em Solidity.
Compreendendo Contratos Proxy
Um contrato proxy atua como um intermediário entre os usuários e a lógica do contrato subjacente. Ele delega as chamadas a um contrato de implementação separado enquanto mantém o estado no proxy em si. Essa abordagem nos permite atualizar a lógica do nosso contrato simplesmente mudando o endereço do contrato de implementação, sem alterar o endereço do proxy.
Estrutura de um Contrato Proxy
Um contrato proxy básico geralmente é composto por:
- Um ponteiro para o contrato de lógica (implementação).
- Armazenamento para as variáveis de estado.
Aqui está um exemplo simples de um contrato proxy:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Proxy {
address public implementation;
constructor(address _implementation) {
implementation = _implementation;
}
fallback() external payable {
address impl = implementation;
require(impl != address(0), "Implementação não definida");
assembly {
// Encaminha a chamada para o contrato de implementação
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(0, 0, size)
switch result
case 0 { revert(0, size) }
default { return(0, size) }
}
}
}
Como Funciona
- A variável
implementation
armazena o endereço do contrato de lógica que contém a lógica de negócio real. - A função
fallback()
é acionada quando o proxy recebe uma chamada que não corresponde a nenhuma de suas funções. Ela usadelegatecall
para encaminhar a chamada ao contrato de implementação. - A chamada mantém o contexto (ou seja,
msg.sender
emsg.value
) porquedelegatecall
é utilizado, permitindo a manipulação direta do estado do proxy.
Atualizando a Implementação
Para atualizar o contrato, precisamos apenas mudar o endereço da implementação no proxy. Veja como você pode adicionar uma função de atualização:
function upgradeTo(address newImplementation) external {
// Para segurança, você pode querer adicionar controle de acesso aqui
implementation = newImplementation;
}
Compreendendo Proxies Mínimos
Proxies mínimos são uma versão mais eficiente em termos de gás de contratos proxy. Eles utilizam a opcode CREATE2
e um modelo de bytecode para implantar novos contratos proxy que apontam para a mesma implementação. Isso ajuda a economizar custos de implantação quando a mesma lógica precisa ser reutilizada.
Aqui está como implementar um proxy mínimo:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MinimalProxy {
address public implementation;
constructor(address _implementation) {
implementation = _implementation;
}
fallback() external payable {
address impl = implementation;
require(impl != address(0), "Implementação não definida");
assembly {
// Encaminha a chamada para o contrato de implementação
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(0, 0, size)
switch result
case 0 { revert(0, size) }
default { return(0, size) }
}
}
}
Para implantar proxies mínimos de forma eficiente, você pode usar um contrato de fábrica que gera múltiplos proxies apontando para a mesma implementação.
Fábrica de Proxies Mínimos
Aqui está um exemplo de uma fábrica que cria proxies mínimos:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MinimalProxyFactory {
function createMinimalProxy(address implementation) external returns (address) {
bytes20 targetBytes = bytes20(implementation);
// Cria o proxy mínimo usando o padrão EIP-1167
bytes memory bytecode = abi.encodePacked(
hex"3d602d80600a3d393df3602052",
targetBytes,
hex"5af43d82803e903d91602b57fd5bf3"
);
address proxy;
assembly {
proxy := create(0, add(bytecode, 32), mload(bytecode))
if iszero(proxy) {
revert(0, 0)
}
}
return proxy;
}
}
Partes Principais da Fábrica:
- A função
createMinimalProxy
gera o bytecode para o proxy mínimo utilizando assembly inline. - Ela utiliza a instrução
create
para implantar o contrato proxy mínimo.
Conclusão
Contratos proxy e proxies mínimos são ferramentas poderosas no ecossistema de desenvolvimento de contratos inteligentes Ethereum. Eles permitem que os desenvolvedores mantenham e atualizem contratos com facilidade.
Entender como implementar e gerenciar esses contratos ajudará você a construir aplicações descentralizadas mais flexíveis e robustas. Sempre lembre-se de incorporar mecanismos adequados de controle de acesso em suas funções de atualização para garantir a segurança de seus contratos!