SwiftHTML & CSSSolidityDesenvolvimento de JogosSolana/Rust
21.11.2024

Aula 092: Layout de Armazenamento em Contratos Atualizáveis

No mundo do desenvolvimento Ethereum, contratos atualizáveis são um padrão de design essencial para manter e evoluir contratos inteligentes ao longo do tempo. No entanto, gerenciar o layout de armazenamento em contratos atualizáveis requer planejamento cuidadoso para evitar corrupção de dados e garantir a funcionalidade adequada durante as atualizações. Nesta aula, vamos explorar o conceito de layout de armazenamento em contratos atualizáveis, fornecendo exemplos práticos de código usando Solidity.

Entendendo o Layout de Armazenamento

Em Solidity, o layout de armazenamento refere-se a como as variáveis são armazenadas na blockchain Ethereum. Cada variável de estado em um contrato é atribuída a um slot exclusivo no armazenamento. Ao atualizar um contrato, é crucial manter um layout de armazenamento consistente; caso contrário, as variáveis podem acabar apontando para os slots de armazenamento errados, levando à corrupção de dados.

Versionamento e Layout de Armazenamento

Quando você atualiza um contrato, geralmente cria uma nova implementação de contrato. Esta nova implementação deve ter o mesmo layout de armazenamento que a anterior. Aqui estão algumas regras a seguir:

  • Mantenha a ordem das variáveis de estado a mesma entre as versões.
  • Evite remover variáveis de estado a menos que tenha certeza de que isso não afetará o layout.
  • Use slots de armazenamento vazios com cautela, especialmente em versões futuras.

Estrutura Básica de Contratos Atualizáveis

A abordagem típica para implementar contratos atualizáveis é utilizar um padrão de proxy. O proxy delega chamadas para um contrato de implementação, permitindo que a implementação mude enquanto o proxy mantém o mesmo endereço.

Exemplo: Configuração Básica com Proxy

Aqui está uma implementação básica de um contrato atualizável usando o padrão proxy.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// O contrato proxy
contract Proxy {
    address internal implementation;

    constructor(address _implementation) {
        implementation = _implementation;
    }

    fallback() external {
        assembly {
            let ptr := mload(0x40) // Ponteiro de memória livre
            calldatacopy(ptr, 0, calldatasize()) // Copiar calldata
            let result := delegatecall(gas(), sload(implementation), ptr, calldatasize(), 0, 0) // Chamada delegada
            let size := returndatasize() // Obter o tamanho dos dados de retorno
            returndatacopy(ptr, 0, size) // Copiar dados de retorno
            switch result
            case 0 { revert(ptr, size) } // Reverter se a chamada delegada falhar
            default { return(ptr, size) } // Retornar o resultado da chamada delegada
        }
    }
}

// O contrato de implementação inicial
contract ImplementationV1 {
    uint public number;

    function setNumber(uint _number) public {
        number = _number;
    }
}

Atualizando a Implementação

Quando você deseja atualizar seu contrato, implanta um novo contrato de implementação e altera o endereço no proxy.

// O contrato de implementação atualizado
contract ImplementationV2 {
    uint public number;
    uint public newVariable; // Nova variável adicionada

    function setNumber(uint _number) public {
        number = _number;
    }

    function setNewVariable(uint _newVariable) public {
        newVariable = _newVariable;
    }
}

Gerenciando o Layout de Armazenamento

Agora, vamos nos aprofundar no layout de armazenamento. Suponha que queremos manter o mesmo layout enquanto adicionamos novas variáveis mais tarde. É crucial manter as variáveis existentes na mesma ordem.

Exemplo: Reestruturação para Manter o Layout de Armazenamento

Suponha que o contrato ImplementationV1 é o seguinte:

// O contrato de implementação inicial
contract ImplementationV1 {
    uint public number; // slot 0
    // Nova variável deve ser adicionada ao final
}

Ao definir ImplementationV2, mantenha a ordem das variáveis existentes:

// O contrato de implementação atualizado
contract ImplementationV2 {
    uint public number; // slot 0 (mesmo que ImplementationV1)
    uint public newVariable; // slot 1 (nova variável)
}

Usando este padrão, quando você atualizar, o proxy ainda apontará para as versões corretas de armazenamento. Se as variáveis forem embaralhadas, a lógica da aplicação pode falhar.

Conclusão

Manter o layout de armazenamento em contratos atualizáveis é essencial para garantir que suas aplicações possam evoluir sem perder dados ou corromper o estado. Seguir uma abordagem estruturada e gerenciar cuidadosamente a ordem e a alocação das variáveis de estado ajudará você a criar contratos atualizáveis robustos.

Nesta aula, cobrimos a importância do layout de armazenamento em contratos atualizáveis, uma implementação básica do padrão proxy e como gerenciar atualizações mantendo um estado consistente. Ao entender esses princípios, você pode desenvolver aplicações Ethereum mais inteligentes, adaptáveis e resilientes.

Video

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

Thank you for voting!