image

Acesse bootcamps ilimitados e +750 cursos pra sempre

70
%OFF
Article image
Carlos Pinheiro
Carlos Pinheiro07/07/2026 12:51
Compartilhe

EIP-2535: Diamond, a arquitetura de Smart Contracts modulares

    Quando começamos a desenvolver Smart Contracts mais complexos, uma limitação aparece rapidamente: contratos grandes demais ficam difíceis de manter, testar, auditar e evoluir. Além disso, na Ethereum existe um limite prático de tamanho para o bytecode de um contrato implantado.

    O EIP-2535, também conhecido como Diamond Standard, propõe uma arquitetura para resolver esse problema usando um contrato central chamado Diamond e vários contratos auxiliares chamados Facets. A proposta oficial descreve Diamonds como sistemas modulares de Smart Contracts que podem ser estendidos ou atualizados após o deploy e que não possuem, na prática, a mesma limitação de tamanho de um contrato monolítico comum. (Ethereum Improvement Proposals)

    A ideia central

    O Diamond funciona como um Proxy Central.

    Ele recebe as chamadas externas dos usuários e encaminha cada função para o contrato correto. Esse encaminhamento é feito usando delegatecall, ou seja, a lógica executa no contrato Facet, mas usando o armazenamento do Diamond.

    De forma simplificada:

    image

    Cada Facet contém um conjunto de funções. O Diamond guarda um mapeamento dizendo qual função pertence a qual Facet.

    Por que o nome Diamond?

    A metáfora é interessante: um diamante possui várias faces. Cada face representa uma parte visível do mesmo objeto.

    No EIP-2535:

    • O Diamond é o contrato principal.
    • As Facets são as “faces” funcionais.
    • O usuário interage com um único endereço.
    • Internamente, cada função pode estar em um contrato diferente.

    Isso permite que um projeto tenha apenas um endereço principal, mas com uma arquitetura dividida em módulos.

    O problema dos contratos monolíticos

    Imagine um contrato que mistura:

    contract MegaContrato {
      // lógica de token
      // lógica de governança
      // lógica de staking
      // lógica de permissões
      // lógica de marketplace
      // lógica de upgrade
    }
    

    Com o tempo, esse contrato fica difícil de entender. Qualquer alteração pode afetar partes não relacionadas. Além disso, o deploy pode esbarrar no limite de tamanho do contrato.

    Com o Diamond, podemos dividir:

    TokenFacet
    GovernanceFacet
    StakingFacet
    MarketplaceFacet
    AdminFacet
    

    A aplicação continua parecendo um único contrato para o usuário, mas internamente ela é modular.

    Como o Diamond sabe para onde enviar cada chamada?

    Cada função em Solidity possui um function selector, que são os 4 primeiros bytes do hash da assinatura da função.

    Por exemplo:

    balanceOf(address)
    transfer(address,uint256)
    mint(address,uint256)
    

    Cada uma dessas assinaturas gera um seletor diferente.

    O Diamond mantém uma tabela semelhante a esta:

    selector de transfer()  -> endereço da TokenFacet
    selector de vote()      -> endereço da GovernanceFacet
    selector de stake()     -> endereço da StakingFacet
    

    Quando alguém chama uma função no Diamond, o fallback do contrato verifica o seletor da função e descobre qual Facet deve executar aquela lógica.

    Exemplo simplificado de um Diamond

    Abaixo está um exemplo conceitual. Ele não é uma implementação completa de produção, mas ajuda a entender o mecanismo.

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    
    contract SimpleDiamond {
      mapping(bytes4 => address) public facets;
    
      address public owner;
    
      constructor() {
          owner = msg.sender;
      }
    
      function setFacet(bytes4 _selector, address _facet) external {
          require(msg.sender == owner, "Apenas o owner");
          facets[_selector] = _facet;
      }
    
      fallback() external payable {
          address facet = facets[msg.sig];
    
          require(facet != address(0), "Funcao nao encontrada");
    
          assembly {
              calldatacopy(0, 0, calldatasize())
    
              let result := delegatecall(
                  gas(),
                  facet,
                  0,
                  calldatasize(),
                  0,
                  0
              )
    
              returndatacopy(0, 0, returndatasize())
    
              switch result
              case 0 {
                  revert(0, returndatasize())
              }
              default {
                  return(0, returndatasize())
              }
          }
      }
    }
    

    Esse contrato faz três coisas principais:

    1. Guarda qual seletor de função aponta para qual Facet.
    2. Recebe chamadas no fallback.
    3. Encaminha a execução para a Facet correta usando delegatecall.

    Exemplo de uma Facet

    Agora imagine uma Facet simples responsável por armazenar e retornar um número:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    
    contract NumberFacet {
      uint256 public number;
    
      function setNumber(uint256 _number) external {
          number = _number;
      }
    
      function getNumber() external view returns (uint256) {
          return number;
      }
    }
    

    À primeira vista, parece um contrato comum. Mas quando ele é chamado via Diamond, o armazenamento usado não é o da Facet. É o armazenamento do Diamond.

    Esse detalhe é essencial.

    Delegatecall: o coração da arquitetura

    O delegatecall permite executar o código de outro contrato preservando o contexto do contrato chamador.

    Em outras palavras:

    Código vem da Facet.
    Storage vem do Diamond.
    msg.sender continua sendo o usuário original.
    

    Isso é poderoso, mas também perigoso se o layout de armazenamento for mal planejado.

    Por isso, implementações reais de Diamond normalmente usam padrões de armazenamento específicos, como o Diamond Storage, onde cada módulo armazena seus dados em uma posição bem definida da storage.

    Exemplo de Diamond Storage

    Uma forma comum de evitar conflitos de storage é usar uma biblioteca com uma posição fixa:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    
    library LibAppStorage {
      bytes32 constant STORAGE_POSITION =
          keccak256("diamond.standard.example.storage");
    
      struct AppStorage {
          uint256 number;
          address admin;
      }
    
      function appStorage()
          internal
          pure
          returns (AppStorage storage s)
      {
          bytes32 position = STORAGE_POSITION;
    
          assembly {
              s.slot := position
          }
      }
    }
    

    Agora a Facet pode usar essa storage compartilhada:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    
    import "./LibAppStorage.sol";
    
    contract NumberFacet {
      function setNumber(uint256 _number) external {
          LibAppStorage.AppStorage storage s = LibAppStorage.appStorage();
          s.number = _number;
      }
    
      function getNumber() external view returns (uint256) {
          LibAppStorage.AppStorage storage s = LibAppStorage.appStorage();
          return s.number;
      }
    }
    

    Esse padrão evita que uma Facet sobrescreva dados de outra por acidente.

    diamondCut: adicionando, trocando e removendo funções

    O EIP-2535 define uma função importante chamada diamondCut. Ela é usada para adicionar, substituir ou remover funções de um Diamond. A especificação oficial indica que o diamondCut gerencia os seletores de funções e os endereços das Facets dentro do storage do Diamond. (Ethereum Improvement Proposals)

    Conceitualmente, seria algo assim:

    enum FacetCutAction {
      Add,
      Replace,
      Remove
    }
    
    struct FacetCut {
      address facetAddress;
      FacetCutAction action;
      bytes4[] functionSelectors;
    }
    

    Exemplo conceitual de uso:

    diamondCut([
      FacetCut({
          facetAddress: address(tokenFacet),
          action: FacetCutAction.Add,
          functionSelectors: selectors
      })
    ]);
    

    Na prática, isso permite que o projeto evolua sem precisar trocar o endereço principal do contrato.

    Loupe: inspecionando o Diamond

    Outro conceito importante do EIP-2535 é o conjunto de funções chamado Diamond Loupe.

    “Loupe” é uma lupa usada para examinar diamantes. No padrão, essas funções permitem consultar quais Facets e funções existem no Diamond.

    Exemplos de perguntas que o Loupe responde:

    Quais Facets existem neste Diamond?
    Quais funções pertencem a uma Facet?
    Qual Facet executa determinado seletor?
    

    Isso melhora a transparência, porque usuários, exploradores de bloco e ferramentas de auditoria podem inspecionar a composição do Diamond.

    Comparação com proxies tradicionais

    Em muitos proxies tradicionais, como Transparent Proxy ou UUPS, normalmente existe:

    Proxy -> Implementação
    

    Já no Diamond temos:

    Diamond -> Facet A
    Diamond -> Facet B
    Diamond -> Facet C
    Diamond -> Facet D
    

    A RareSkills resume bem essa diferença: o Diamond Pattern usa múltiplos contratos de implementação ao mesmo tempo, enquanto padrões como Transparent Proxy e UUPS geralmente apontam para uma implementação principal por vez. (rareskills.io)

    Vantagens do EIP-2535

    A primeira grande vantagem é a modularidade. Cada Facet pode representar uma parte específica do sistema.

    A segunda é a escalabilidade do código. Como a lógica fica distribuída em várias Facets, o sistema consegue crescer sem concentrar tudo em um único contrato.

    A terceira é a atualização seletiva. Em vez de substituir uma implementação inteira, é possível trocar apenas uma Facet ou até funções específicas.

    A quarta é a organização arquitetural. Projetos grandes podem separar melhor as responsabilidades, facilitando testes, auditoria e manutenção.

    Riscos e cuidados

    Apesar das vantagens, o Diamond não deve ser usado sem planejamento.

    O primeiro cuidado é com storage collision. Como várias Facets usam o storage do Diamond, é necessário organizar cuidadosamente onde cada dado será salvo.

    O segundo cuidado é com controle de upgrade. A função diamondCut é extremamente poderosa. Se for mal protegida, alguém pode substituir funções críticas do contrato.

    O terceiro cuidado é com auditoria. Um Diamond pode ser mais difícil de auditar do que um contrato simples, porque a lógica está distribuída em várias Facets.

    O quarto cuidado é com complexidade desnecessária. Para contratos pequenos, um Diamond pode ser exagerado.

    Quando vale a pena usar Diamond?

    O EIP-2535 faz mais sentido quando o projeto possui:

    • Muitos módulos funcionais.
    • Necessidade de upgrade.
    • Contratos grandes.
    • Arquitetura de longo prazo.
    • Separação clara de responsabilidades.
    • Um único endereço principal para o usuário interagir.

    Exemplos de uso:

    DAOs complexas
    Marketplaces
    Jogos on-chain
    Protocolos DeFi
    Sistemas de identidade
    Plataformas com múltiplos módulos
    Agentes de IA com identidade e permissões on-chain
    

    Uma analogia simples

    Imagine que o Diamond é um quadro de distribuição elétrica.

    O usuário chega em um único painel, mas internamente cada disjuntor leva para um circuito diferente:

    Circuito de iluminação
    Circuito das tomadas
    Circuito do ar-condicionado
    Circuito da bomba
    Circuito de segurança
    

    No Smart Contract Diamond:

    Facet de token
    Facet de staking
    Facet de governança
    Facet de permissões
    Facet de marketplace
    

    Tudo parece um único sistema, mas por dentro está dividido em módulos especializados.

    Conclusão

    O EIP-2535 Diamond Standard é uma arquitetura poderosa para Smart Contracts modulares, atualizáveis e escaláveis. Ele transforma o contrato principal em um Proxy Central, chamado Diamond, que encaminha chamadas para múltiplas Facets.

    Seu maior benefício é permitir que sistemas on-chain cresçam de maneira organizada. Em vez de construir um contrato gigantesco, o desenvolvedor pode dividir a lógica em partes menores, mais testáveis e mais fáceis de evoluir.

    Por outro lado, essa liberdade vem com responsabilidade. Um Diamond exige bom planejamento de storage, controle rigoroso de permissões e auditoria cuidadosa.

    Para projetos simples, ele pode ser complexo demais. Mas para sistemas grandes, como DAOs, protocolos DeFi, marketplaces, plataformas Web3 e infraestruturas para agentes autônomos, o EIP-2535 oferece uma das arquiteturas mais interessantes e flexíveis do ecossistema Ethereum.

    Compartilhe
    Recomendados para você
    AWS - Agentes de IA em Campo
    Riachuelo - Criando produtos com IA
    Michael Page - Criando Seu Primeiro Agente de IA
    Comentários (2)
    Sandra Souza
    Sandra Souza - 07/07/2026 13:30


    Sandra Souza
    Sandra Souza - 07/07/2026 13:30

    Parabéns pelo artigo!