Skip to content

ERC-2535

ERC-2535

Overview

ERC-2535 (Diamond Standard, also known as Diamonds, Multi-Facet Proxy) is Ethereum's diamond standard, defining a modular smart contract system that can be infinitely upgraded and extended after deployment, breaking through Ethereum's 24KB contract size limit.

The diamond standard is like a multi-faceted diamond, where each facet represents a functional module, and all facets together form a complete contract system. This design enables large and complex DeFi protocols to organize and manage unlimited functionality under a single address.

Core Concepts

What Is a Diamond A Diamond is a proxy contract with the following characteristics: - Single contract address - Unlimited functional extensibility - Modular code organization - Flexible upgrade mechanism

What Is a Facet A Facet is a contract that implements specific functionality: - Similar to an implementation contract or library - Each Facet contains a set of related functions - Multiple Facets can compose complete business logic - Can be independently upgraded and replaced

Differences from Traditional Proxy Patterns

Feature Traditional Proxy Diamond Standard
Number of implementation contracts 1 Unlimited
Contract size limit 24KB Unlimited
Upgrade granularity Entire contract Individual functions
Code organization Monolithic Modular

Core Components

Diamond Contract The main contract acting as a proxy:

contract Diamond {
    // Store function selector to facet address mapping
    mapping(bytes4 => address) selectorToFacet;

    // Fallback function: routes all calls
    fallback() external payable {
        address facet = selectorToFacet[msg.sig];
        require(facet != address(0), "Function does not exist");

        // Delegatecall to the corresponding facet
        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()) }
        }
    }
}

DiamondCut The core interface for upgrade functionality:

interface IDiamondCut {
    enum FacetCutAction { Add, Replace, Remove }

    struct FacetCut {
        address facetAddress;      // Facet contract address
        FacetCutAction action;     // Operation type
        bytes4[] functionSelectors;// Function selector list
    }

    function diamondCut(
        FacetCut[] calldata _diamondCut,
        address _init,
        bytes calldata _calldata
    ) external;

    event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}

DiamondLoupe The diamond inspection interface for querying Diamond structure:

interface IDiamondLoupe {
    struct Facet {
        address facetAddress;
        bytes4[] functionSelectors;
    }

    // Return all facet addresses
    function facets() external view returns (Facet[] memory);

    // Return the facet for a given function selector
    function facetAddress(bytes4 _functionSelector) external view returns (address);

    // Return all function selectors
    function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory);

    // Return all facet addresses
    function facetAddresses() external view returns (address[] memory);
}

Storage Patterns

Diamond Storage The recommended storage pattern to avoid storage collisions:

// Define storage structure
struct DiamondStorage {
    mapping(address => uint256) balances;
    uint256 totalSupply;
    // ...other state variables
}

// Use a fixed storage position
bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");

function diamondStorage() internal pure returns (DiamondStorage storage ds) {
    bytes32 position = DIAMOND_STORAGE_POSITION;
    assembly {
        ds.slot := position
    }
}

// Usage in a Facet
contract TokenFacet {
    function transfer(address to, uint256 amount) external {
        DiamondStorage storage ds = diamondStorage();
        require(ds.balances[msg.sender] >= amount, "Insufficient balance");

        ds.balances[msg.sender] -= amount;
        ds.balances[to] += amount;
    }
}

AppStorage Another storage pattern where all state variables are in a single struct:

struct AppStorage {
    uint256 totalSupply;
    mapping(address => uint256) balances;
    mapping(address => mapping(address => uint256)) allowances;
    // ...all state variables
}

// In the first storage position of all Facets
contract BaseFacet {
    AppStorage internal s;
}

How It Works

Function Call Flow

  1. User calls a function

    User -> Diamond.transfer(to, amount)
    

  2. Fallback function intercepts

    Diamond's fallback function is triggered
    Extracts function selector: 0xa9059cbb (transfer's signature)
    

  3. Find the corresponding Facet

    Look up in the selectorToFacet mapping
    Find the TokenFacet address
    

  4. Delegatecall execution

    Diamond -> delegatecall -> TokenFacet.transfer()
    Executes in the Diamond's storage context
    

  5. Return result

    Execution result is returned to the user
    

Upgrade Operations

Add New Functionality

// Deploy new Facet
GovernanceFacet governanceFacet = new GovernanceFacet();

// Prepare DiamondCut
IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);
cut[0] = IDiamondCut.FacetCut({
    facetAddress: address(governanceFacet),
    action: IDiamondCut.FacetCutAction.Add,
    functionSelectors: [
        GovernanceFacet.propose.selector,
        GovernanceFacet.vote.selector,
        GovernanceFacet.execute.selector
    ]
});

// Execute upgrade
diamond.diamondCut(cut, address(0), "");

