Hello Magicians,
I’d like to propose a domain-based architectural pattern for Diamond contracts (ERC-2535), building on the human-readable storage identifiers introduced in Dimond Storage (ERC-8042).
Introduction
This proposal introduces a domain-based architectural pattern for Diamond
contracts.
In this context, a domain represents logical ownership of storage and
responsibility inside a Diamond, following common software architecture
practices.
The core idea is to treat storage as something owned by clearly defined
domains, while facets act as logic interfaces that read or modify that storage.
By organizing storage around explicit domain boundaries and anchoring them to
ERC-8042 identifiers, this pattern aims to improve clarity, reduce accidental
storage collisions, and make upgrades easier to reason about.
This EIP does not introduce new protocol rules or interfaces.
It documents an architectural pattern intended to complement existing Diamond
implementations.
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
-
A domain represents the conceptual ownership of a storage space.
-
Each domain corresponds to exactly one storage struct and one identifier.
-
A domain has a one-to-many relationship with the group of function selectors that access it.
-
A domain is independent of facets, multiple facets MAY read or modify the same domain.
-
Domains SHOULD be defined according to business or system responsibility, not by facet name.
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
-
org
Optional organization or author prefix (e.g.,eth,vag,safe) -
project
Project or protocol name -
domain_type
Optional classification of the domain. If present, it SHOULD be one of:-
diamond— core Diamond protocol domains, such as upgrade, introspection, and ownership. -
system— shared system-level domains providing cross-cutting functionality. (e.g., reentrancy, pause, access control) -
business— application-specific domains. (tokens, guards, modules)
-
-
domain_name
Lowercase keyword identifying the storage domain -
version
Optional storage layout version identifier-
The initial storage layout is conceptually treated as
v1. -
If omitted, the identifier refers to this initial (
v1) storage layout for backward compatibility. -
v2,v3, … MUST be used for layout-breaking changes. -
The version MUST be incremented only when the storage layout is no longer append-only.
-
Each domain:
-
MUST have one storage struct and one identifier.
-
MUST use append-only struct upgrades, add new fields at the end.
-
SHOULD be implemented in a dedicated directory named after the 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.
-
Each domain defines a logical namespace for storage ownership.
Directories are named after this namespace and serve as its physical representation in the codebase. -
Facets act as logic containers and do not own storage.
They MAY reside alongside domain directories or reference domain-owned logic. -
Both directory names and storage identifiers SHOULD include domain information.
This consistency allows tooling and precompilers to automatically associate selectors, domains, and storage layouts. -
Each domain SHOULD be represented by a dedicated directory.
Within this directory, domain-owned logic such as storage layout definitions, internal helper logic, and any facets primarily associated with the domain MAY be organized under subdirectories as needed. -
This structure reduces the risk of storage collisions by design.
The alignment of domain namespaces, directory layout, and storage identifiers allows file system constraints and static analysis tools to surface conflicts early and reason about upgrades proactively.
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:
- Independent evolution of business logic without rewriting storage.
- Clear separation between reusable system components and app-specific domains.
- A deterministic mapping between identifiers and state.
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:
-
Reinforcing modularity and upgrade safety established by ERC-2535 and ERC-8109
-
Extending the human-readable storage identifier design introduced by ERC-8042
-
Providing a domain-based approach to storage ownership and organization
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:
- Storage collisions between unrelated facets or upgrades.
- Human errors caused by inconsistent or reused identifiers.
- State corruption during upgrades or extensions.
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.
Thoughts & Feedback
Any kind of feedback would be appreciated, especially from real-world Diamond implementations.