DEV Community

Cover image for Why Vinu Digital Uses Diamonds for Complex Upgradeable Solidity Systems?
Vinu Digital
Vinu Digital

Posted on Originally published at vinu.com.tr

Why Vinu Digital Uses Diamonds for Complex Upgradeable Solidity Systems?

Vinu Digital uses ERC-2535 Diamond Proxy for large, evolving, and heterogeneous Solidity systems because it enables selector-level upgrades, modular facets, explicit storage architecture, standardized introspection, and a stable contract address while allowing the system to evolve over multiple release cycles.

For smaller systems with a coherent logic surface, UUPS may remain the better option. For fleets of homogeneous contract instances that need to upgrade together, Beacon Proxy often provides the right abstraction. But when upgradeability is a genuine lifecycle requirement and the system is expected to grow across multiple domains, we consider ERC-2535 the stronger long-term architecture.

That distinction is important because upgradeability is not simply about whether a smart contract can change after deployment. It is about how much of the system must change, how those changes are governed, how storage remains safe, and how understandable the architecture remains as complexity accumulates.

Immutability Is a Strength Until It Becomes an Operational Constraint

Ethereum is a replicated state machine: every node executes the same transactions against shared state, and a deployed smart contract is code plus persistent storage bound to a specific address onchain. That execution model is exactly why contracts are deterministic in practice and why blockchain systems derive so much of their trust model from code that cannot be silently altered after deployment.

Ethereum’s own documentation emphasizes that deployed smart contracts execute the business logic embedded at deployment time and are immutable by design unless developers explicitly introduce an upgrade mechanism.

That immutability is not merely aesthetic. It constrains unilateral tampering, reduces governance ambiguity, and makes audit scope stable. But the same property also creates an engineering dead end when production code needs a security patch, a bug fix, a feature extension, or a standards update after launch.

Ethereum’s documentation calls this out directly: immutability is necessary for trustlessness and security, yet it can be a drawback when business logic must evolve.

For that reason, upgradeability is not an anti-pattern in itself. The real question is architectural: what form of upgradeability introduces the lowest operational risk and the best long-term maintainability for a serious protocol surface?

For small and stable systems, the answer may be minimal proxies or UUPS. For factory-driven fleets, Beacon often fits. For large, evolving, domain-rich systems, our answer at Vinu Digital is unambiguous: Diamond Proxy, standardized as ERC-2535, is the superior production pattern.

ERC-2535 Diamond Proxy vs. UUPS and Beacon: The Upgradeability Landscape

At a high level, the three most important upgradeable patterns for Solidity architects today are UUPS (Universal Upgradeable Proxy Standard), Beacon Proxy, and Diamond Proxy.

UUPS places upgrade logic in the implementation contract and typically uses ERC-1967 slots. Beacon routes many proxies through a shared beacon contract that returns the active implementation. Diamond routes individual function selectors to facet contracts, allowing the system to evolve function by function rather than contract by contract.

Pattern Upgrade Unit Operational Shape Typical Best Fit
UUPS Entire implementation One proxy delegates to one implementation; upgrade logic lives in the implementation Lean single-system upgrades with a relatively coherent logic surface
Beacon Proxy Entire shared implementation Many proxies query one beacon for the implementation and delegate to it Large fleets of homogeneous instances upgraded in lockstep
Diamond Individual selectors grouped into facets One diamond address delegates different selectors to different facet contracts Large, modular, long-lived systems with heterogeneous features

OpenZeppelin’s proxy documentation explicitly characterizes UUPS as lightweight and versatile, and Beacon as the pattern used when many proxies must be upgraded together. ERC-2535 Diamond Proxy, by contrast, standardizes a modular, multi-facet proxy with “virtually no size limit.”

That difference in upgrade granularity is the most important architectural dividing line. UUPS and Beacon swap implementations; Diamonds edit the protocol surface itself.

Why Vinu Digital Standardizes on ERC-2535 Diamond Proxy

At Vinu Digital, whenever upgradeable contracts are actually warranted, we standardize on Diamond Proxy (ERC-2535), the approach authored by Nick Mudge, because it best matches how complex protocols evolve in reality: not as one monolithic implementation rewritten wholesale, but as a growing graph of distinct features, permissions, workflows, and domain modules.

ERC-2535 was explicitly designed for modular systems that can be extended after deployment, can exceed the single-contract size ceiling, and can add, replace, or remove functionality without discarding everything else.

This is not a stylistic preference. It is a production architecture decision.

The Diamond model gives us a stable system address, unlimited surface area through facets, explicit upgrade records through DiamondCut events, mandatory introspection through loupe functions, and a storage model that can be made far more understandable than conventional proxy layouts when the codebase becomes large.

