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:

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:
- Guarda qual seletor de função aponta para qual Facet.
- Recebe chamadas no
fallback. - 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.




