Understanding Immutability on the EVM: Myth, Reality, and the Diamond Standard
by Kyle Thornton, CTO
Today, August 5th, one of our clients launched a protocol that leans heavily on the Diamond Proxy pattern (EIP-2535).
As a consulting firm that helps Web3 teams navigate legal, regulatory, and technical challenges, we often work with projects that need to balance flexibility with decentralization. This client wanted to move fast, adapt quickly, and still make credible commitments toward trustlessness. For them, the Diamond Proxy was the right fit.
Working on this engagement pushed me deep into the world of Diamonds. And I quickly discovered something frustrating. Very few tools existed to inspect or reason about Diamond contracts. We ended up building internal tools from scratch to decode function selectors, trace upgrades, and map facets to storage layouts.
Here’s an example of what I built:
While this seems like chaos, just imagine what it looked like in Etherscan, where each of those purple circles is a separate contract address. Reasoning about which functions exist on that SpaceFactory diamond, and where their implementation lives, suddenly became feasible.
Fun fact, Etherscan eventually added some basic support for Diamond contracts after our work was already complete. Even now, good luck making sense of the implementation with Etherscan alone. Give it a try if you'd like: SpaceFactory Diamond.
The deeper I got, the more I realized that even seasoned industry pros have misunderstandings about what "immutability" really means in the context of Ethereum. This article is my attempt to make a relatively niche pattern digestible, explain why it matters, and provide a concrete mental model for teams thinking about progressive decentralization.
Immutable Does Not Mean Unchangeable
It's true that once a smart contract is deployed to Ethereum, the code at that address is locked forever. But that doesn't mean the behavior of the contract is fixed.
Immutability, in this case, only means the bytecode at the address cannot change. But contracts can expose variables and methods that allow internal state to evolve. The ability to update behavior comes down to how the contract is designed.
This is where upgradeable proxies come into play.
Why True Immutability Is Hard
To launch something truly immutable, you need to get everything right before deployment. That means:
- All logic perfectly tested
- Tokenomics locked and simulated across usage scenarios
- Product-market fit nailed down with no major future changes expected
If you can do all that, congrats. That’s some deep-space engineering. It’s like launching a probe: once it's gone, it’s gone. You don’t get to send a patch.
The good news is most of us don’t need to be that hardcore to ship a functional MVP. Upgradability is what lets early teams iterate, stay responsive, and move toward stability at their own pace.
How Proxies Enable Upgrades
The standard proxy setup consists of two contracts:
- An entrypoint that users interact with
- An implementation where all the actual logic lives
The entrypoint uses delegatecall
to forward all user calls to the implementation. This means it runs the implementation code but uses its own storage. So the entrypoint holds the data, and the implementation defines how to work with that data.
To upgrade, you just point the entrypoint at a new implementation. Users keep using the same address. Their funds and state don’t need to migrate.
That’s a huge win for developers. But it introduces serious trust assumptions for users.
The Trust Problem
Let’s say you deposit ETH into a contract after reviewing the implementation and confirming that the withdraw()
method works as expected.
Later, the contract owner upgrades the implementation and removes withdraw()
. Now your ETH is stuck.
Even if the team is acting in good faith, any change to critical logic introduces risk. And as a user, you don’t get much protection unless the contract's upgradeability is locked down.
Toward Progressive Decentralization
Eventually, credible projects want to minimize or eliminate the ability to change core logic. That’s the idea behind progressive decentralization.
You start with an upgradeable design so you can iterate. But over time, you lock down the most important parts. In the standard proxy model, that’s a binary choice. You either keep upgrade access or you burn it forever by setting the admin to the zero address.
The problem is that this approach lacks nuance. What if you want to lock down just one critical function while continuing to iterate on others?
That’s where Diamonds come in.
Diamonds: A More Granular Approach
The Diamond Proxy (EIP-2535) improves on the standard proxy by letting you split logic into facets. Each facet implements one or more functions. The Diamond contract itself holds a mapping of function selectors to facet addresses.
This architecture lets you:
- Upgrade individual functions
- Lock down some methods while leaving others open
- Provide strong guarantees about your public interface
"Enables zero, partial or full diamond immutability as desired, and when desired."
You might start by locking down a function like withdraw()
to build user confidence. Meanwhile, other functions remain upgradeable so you can continue improving your system.
Why Storage Still Matters
Here’s where it gets tricky. Even if you make a function immutable, the storage it touches is still shared with the rest of the Diamond.
As long as any part of the Diamond remains upgradeable, the storage layout remains mutable. That means your immutable function can be influenced by changes to shared state.
In other words, you can trust the function logic won't change, but you can't fully trust that the data it uses won't be tampered with—unless you’ve also locked down storage access.
This is an important distinction. What Diamonds offer is the ability to lock down interfaces, which makes it far easier for developers to build on top of your protocol. They can rely on a stable API without worrying that a future upgrade will pull the rug out from under their integration.
Users still need to trust the core team until the Diamond is fully locked down. But the path to decentralization becomes clearer and more deliberate.
Real-World Developer Benefits
Let’s say you’re building a protocol on top of another team’s smart contracts.
With a standard proxy, you have to assume the implementation could change at any moment. That makes your own code fragile.
With a Diamond, you can at least be sure that some function selectors are locked. If you depend on getPrice()
or withdraw()
, and those methods are marked immutable, you can build with confidence that your integration won’t suddenly break.
This improves composability, reduces integration risk, and helps protocols evolve while earning trust along the way.
Screenshots from the Field
Here’s some more glimpses of the custom diamond proxy analysis tooling:


TL;DR
- Ethereum contracts are immutable in code, but that doesn't guarantee unchanging behavior
- Upgradeable proxies allow changes, but trust assumptions are high
- Diamond proxies let you lock individual functions, offering more control and clarity
- Storage safety in Diamonds requires care, because shared state remains mutable until fully locked down
- Developers can build more confidently on top of Diamonds because the interface can be frozen
- Progressive decentralization is a journey, and Diamonds give you tools to take real steps in the right direction