Lição: 283: Implementando Proxy UUPS
Introdução
Nesta aula, vamos explorar o padrão de Proxy UUPS (Universal Upgradeable Proxy Standard) em Solidity. Este padrão nos permite implantar um contrato que pode ser atualizado posteriormente, utilizando um proxy para manter uma interface consistente. Os proxies UUPS estão ganhando popularidade devido à sua simplicidade e eficiência em gas quando comparados a padrões de proxy tradicionais.
O que é um Proxy?
Um contrato proxy atua como um intermediário que encaminha chamadas para outro contrato de implementação. Isso nos permite separar a lógica de nossos contratos de seu armazenamento. Essa separação torna possível atualizar a lógica sem afetar o estado armazenado no contrato proxy.
Visão Geral do Proxy UUPS
Os proxies UUPS utilizam um delegatecall para executar funções em um contrato de implementação. O contrato de implementação pode ser atualizado alterando seu endereço no contrato proxy. Na nossa implementação UUPS, usaremos um único contrato que gerencia tanto o proxy quanto a lógica.
Implementando o Proxy UUPS
Passo 1: Criar o Contrato de Lógica
Primeiro, criaremos um contrato de lógica simples que contém uma variável de estado e uma função para modificá-la.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract LogicContract {
uint256 public value;
function setValue(uint256 _value) external {
value = _value;
}
}
Passo 2: Criar o Contrato Proxy UUPS
Em seguida, vamos criar o contrato proxy UUPS. Este contrato será responsável por gerenciar as atualizações do contrato de implementação.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract UUPSProxy {
address private _implementation;
constructor(address implementation) {
_implementation = implementation;
}
function upgradeTo(address newImplementation) external {
// Em uma implementação real, você adicionaria controle de acesso aqui.
_implementation = newImplementation;
}
fallback() external {
address implementation = _implementation;
require(implementation != address(0), "Implementação não definida");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(0, 0, size)
switch result
case 0 { revert(0, size) }
default { return(0, size) }
}
}
}
Passo 3: Atualizando o Contrato de Lógica
Para demonstrar a funcionalidade do proxy, vamos criar uma versão atualizada do LogicContract
.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract UpgradedLogicContract {
uint256 public value;
function setValue(uint256 _value) external {
value = _value;
}
function incrementValue() external {
value += 1;
}
}
Passo 4: Implantação e Interação
Agora, vamos implantar e interagir com nossos contratos.
- Implante o
LogicContract
. - Implante o
UUPSProxy
com o endereço doLogicContract
implantado. - Chame
setValue
através do proxy para definir ovalue
. - Implante o
UpgradedLogicContract
. - Chame
upgradeTo
no proxy para atualizar o endereço da implementação para o novo contrato de lógica. - Chame
incrementValue
através do proxy para incrementar o valor.
Exemplo de Interação
Aqui está um exemplo utilizando JavaScript (por exemplo, com Hardhat ou Truffle):
const { ethers } = require("hardhat");
async function main() {
// Implante o LogicContract
const Logic = await ethers.getContractFactory("LogicContract");
const logic = await Logic.deploy();
await logic.deployed();
console.log("LogicContract implantado em:", logic.address);
// Implante o UUPSProxy
const Proxy = await ethers.getContractFactory("UUPSProxy");
const proxy = await Proxy.deploy(logic.address);
await proxy.deployed();
console.log("UUPSProxy implantado em:", proxy.address);
// Defina o valor através do proxy
const proxyLogic = await ethers.getContractAt("LogicContract", proxy.address);
await proxyLogic.setValue(42);
console.log("Valor definido para 42 através do proxy.");
// Implante o LogicContract atualizado
const UpgradedLogic = await ethers.getContractFactory("UpgradedLogicContract");
const upgradedLogic = await UpgradedLogic.deploy();
await upgradedLogic.deployed();
console.log("UpgradedLogicContract implantado em:", upgradedLogic.address);
// Atualize o proxy para o novo contrato de lógica
await proxy.upgradeTo(upgradedLogic.address);
console.log("Proxy atualizado para UpgradedLogicContract.");
// Chame incrementValue através do proxy
await proxyLogic.incrementValue();
const value = await proxyLogic.value();
console.log("Valor após a incrementação:", value.toString());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Conclusão
Nesta aula, implementamos um contrato proxy UUPS em Solidity. Criamos um contrato de lógica, um contrato proxy e uma versão atualizada do contrato de lógica. O padrão de proxy UUPS nos permite atualizar nossos contratos enquanto mantemos uma interface consistente, tornando mais fácil gerenciar e expandir nossas aplicações descentralizadas.