ERC-8110 - Domain Architecture for Diamonds

Created 2025-12-20
Status Draft
Category ERC
Type Standards Track
Authors
Requires

Abstract

This EIP introduces a domain-based architectural pattern for contracts implementing the Diamond execution model defined by ERC-2535 (Diamond Standard) or ERC-8109 (Diamond, Simplified), together with the storage identifier mechanism defined by ERC-8042 (Diamond Storage Identifier).

It defines a consistent naming convention for storage identifiers and a directory organization model that decouples storage management from facet logic.

This pattern helps reduce storage collisions and human error while enabling better tooling for multi-facet systems.

Motivation

ERC-2535 provides a flexible foundation for modular smart contracts through facets, but it intentionally leaves storage organization and architectural conventions open to implementation.
While this flexibility encourages creativity, it can sometimes lead to inconsistency.
Each developer or team may structure storage differently, making it harder to design robust and easy-to-use tooling for storage management.

Without a shared structural framework, storage identifiers may be inconsistently verified or reused across facets, which can result in unexpected collisions or subtle upgrade issues between facets sharing the same state.

ERC-8042 introduced human-readable storage identifiers to improve clarity, but it does not define how those identifiers should be structured or grouped in larger projects.

This EIP proposes a domain-centric architectural pattern that establishes a consistent framework for managing storage independently of facet implementation.

By introducing clear domain boundaries and deterministic naming rules for storage identifiers, the pattern maintains the openness of the Diamond Standard while providing a shared foundation for collaboration, tooling support, and long-term upgrade safety across complex systems.

Specification

1. Domain Definition

2. Storage Identifier Naming Convention

A storage identifier is the human-readable string whose keccak256 hash defines a Diamond Storage position.

    bytes32 constant STORAGE_POSITION = keccak256("meaningful.string");

It represents the domain that owns and manages a specific storage layout.
To ensure uniqueness and clarity, at a minimum, a storage identifier SHOULD include the following components:

    {project}.{domain_name}.{version}

To improve readability, namespace separation, and tooling support, additional contextual components MAY be included, resulting in the following extended format:

    {org}.{project}.{domain_type}.{domain_name}.{version}

Identifier Components

Each domain:

Example Identifiers

Diamond Storage
Used for the core Diamond logic such as upgrade and introspection.

    bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("org.project.diamond.storage");

Allowance
Represents a business domain responsible for tracking token allowances or permissions.

    bytes32 constant ALLOWANCE_STORAGE_POSITION = keccak256("org.project.business.allowance.v1");

Pausable
Defines a system-level domain for pausing logic shared across multiple facets.

    bytes32 constant PAUSABLE_STORAGE_POSITION = keccak256("org.project.system.pausable.v2");

3. Storage Declaration Requirements

To support reliable tooling and explicit storage ownership, each domain defined by this proposal MUST declare its storage location using the ERC-8042 NatSpec annotation.

Specifically, the domain-owned storage struct MUST be annotated with:

@custom:storage-location erc8042:<NAMESPACE_ID>

This proposal does not redefine the storage location formula, but requires the use of this annotation to ensure that domain storage is discoverable, unambiguous, and machine-readable.

4. Directory Convention

In line with Domain-Driven Design principles, the directory layout SHOULD reflect domain ownership.

Example Directory

This directory structure is illustrative and does not mandate a specific naming convention.
Subdirectory names such as storage/ are illustrative and may contain both storage layout definitions and internal domain logic.

contracts/
├── diamond/
│   ├── storage/
│   │   └── DiamondStorage.sol
│   └── facets/
│       ├── DiamondCutFacet.sol
│       └── DiamondLoupeFacet.sol
│
├── allowance/
│   ├── storage/
│   │   └── AllowanceStorage.sol
│   └── facets/
│       └── AllowanceFacet.sol
│
└── pausable/
    └── storage/
        └── PausableStorage.sol

Rationale

From the beginning, the Diamond Standard (ERC-2535) was designed around the relationship between function selectors and storage positions, not around facets themselves.
Facets are replaceable units of logic — the diamondCut operation only replaces, removes or adds code — but the storage layout persists and defines the actual state continuity of the contract.

A clear example of this can be found in Reference Implementation of ERC-8109.

Both DiamondUpgradeFacet and DiamondInspectFacet interact with the same storage.
Although these facets serve different purposes — one mutating, one querying — they share the same domain (erc8109.diamond).
This demonstrates that storage belongs to the domain, not the facet, facets merely provide interfaces for logic to read or mutate that domain.

Over time, many implementations have treated facets as the primary boundary of responsibility, grouping logic and storage together without recognizing that storage domains are the true architectural anchors.
This misunderstanding leads to inconsistent storage management, overlapping identifiers and fragile upgrade paths where one facet unintentionally corrupts another’s state.

The domain-centric approach restores the original intent of the Diamond:
Selectors (facets) operate through domains, not as domains.
Each domain defines its own persistent storage struct and identifier, while facets merely act as interfaces that execute logic against it.