The standard’s own motivation section highlights a single address for unlimited functionality, fine-grained upgrades, modular organization, atomic multi-function change sets, and the ability to become immutable later.

That combination matters most when the protocol is expected to survive multiple release cycles, multiple teams, audit turnovers, governance transitions, and standards churn.

In those environments, the winning architecture is not the smallest proxy; it is the architecture that remains intelligible and safely evolvable after years of accumulated complexity.

Diamonds are the only major proxy family that was designed around that premise from the ground up.

How an ERC-2535 Diamond Proxy Routes Execution

An ERC-2535 Diamond Proxy routes calls by mapping function selectors to individual facet contracts and executing their code through delegatecall.

A Diamond is a proxy with a fallback function that inspects the first four bytes of calldata. The function selector identifies the facet responsible for that selector, and the Diamond then executes the facet’s code via delegatecall.

Solidity’s ABI specification defines the selector as the first four bytes of the Keccak-256 hash of the canonical function signature, and ERC-2535 explicitly uses that selector as the routing key for the Diamond’s dispatch layer.

Conceptually, the dispatch path looks like this:

fallback() external payable {
    address facet = selectorToFacet[msg.sig];

    require(facet != address(0), "FunctionNotFound");

    assembly {
        calldatacopy(0, 0, calldatasize())

        let ok := delegatecall(
            gas(),
            facet,
            0,
            calldatasize(),
            0,
            0
        )

        returndatacopy(0, 0, returndatasize())

        switch ok
        case 0 {
            revert(0, returndatasize())
        }
        default {
            return(0, returndatasize())
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

That flow mirrors the standard’s routing model: calldata is copied, the resolved facet is delegatecall'd, and return data is bubbled back to the external caller.

Because delegatecall executes callee code in the context of the caller, msg.sender and msg.value remain unchanged, and all reads and writes hit the Diamond’s storage, not the facet’s storage. Solidity’s own documentation describes delegatecall the same way, and ERC-2535 relies on that property explicitly.

How diamondCut Handles ERC-2535 Upgrades

The upgrade surface is codified in diamondCut.

Instead of replacing an entire implementation address, diamondCut receives an array of changes where each item contains a facet address, an action — Add, Replace, or Remove — and a selector set.

The standard further allows an _init address and _calldata payload to perform post-cut initialization with delegatecall, all in the same transaction.

That is a major engineering advantage: the data migration logic is transactionally tied to the code change instead of being spread across separate operational steps.

ERC-2535 also states that executing all changes in a single transaction prevents the sort of corruption that can occur when upgrades are split across multiple transactions.

Diamond Loupe and Function-Level Introspection

Diamonds also standardize introspection through the Diamond Loupe interface.

A compliant Diamond must expose:

  • facets()
  • facetFunctionSelectors(address)
  • facetAddresses()
  • facetAddress(bytes4)

This matters far more than it first appears.

With a traditional proxy, verified source code alone does not tell you the live per-function routing of a composite system. With an ERC-2535 Diamond Proxy, the standard requires machine-readable introspection of the function graph itself.

Even more important, every selector mutation must be recorded via the DiamondCut event.

ERC-2535 frames this as a transparency benefit: Diamonds provide both a snapshot of current routing through loupe functions and a historical record of all function additions, replacements, and removals through events.

That is a better operational audit trail than “implementation slot changed from A to B” because it shows exactly which functions moved, which were removed, and which were introduced.

Why Diamond Storage Is the Decisive Engineering Advantage

For long-lived upgradeable Solidity systems, storage discipline is one of the hardest engineering problems.

Most proxy discussions focus too much on code dispatch and not enough on storage architecture. In practice, for long-lived upgradeable systems, storage discipline is the hardest part.

UUPS and Beacon both inherit the ERC-1967 / unstructured storage worldview. OpenZeppelin’s proxy documentation explains the core idea clearly: instead of storing the implementation pointer in slot 0, the proxy uses pseudo-random, standardized slots so that proxy metadata does not collide with the implementation’s state.

ERC-1967 formalizes those slots precisely because proxies should not expose ordinary public methods that might clash with implementation selectors, and because proxy metadata must live in addresses the compiler will not allocate to normal state variables.

That solves one class of collision: proxy-vs-implementation collision.

It does not solve the more dangerous class for real upgrade programs: implementation-vs-implementation collision across versions.

OpenZeppelin explicitly warns that unstructured storage does not safeguard against collisions between different implementation versions; developers must preserve layout compatibility and append state rather than reorder it.

ERC-1822 says the same in different language, recommending a shared base contract so variables are not reordered across upgrades.

Diamonds change that conversation.

ERC-2535 does not force one storage schema, but it explicitly names Diamond Storage and AppStorage as valid storage layout strategies for Diamonds. In both approaches, storage becomes an explicit architectural layer rather than an implicit side effect of inheritance order.

Diamond Storage: Namespaced State for Modular Systems

With Diamond Storage, you define namespaced structs stored at deterministic slots, typically computed from a unique hash string.

Nick Mudge’s reference explanation describes the namespace string as effectively a storage namespace, with the struct pinned to a unique storage position.

That isolation makes it natural to compartmentalize state per feature area or per facet family.

As long as namespaces remain unique and structs are only extended append-only, the risk of accidental cross-module overwrite is dramatically reduced relative to sprawling inheritance-based layouts.

AppStorage: Shared Application-Level State Across Facets

With AppStorage, you make a different trade-off.

Instead of many namespaced structs, you define one application-level struct, usually named AppStorage, and expose it consistently to application-specific facets as a single shared state object, commonly through:

AppStorage internal s;
Enter fullscreen mode Exit fullscreen mode

Mudge argues that this improves readability, reduces repeated storage accessor boilerplate, and is even “a little more gas efficient” than repeated Diamond Storage access in some usage patterns.

The important practical point is not the micro-gas claim; it is that AppStorage makes shared application state explicit and uniform across facets.

ERC-1967 Storage vs. Diamond Storage Architecture

The practical comparison is therefore not “Transparent vs. Diamond storage” in a strict taxonomy sense.

The meaningful storage comparison is ERC-1967-style unstructured metadata slots plus append-only implementation layout versus explicit namespaced or application-scoped storage architecture inside the Diamond.

For small proxies, the former is fine. For large protocol surfaces, the latter is significantly easier to reason about, refactor, audit, and govern.

That said, Diamonds do not repeal storage rules.

Solidity still stores state according to deterministic layout rules, starting from slot 0, packing values where possible, and deriving mapping or dynamic-array locations from slot-based hashing.

Solidity explicitly considers storage layout part of the language’s external interface.

So Diamond Storage and AppStorage are not magic; they are disciplined strategies layered on top of Solidity’s underlying storage semantics.

If you change the meaning of an existing namespace, or reorder fields inside a live struct, you can still corrupt state.

Where ERC-2535 Diamond Proxy Outperforms UUPS and Beacon

The advantages of ERC-2535 become most significant as an upgradeable Solidity system grows in size, modularity, governance complexity, and lifecycle requirements.

1. Contract Size

The first major advantage is contract size.

EIP-170 caps deployed code size at 0x6000 bytes, i.e. 24,576 bytes, and contract creation fails if runtime code exceeds that size.

ERC-2535 was designed specifically to escape that ceiling by splitting external functionality across many facets while preserving a single system address.

The Diamond standard states this directly: Diamonds have “virtually no size limit,” and one of their core motivations is to solve the 24 KB limit for related functionality that should still present as one contract surface.

UUPS and Beacon do not solve this problem; each implementation is still an ordinary contract subject to the same code-size ceiling.

2. Modularity and Code Organization

The second advantage is modularity and code organization.

ERC-2535 treats facets as separate, independent contracts that can share state, internal functions, and libraries.

The standard even notes that external library contracts may serve as facets and that internal library code can be shared across facets.

This is a much more sustainable decomposition model than a single upgradeable implementation contract whose inheritance tree must keep absorbing new domains over time.

If your protocol has staking, governance, fee logic, account management, settlement logic, admin tooling, migrations, and diagnostics, each of those concerns can sit in separate facets without collapsing into one giant implementation file.

3. Fine-Grained Upgrade Flexibility

The third advantage is upgrade flexibility.

UUPS and Beacon replace an implementation address. That is a coarse-grained operation even when the actual code change is small.

Diamond upgrades can add, replace, or remove only the selectors that need to move.

ERC-2535 calls these fine-grained upgrades and explicitly states that parts of a Diamond can be changed while other parts are left alone.

That is closer to how real protocol maintenance works.

You do not always want to redeploy and re-authorize an entire implementation because you changed one subsystem.

4. Granular Access Control

The fourth advantage is granular access control.

The Diamond standard intentionally leaves ownership and authentication out of scope, but that is a feature, not a defect.

ERC-2535 explicitly says Diamond authentication can be simple or complex, fine-grained or coarse, and even gives examples where different actors or a DAO can be authorized to add, replace, or remove only certain functions.

In other words, the standard does not hard-code one governance model; it permits a governance model aligned to the protocol’s module boundaries.

That makes per-facet or per-function upgrade permissions architecturally natural in a way that whole-implementation UUPS upgrades are not.

UUPS typically consolidates upgrade authorization behind _authorizeUpgrade, and Beacon typically centralizes shared implementation control in the beacon owner.

5. System-Level Gas Efficiency

The fifth advantage is system-level gas efficiency, but this requires precise language.

For an isolated, single-entry call path, UUPS is usually the leanest general-purpose upgradeable proxy.

OpenZeppelin explicitly describes UUPS as lightweight and notes that transparent-style proxies are more expensive to deploy because upgrade logic lives in the proxy rather than the implementation.

Beacon adds another moving part because the proxy must resolve implementation via the beacon.

OpenZeppelin documents that each call retrieves the implementation from the beacon, even though newer implementations reduce some overhead by storing the beacon address immutably to avoid unnecessary storage reads.

Diamonds are different.

Their dispatch path includes selector lookup and delegatecall, so they are not automatically the cheapest possible proxy for a trivial application.

But ERC-2535’s own gas rationale is broader and more realistic: Diamonds can reduce gas at the architecture level by collapsing multi-contract flows into one address, enabling direct shared-storage access, and letting developers add specialized gas-optimized functions for specific use cases rather than forcing every flow through generic abstractions.

The standard explicitly mentions condensing multiple contracts into a single Diamond and implementing gas-optimized functions such as batch operations.

That advantage compounds as system complexity grows.

There is also a tooling-related gas nuance.

Nick Mudge’s reference implementations show that different internal data structures produce different gas profiles.

In the diamond-3 family, loupe functions are deliberately optimized so they can be called onchain, whereas earlier variants favored cheaper diamondCut operations at the cost of more expensive loupe reads.

That illustrates an important point for architects: Diamonds are not one rigid implementation but a standardized interface with multiple internal trade-off profiles.

You can tune the shape of the system to your operational priorities.

6. Introspection and Transparency

The sixth advantage is introspection and transparency.

ERC-1967 lets tooling read proxy slots such as implementation, admin, or beacon addresses, which is extremely useful.

But those slots do not tell you the live function-level topology of a large modular system.

Diamonds do.

Loupe functions expose current facet composition, and DiamondCut events preserve the full mutation history of the contract surface.

For governance-heavy systems, audit-heavy systems, or systems with long-lived decentralization roadmaps, that is a serious operational advantage rather than a cosmetic one.

The Trade-Offs of ERC-2535 Diamond Proxy That Still Matter

An ERC-2535 Diamond architecture introduces more complexity than a vanilla UUPS proxy, and that complexity still has to be managed deliberately.

There is selector bookkeeping, facet composition, storage discipline, ABI management, upgrade orchestration, and more surface area for governance mistakes.

ERC-2535 itself warns that diamondCut allows arbitrary execution with access to the Diamond’s storage via delegatecall, so access to it must be restricted carefully.

The standard also discourages selfdestruct inside facets because misuse can delete a Diamond or a facet.

Selector Management Still Requires Discipline

You also need disciplined selector management.

Solidity function selectors are only four bytes, and selector clash is a real phenomenon in the ABI generally.

ERC-1967 highlights exactly why proxies should avoid exposing ordinary public methods that might collide with implementation selectors.

Diamonds deal with this better than typical proxies because a correct diamondCut implementation rejects attempts to add selectors that already exist, but the risk only stays controlled if the Diamond’s cut logic is implemented correctly and reviewed rigorously.

Diamonds Are Not Always the Right Answer

Equally important, Diamonds are not always the right answer.

If you have a compact contract with a coherent domain model and straightforward governance, UUPS remains an excellent choice because it is operationally simpler and economically lighter.

If you run a factory that creates many homogeneous instances that must all upgrade together, Beacon is usually the right operational abstraction.

The argument for Diamonds is not “Diamonds win every benchmark.”

The correct argument is narrower and stronger:

For large, evolving, heterogeneous contract systems, Diamonds provide the best long-term upgrade architecture available in the Ethereum standards ecosystem.

How Vinu Digital Approaches Upgradeable Solidity Architecture

At Vinu Digital, we do not treat ERC-2535 Diamond Proxy as the default answer to every upgradeability problem.

Our approach begins with the lifecycle and architecture of the system itself.

If a contract is compact, its domain model is coherent, and its upgrade requirements are straightforward, UUPS can remain the more appropriate choice because of its operational simplicity.

If the architecture consists of a large number of homogeneous instances that need to move to the same implementation together, Beacon Proxy can provide a better operational model.

We use ERC-2535 Diamond Proxy when the architecture requires something fundamentally different: a long-lived contract surface expected to expand across multiple functional domains while retaining a stable address and allowing individual parts of the system to evolve independently.

In those systems, the decision is driven by the same architectural requirements discussed throughout this article:

  • Selector-level upgrades instead of whole-implementation replacements
  • Modular functionality organized through facets
  • Explicit Diamond Storage or AppStorage strategies
  • Function-level introspection through Diamond Loupe
  • Historical visibility through DiamondCut events
  • Support for systems that exceed a single implementation contract’s code-size constraints
  • A path toward eventual immutability if governance later chooses to disable upgrades

That is why our preference for Diamonds is not based on proxy minimalism or a single gas benchmark.

It is based on how well the architecture remains understandable, maintainable, governable, and safely evolvable as the system grows.

Frequently Asked Questions About ERC-2535 Diamond Proxy

What Is an ERC-2535 Diamond Proxy?

An ERC-2535 Diamond Proxy is a modular upgradeable smart contract architecture that routes individual function selectors to separate contracts called facets.

Instead of delegating all functionality to one implementation contract, a Diamond can route different functions to different facets while maintaining a single external contract address and shared storage context.

How Does ERC-2535 Differ From UUPS?

The primary difference between ERC-2535 Diamond Proxy and UUPS is upgrade granularity.

UUPS generally replaces an entire implementation contract, while ERC-2535 can add, replace, or remove individual function selectors grouped across different facets.

For compact systems, UUPS can therefore be simpler. For large and heterogeneous systems, Diamond’s finer upgrade model can provide greater architectural flexibility.

When Should You Use a Diamond Proxy Instead of UUPS?

A Diamond Proxy is most appropriate when a Solidity system is expected to become large, modular, long-lived, and functionally heterogeneous.

If the application has a compact logic surface and relatively straightforward upgrade requirements, UUPS may be the better option. Diamonds become more valuable when different subsystems need to evolve independently without replacing the entire implementation surface.

What Is the Difference Between Diamond Storage and AppStorage?

Diamond Storage typically separates state into namespaced structs stored at deterministic storage slots, making it possible to isolate storage by feature or module.

AppStorage instead uses a shared application-level struct that can be accessed consistently across application-specific facets.

Both approaches make storage architecture explicit, but they make different trade-offs between modular isolation and shared application-level state.

Does ERC-2535 Remove Solidity’s Contract Size Limitation?

ERC-2535 does not change Ethereum’s underlying contract-size rules.

Instead, it allows a system’s functionality to be distributed across multiple facet contracts while presenting those functions through one Diamond address.

This enables a Diamond architecture to support a much larger combined functional surface than would fit inside a single implementation contract.

Are ERC-2535 Diamonds More Gas Efficient Than UUPS?

Not automatically.

For a simple call path, UUPS is generally the leaner general-purpose upgradeable proxy. Diamond dispatch adds selector lookup and delegatecall.

The potential advantage of Diamonds emerges at the system architecture level, where multiple contract interactions can be consolidated, state can be shared directly across facets, and specialized functions can be introduced for specific workflows.

Are Diamond Proxies Always the Best Upgradeability Pattern?

No.

UUPS remains a strong option for compact systems with coherent logic and straightforward governance. Beacon Proxy is particularly useful for fleets of homogeneous instances that need to upgrade together.

Our argument for ERC-2535 is specific: Diamonds are best suited to large, evolving, heterogeneous contract systems where fine-grained upgrades and long-term modularity are genuine architectural requirements.

Why We Use ERC-2535 Diamonds for Long-Lived Solidity Systems

That is the rule we apply at Vinu Digital.

We use Diamonds when upgradeability is a real lifecycle requirement and the system surface is expected to grow.

We do so because Diamonds give us selector-level upgrades, modular facets, storage architectures that stay legible under scale, standard introspection, a better historical record of changes, and a clean path from iterative delivery to eventual immutability if governance chooses to lock the system down.

ERC-2535 was designed for exactly that lifecycle, and in production-grade Solidity architecture, that matters more than minimalism for its own sake.

Building a Complex Upgradeable Solidity System?

Choosing between ERC-2535 Diamond Proxy, UUPS, Beacon, or another smart contract architecture should begin with the lifecycle, modularity, governance model, and operational requirements of the system — not with the proxy pattern alone.

At Vinu Digital, we design and develop blockchain and smart contract architectures around those requirements, from modular Solidity systems to complex Web3 infrastructure.

If you are designing or expanding an upgradeable Solidity system, contact Vinu Digital to discuss the architecture behind your project.

Top comments (0)