SwiftHTML & CSSSolidityDesenvolvimento de JogosSolana/Rust
10.12.2024

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.

  1. Implante o LogicContract.
  2. Implante o UUPSProxy com o endereço do LogicContract implantado.
  3. Chame setValue através do proxy para definir o value.
  4. Implante o UpgradedLogicContract.
  5. Chame upgradeTo no proxy para atualizar o endereço da implementação para o novo contrato de lógica.
  6. 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.

Video

Did you like this article? Rate it from 1 to 5:

Thank you for voting!