What Is the Diamond Proxy Pattern (EIP-2535)?

A diamond is a single smart contract whose external functions are supplied by several other contracts, called facets. To a wallet, a block explorer, or another contract, it looks like one ordinary contract at one Ethereum address. Behind that address, the diamond forwards each call to the facet that implements it using delegatecall.

That design is the core of the diamond proxy pattern, standardized as EIP-2535 by Nick Mudge. It solves two problems at once: it lets a contract system grow past Ethereum’s 24 KB code-size limit, and it lets you upgrade one function at a time instead of redeploying everything.

EIP-2535 has been a Final Ethereum standard since October 2022, and several production protocols run on it. This guide covers what an Ethereum diamond is, how calls and storage actually work, how upgrades are recorded, and where the pattern’s real risks sit.

What Is an Ethereum Diamond?

An Ethereum diamond is a proxy contract that routes function calls to multiple logic contracts. The EIP-2535 specification gives three terms worth learning before anything else.

  • A diamond is the facade contract. It holds all the state (storage) and delegates execution to its facets.
  • A facet is a stateless contract (or deployed Solidity library) that holds external functions. It stores no data of its own but can read and write the diamond’s storage.
  • A loupe facet provides introspection: functions that report which facets and functions a diamond currently has.

The names come from the gem trade. A facet is a flat cut surface of a diamond, and a loupe is the small magnifying glass a jeweler uses to inspect one. In EIP-2535 you “cut” a diamond by adding, replacing, or removing functions, and you use the loupe functions to look inside it.

One detail matters for everything that follows: a deployed facet is not tied to a single diamond. The same facet can be shared by many diamonds, which keeps deployment costs down and makes common logic reusable.

Why Diamonds Exist: Size Limits and Upgrades

Two practical problems pushed this pattern into existence: the contract size ceiling and the need to change live code.

Since the Spurious Dragon hard fork in 2016, Ethereum has enforced a maximum contract size of 24,576 bytes — roughly 24 KB — under EIP-170. Push a contract past that and it simply won’t deploy. A diamond sidesteps the limit because its logic lives in separate facets. The diamond itself has no size cap, so a system can hold far more functionality than any single contract could.

The second problem is change. A normal contract is immutable once deployed. Diamond proxies make a contract upgradeable at the granularity of individual functions — you can add a new function, replace a buggy one, or remove an obsolete one, all in a single transaction, without touching the parts that already work.

Nick Mudge, the standard’s author, argues the organizational benefit outlasts the original motivation:

“Even if the 24KB max size contract limitation was removed I would still use EIP-2535 Diamonds because it provides a systematic way to organize, upgrade and extend a smart contract system.”

That is the honest pitch. Diamonds are less about squeezing under a byte limit and more about running a large contract system you can reason about and change over years.

How a Diamond Routes a Function Call

When you call a function on a diamond, the diamond’s fallback function decides which facet runs it.

The Fallback Function and delegatecall

The diamond keeps an internal mapping from a function selector — the first four bytes of the call data — to a facet address. On any incoming call, the fallback looks up the selector, finds the facet, and executes the function there with delegatecall.

delegatecall is the key. It runs the facet’s code in the diamond’s context, so msg.sender and msg.value stay the same and only the diamond’s storage is read and written. The facet supplies the logic; the diamond owns the data. A simplified call looks like this:

  1. A user sends a transaction to the diamond’s address.
  2. The fallback reads the function selector from the call data.
  3. It finds the facet mapped to that selector, and reverts if none exists.
  4. It delegatecalls the function on that facet.
  5. Any return value or revert passes straight back to the caller.

Shared Storage: Diamond Storage and AppStorage

There’s a catch with proxies. Solidity’s default storage layout wasn’t built for them, so facets can’t just declare state variables and expect them to line up. EIP-2535 doesn’t mandate one storage layout, but two patterns have become standard: Diamond Storage and AppStorage.

Both work by placing a struct at a fixed, known storage position. Facets that use the same struct at the same position read and write the same data, which is how independent facets safely share state inside one diamond. Get this wrong and two facets can collide on the same slot, so storage layout is where diamond bugs tend to hide.

Changing a Diamond: diamondCut and the DiamondCut Event

Upgrades happen through one function, diamondCut, and every change is recorded by one event, DiamondCut.

A cut is described by an array of FacetCut structs. Each struct names a facet address, an action, and the function selectors affected:

solidity

enum FacetCutAction { Add, Replace, Remove }

struct FacetCut {
    address facetAddress;
    FacetCutAction action;
    bytes4[] functionSelectors;
}

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

The three actions are what they sound like: Add wires new selectors to a facet, Replace points existing selectors at a new facet, and Remove deletes them. Doing all of this in a single transaction is deliberate — splitting an upgrade across several transactions is how diamonds get left in a half-updated, corrupt state.

The _init and _calldata arguments handle setup. After the selectors change, the diamond delegatecalls _calldata on _init to initialize any new storage the upgrade needs. If _init is the zero address, that step is skipped.

Every add, replace, or remove must emit a DiamondCut event. That event is the diamond’s public upgrade history. Anyone can replay it to see exactly how a contract’s functions changed over time, which is a real transparency advantage over a private, mutable backend.

How to Inspect a Diamond

Because a diamond’s verified source code doesn’t list the functions its facets provide, EIP-2535 standardizes four read-only “loupe” functions so software can discover them:

solidity

function facets() external view returns (Facet[] memory);
function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory);
function facetAddresses() external view returns (address[] memory);
function facetAddress(bytes4 _functionSelector) external view returns (address);

Together they answer every structural question about a diamond: which facets it has, which selectors each facet serves, and which facet handles a given function. Wallets and dashboards call them to build an accurate picture of a contract they didn’t deploy.