This shift decouples storage from logic when separation is desired, while still allowing tightly coupled designs when intentional.
It enables:

By formalizing this pattern, Diamond architecture becomes safer, more transparent and easier to extend — re-aligning practice with its original design philosophy.

Scalable Upgrade Boundaries (Vertical and Horizontal)

The domain-centric model defines explicit upgrade boundaries that scale in two independent dimensions: vertical and horizontal.

A vertical upgrade occurs when a domain evolves to support additional features or behaviors. In this case, new facets and function selectors MAY be added to interact with the same domain-owned storage, without modifying existing storage layout or other domains.

This allows a domain to grow in capability while preserving its existing state and interfaces.

A horizontal upgrade occurs when the contract introduces an entirely new feature set that does not belong to any existing domain. In this case, a new domain is introduced, along with its own storage identifier and layout, without impacting previously deployed domains.

By distinguishing between vertical and horizontal upgrades, systems can evolve incrementally as requirements change, while keeping each domain isolated, understandable, and independently upgradeable.

Isolation of Schema Upgrades from Logic Upgrades

In the domain-centric model, storage schema evolution is treated as a domain-level concern and is isolated from logic upgrades.

When extending an existing domain with additional state, new variables MUST be appended to the end of the domain’s storage struct, preserving compatibility with existing storage layouts.

If a layout-breaking change is required, a new version of the domain MUST be introduced using a new storage identifier. Existing state MAY be migrated explicitly if required, but such migration is not implicit and does not affect unrelated domains.

When introducing a new domain, a new storage identifier and storage layout are defined independently, without modifying or reusing existing storage.

This approach ensures that logic upgrades can proceed independently of storage schema changes, while storage evolution remains explicit, deliberate, and auditable.

Special Case: Domain-Facet Overlap

There is a special case within this separation principle where a domain and its facet intentionally represent the same entity. A great example of this approach can be found in the Compose project

In Compose, a facet and its associated domain are explicitly mapped into a single entity.
This is a deliberate design choice that enables predefined, plug-and-play standard facets with a well-defined storage layout and reduced collision risk.

This approach is suitable for systems that prioritize modular composition and standardized functionality, allowing developers to safely integrate common features with predictable behavior.
However, when implementing custom or project-specific logic, domains and facets SHOULD still be treated as separate entities.

Maintaining this separation preserves clarity of ownership, supports future upgrades, and improves the long-term scalability of complex Diamond-based architectures.

Backwards Compatibility

This proposal is fully backward-compatible with ERC-2535 (Diamond Standard), ERC-8042 (Diamond Storage Identifier) and ERC-8109 (Diamond, Simplified). It introduces no breaking changes, no new opcodes, and no modifications to existing protocol mechanics.

It does not alter the execution model defined by ERC-2535 or ERC-8109. The relationships between facets, selectors, and shared storage remain unchanged and fully compatible across all three standards.

Instead, this proposal defines an architectural convention that complements existing Diamond standards by:

This architecture may be applied to contracts implementing either ERC-2535 or ERC-8109, as both share the same fundamental facet and selector architecture.

Developers are encouraged to continue following all applicable standards to maintain interoperability while benefiting from clearer state ownership, reduced storage collision risk, and lower architectural complexity.

Reference Implementation

Minimal implementation examples demonstrating the convention:

Diamond Domain

    bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("org.project.diamond.storage");

    struct DiamondStorage {
        mapping(bytes4 => address) selectorToFacet;
        address contractOwner;
    }

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

Business Domain (Allowance)

    bytes32 constant ALLOWANCE_STORAGE_POSITION = keccak256("org.project.business.allowance.v1");

    struct AllowanceStorage {
        mapping(address => mapping(address => uint256)) allowance;
    }

    function allowanceStorage() pure returns (AllowanceStorage storage s) {
        bytes32 position = ALLOWANCE_STORAGE_POSITION;
        assembly {
            s.slot := position
        }
    }

System Domain (Pausable)

    bytes32 constant PAUSABLE_STORAGE_POSITION = keccak256("org.project.system.pausable.v2");

    struct PausableStorage {
        mapping(bytes4 => bool) isSelectorPaused;
    }

    function pausableStorage() pure returns (PausableStorage storage s) {
        bytes32 position = PAUSABLE_STORAGE_POSITION;
        assembly {
            s.slot := position
        }
    }

Each domain defines and owns its storage independently. Facets interact with domain-owned storage definitions, supporting safe upgrades and avoiding unintended storage overlap.

Security Considerations

This pattern strengthens the security model of Diamond-based systems by introducing explicit and deterministic storage identifiers.

By separating domains and enforcing consistent naming rules, it reduces the risk of:

Each domain owns its ERC-8042 storage identifier. When combined with append-only storage layout upgrades, this allows storage evolution without interfering with existing state.

This clarity also improves auditability and supports static analysis tooling when analyzing storage safety across upgrades.

Copyright

Copyright and related rights waived via CC0.