Replace Existing Functionality

// Deploy new version of Facet
TokenFacetV2 newTokenFacet = new TokenFacetV2();

cut[0] = IDiamondCut.FacetCut({
    facetAddress: address(newTokenFacet),
    action: IDiamondCut.FacetCutAction.Replace,
    functionSelectors: [TokenFacet.transfer.selector]
});

diamond.diamondCut(cut, address(0), "");

Remove Functionality

cut[0] = IDiamondCut.FacetCut({
    facetAddress: address(0),
    action: IDiamondCut.FacetCutAction.Remove,
    functionSelectors: [OldFeature.deprecatedFunction.selector]
});

diamond.diamondCut(cut, address(0), "");

Initialization Data

// Deploy initialization contract
InitContract init = new InitContract();

// Call initialization during upgrade
diamond.diamondCut(
    cut,
    address(init),
    abi.encodeWithSelector(InitContract.init.selector, constructorArgs)
);

Key Advantages

Breaking Contract Size Limits - Ethereum contract size limit: 24KB - A Diamond can have unlimited Facets - Each Facet can be up to 24KB - Total functionality size: Unlimited

Fine-Grained Upgrades - Only upgrade the functions that need modification - Does not affect other functionality - Reduces upgrade risk - Saves Gas fees

Single Address - Users and integrators only need to remember one address - Simplifies frontend integration - Facilitates brand recognition - Simplifies authorization management

Modular Architecture - Clear code organization - Independent functional modules - Facilitates team collaboration - Easy to maintain and test

Transparent Upgrade History - Each upgrade triggers a DiamondCut event - Complete upgrade logs - Traceable change history - Improves transparency

Use Cases

Large **DeFi Protocols** Complex protocols like Aave, Compound: - Lending functionality - Governance functionality - Liquidation functionality - Reward distribution - Each module can be an independent Facet

Game Contracts Blockchain games require extensive functionality: - Character management - Item system - Battle logic - Trading marketplace - Each system upgrades independently

DAO Infrastructure Contracts for decentralized organizations: - Proposal system - Voting mechanism - Treasury management - Member management - Modular extension

*NFT* Platforms** Complex NFT protocols: - Minting logic - Market trading - Royalty distribution - Metadata management - Flexible extension

Notable Implementations

Aavegotchi A GameFi project using the Diamond standard: - Modular game logic - Continuous feature iteration - Maintains a single contract address

Louper Diamond explorer and development tool: - Visualize Diamond structure - View all Facets - Track upgrade history

Development Tools

diamondABI Automatically generates the complete ABI for a Diamond:

const diamond = await ethers.getContractAt('DiamondFullABI', diamondAddress)
// Can call functions from all Facets

hardhat-deploy Supports Diamond deployment and upgrades:

await deploy('Diamond', {
    from: deployer,
    diamondCut: facetCuts,
})

louper.dev Online Diamond explorer: - Enter a Diamond address - View all Facets and functions - Track upgrade history

Security Considerations

Upgrade Permission Management

// Using AccessControl
modifier onlyOwner() {
    require(msg.sender == owner, "Not authorized");
    _;
}

function diamondCut(...) external onlyOwner {
    // Upgrade logic
}

Storage Collisions - Use the Diamond Storage pattern - Each data structure uses a unique storage position - Avoid storage overwrites between Facets

Function Selector Collisions - Ensure different Facets do not have identical function signatures - DiamondCut should detect collisions - Use tools for automated checking

Initialization Safety

bool private _initialized;

function init() external {
    require(!_initialized, "Already initialized");
    _initialized = true;
    // Initialization logic
}

Best Practices

Module Division - Divide Facets by functional domain - Each Facet has a single responsibility - Avoid overly large Facets - Keep code clean

Upgrade Strategy - Use timelocks to delay upgrades - Multi-sig controls upgrade permissions - Provide emergency pause mechanisms - Thoroughly test upgrade processes

Event Logging

event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);

// Log all important operations
emit FunctionAdded(selector, facetAddress);

Documentation Maintenance - Document the functionality of each Facet - Describe upgrade history - Provide integration guides - Maintain ABI documentation

Limitations

Complexity - Steep learning curve - More complex development and debugging - Requires specialized knowledge

*Gas* Cost** - Delegatecall has additional overhead - Function lookup has a cost - Slightly more expensive than direct calls

Ecosystem Support - Relatively few tool support - Limited audit experience - Fewer community resources

Audit Challenges - Need to audit the entire Diamond system - Upgrades may introduce new vulnerabilities - Continuous security monitoring required

Future Outlook

The Diamond standard is becoming the preferred architecture for large and complex protocols: - More DeFi protocols adopting it - Tool ecosystem continuously improving - Best practices gradually forming - Audit standardization