You don’t have to call them by hand. Louper, the Ethereum Diamond Inspector, is an open-source web tool that reads a diamond’s facets and lets you interact with its functions. Inspector Facet does the same from the command line and can build an audit log of a diamond’s cuts. Both lean on the loupe functions and the DiamondCut event underneath.

Who Uses Ethereum Diamonds, and When They Fit

Diamonds suit large, long-lived systems that need to keep changing. The cross-chain bridge aggregator LI.FI routes its logic through facets behind a single diamond, and the Beanstalk stablecoin protocol is built on the pattern too. Both are cases where functionality keeps expanding and a single 24 KB contract was never realistic.

They are overkill for small, single-purpose contracts. A token or a simple vault gains little from facets and inherits real complexity: more moving parts, trickier storage, and a hard dependency on tooling to see what’s deployed.

If all you need is upgradeability, diamond proxies aren’t the only option. Here’s how the pattern compares with the single-implementation proxies many teams reach for first.

Diamond (EIP-2535)Single-implementation proxy (Transparent / UUPS)
Logic behind the addressMany facetsOne implementation contract
Code size limitEffectively none~24 KB per implementation
Upgrade granularityPer function (add / replace / remove)Swap the whole implementation
Built-in introspectionStandard loupe functionsNone standardized
ComplexityHigherLower

The decision usually comes down to size and granularity. If your system fits in one implementation and you upgrade it wholesale, a standard proxy is simpler. If it’s outgrowing 24 KB or you want to upgrade pieces independently, a diamond earns its complexity.

The Risks: Access Control Is Your Job

The most important thing EIP-2535 does not do is handle security for you. The standard is explicit that ownership and authentication are not part of it — that’s left entirely to the implementer.

That matters because diamondCut allows arbitrary execution with full access to the diamond’s storage through delegatecall. Whoever can call it can rewrite the contract. If that permission is weak, the diamond is only as safe as the weakest path to diamondCut.

Beanstalk is the cautionary tale. In April 2022 an attacker drained about $182 million from the protocol, and Beanstalk was built on the diamond pattern — but the exploit was a governance flaw, not a flaw in EIP-2535. A flash loan let the attacker buy a temporary supermajority of votes and push through a malicious proposal that moved funds out. The diamond did exactly what it was told; the access control around it was the problem.

A few other risks are worth keeping in view:

  • Function selector clashes. Two different signatures can hash to the same four-byte selector. A correct diamondCut implementation rejects selectors that already exist, which prevents accidental overwrites.
  • selfdestruct. The standard warns against it in facets — misused, it can delete a facet or the diamond.
  • Opacity by default. A block explorer shows only the diamond’s own source, not its facets’ functions. Without the loupe functions or a tool like Louper, a diamond is hard to audit, so inspect before you trust one.

Reading and Interacting With Diamonds via NOWNodes

Everything you do with a diamond — calling a loupe function, reading its DiamondCut history, sending a transaction to one of its facets — is a request to the Ethereum network at the diamond’s single address.

At NOWNodes, we run the Ethereum infrastructure that sits behind those requests. You point your application at one of our Ethereum endpoints and query any diamond directly. An eth_call to facets() or facetAddress(selector) returns a diamond’s current structure, and an eth_getLogs query filtered to the DiamondCut event rebuilds its full upgrade history. Pulling that history from a contract’s first block needs archive-level data, which is available on supported Ethereum access.

The point is practical: inspecting or integrating with a diamond doesn’t require you to maintain your own Ethereum infrastructure. NOWNodes provides the connection; your code handles the diamond logic. Our products and limits can change, so check the current Ethereum pages before you build against specific features.

When Diamonds Make Sense

An Ethereum diamond gives you one stable address, a contract system with no size ceiling, and upgrades you can apply function by function with a public, event-backed history. That’s a strong fit for protocols that will keep growing and changing, and a poor fit for small contracts that won’t.

The trade-off is responsibility. EIP-2535 hands you the architecture but not the guardrails — access control, storage layout, and upgrade discipline are all on you, and that’s where diamonds succeed or fail. Learn the loupe functions, restrict diamondCut carefully, and inspect any diamond before you interact with it.

FAQ

Can a diamond be made immutable?

Yes. A diamond is only upgradeable while it keeps a working diamondCut function. Omit it, or remove the ability to call it, and the diamond becomes immutable — its functions can no longer be added, replaced, or removed. Some projects launch upgradeable and then lock the diamond down once the code is stable.

What’s the difference between EIP-2535 and EIP-1538?

EIP-1538, the Transparent Contract Standard, was the earlier version of the same idea, also by Nick Mudge. EIP-2535 is its successor: it keeps the multi-facet proxy concept but uses function selectors and adds the standardized loupe functions and DiamondCut event that make diamonds work cleanly with tooling.

Do diamonds add gas overhead?

Routing a call through the fallback and delegatecall adds a small amount of gas versus calling a plain contract directly. In practice it’s often offset. A diamond can bundle multiple operations into one transaction and read storage directly instead of calling across separate contracts, which can save more gas than the routing costs.

If a block explorer only shows the proxy, where is a diamond’s real code?

Each facet is its own deployed, independently verifiable contract. The diamond’s verified source won’t list them, so you use the loupe functions — or a tool like Louper that calls them for you — to map selectors to facet addresses and read each facet’s verified code.

Is EIP-2535 production-ready?

EIP-2535 is a Final Ethereum standard with multiple reference implementations and several independent security audits of diamond code. It’s used in production today. As with any upgradeable contract, its safety in a given project depends on that project’s access control and audits, not on the standard alone.