SwiftHTML & CSSSolidityDesenvolvimento de JogosSolana/Rust
26.11.2024

Lição 148: Padrão Diamond (EIP-2535)

Introdução

O Padrão Diamond, também conhecido como EIP-2535, é uma abordagem inovadora na arquitetura de contratos inteligentes que permite a criação de contratos modulares e atualizáveis na blockchain Ethereum. Tradicionalmente, atualizar contratos inteligentes é uma tarefa desafiadora, frequentemente exigindo um planejamento cuidadoso para evitar problemas como perda de dados ou vulnerabilidades de segurança. O Padrão Diamond aborda esses desafios ao permitir que um único contrato, chamado de diamond, suporte múltiplos métodos através de um sistema modular de facetas.

Esta lição irá explorar os conceitos principais do Padrão Diamond, fornecer um exemplo de implementação e demonstrar seus benefícios.

Conceitos Principais

  1. Diamonds: Um diamond é um contrato agregado com múltiplas facetas. Cada faceta contém um conjunto de funções (métodos).

  2. Facetas: Uma faceta é um contrato que implementa funções utilizadas pelo diamond. Múltiplas facetas podem conter diferentes partes da funcionalidade do diamond.

  3. Seletores: Cada função pode ser identificada usando um seletor único, que é um valor de 4 bytes derivado da assinatura da função.

  4. Função de Fallback: O diamond depende de uma função de fallback para delegar chamadas à faceta apropriada.

  5. Armazenamento Diamond: O padrão diamond utiliza uma estrutura de armazenamento compartilhada, permitindo que as facetas leiam e escrevam nas mesmas variáveis de estado.

Exemplo de Implementação

Passo 1: Definir Estruturas de Armazenamento

Vamos configurar uma estrutura de armazenamento diamond que irá conter as informações necessárias sobre as facetas.

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

library DiamondStorage {
    struct Facet {
        address facetAddress;
        bytes4[] functionSelectors;
    }

    struct Diamond {
        mapping(bytes4 => address) facetAddress;
        Facet[] facets;
    }

    function diamondStorage() internal pure returns (Diamond storage ds) {
        assembly {
            ds.slot := 0
        }
    }
}

Passo 2: Criar Facetas

Agora, vamos definir duas facetas que fornecerão funcionalidades para o nosso diamond.

Faceta A

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

import "DiamondStorage.sol";

contract FacetA {
    function functionA() external pure returns (string memory) {
        return "Função A";
    }
}

Faceta B

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

import "DiamondStorage.sol";

contract FacetB {
    function functionB() external pure returns (string memory) {
        return "Função B";
    }
}

Passo 3: Implementar o Diamond

O contrato diamond em si gerenciará as facetas e delegará chamadas a elas.

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

import "DiamondStorage.sol";

contract Diamond {
    constructor(address[] memory _facets, bytes4[][] memory _selectors) {
        require(_facets.length == _selectors.length, "Entradas não correspondem");

        for (uint256 i = 0; i < _facets.length; i++) {
            addFacet(_facets[i], _selectors[i]);
        }
    }

    function addFacet(address _facet, bytes4[] memory _selectors) internal {
        DiamondStorage.Diamond storage ds = DiamondStorage.diamondStorage();

        for (uint256 i = 0; i < _selectors.length; i++) {
            bytes4 selector = _selectors[i];
            require(ds.facetAddress[selector] == address(0), "Função já existe");

            ds.facetAddress[selector] = _facet;
        }

        ds.facets.push(DiamondStorage.Facet({facetAddress: _facet, functionSelectors: _selectors}));
    }

    fallback() external {
        DiamondStorage.Diamond storage ds = DiamondStorage.diamondStorage();
        address facet = ds.facetAddress[msg.sig];

        require(facet != address(0), "Função não existe");
        assembly {
            let result := delegatecall(gas(), facet, add(calldata, 0x20), calldatasize(), 0, 0)
            let size := returndatasize()
            returndatacopy(0, 0, size)

            switch result
            case 0 { revert(0, size) }
            default { return(0, size) }
        }
    }
}

Passo 4: Implantação

Para implantar o diamond, você pode usar um script que configura as facetas e o contrato diamond com os seletores apropriados.

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

import "Diamond.sol";
import "FacetA.sol";
import "FacetB.sol";

// Script de implantação
contract DeployDiamond {
    function deploy() external {
        address facetA = address(new FacetA());
        address facetB = address(new FacetB());

        bytes4[] memory selectorsA = new bytes4[](1);
        selectorsA[0] = FacetA.functionA.selector;

        bytes4[] memory selectorsB = new bytes4[](1);
        selectorsB[0] = FacetB.functionB.selector;

        address[] memory facets = new address[](2);
        facets[0] = facetA;
        facets[1] = facetB;

        bytes4[][] memory selectors = new bytes4[][](2);
        selectors[0] = selectorsA;
        selectors[1] = selectorsB;

        Diamond diamond = new Diamond(facets, selectors);
    }
}

Conclusão

O Padrão Diamond (EIP-2535) introduz uma forma flexível e escalável de gerenciar arquiteturas complexas de contratos inteligentes. Ao dividir a funcionalidade em facetas modulares, os desenvolvedores podem facilmente atualizar e manter contratos sem perder dados ou funcionalidade. Este design é particularmente vantajoso para aplicações que requerem atualizações frequentes ou novos recursos, enquanto ainda garantem a integridade dos dados existentes.

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

Thank you for voting!