Introduction
Smart contracts were once hailed for their immutability. The idea that code, once deployed, could never change was both a feature and a flaw. As protocols matured and user needs evolved, developers realized the limitations of frozen logic. Upgradability became the answer. But it came with a cost. Each upgrade mechanism introduced a new layer of complexity, a new class of bugs, and a new surface for attack.
We’ve spent the last few years auditing smart contracts across Ethereum, Solana, Cosmos, and other ecosystems. One recurring theme has been the creative —and sometimes dangerous —ways developers try to make their contracts more flexible. This blog dives deep into the different upgradability patterns that exist, how they’re implemented under the hood, and where they go wrong.
The Tradeoff: Flexibility vs Finality
Immutability sounds perfect in theory. But no matter how well-written your smart contract is, bugs happen. Business logic evolves. Governance changes. Without a way to upgrade, a single mistake in an immutable contract can lock millions of dollars forever. On the flip side, the ability to upgrade can undermine trust in decentralization and open the door to rug pulls, backdoors, or unintended bricking.
Finding the right balance is key. But to strike it, you need to understand the design space of upgradability.
Upgradability in EVM-Based Chains
Proxy-Based Patterns
The most widely adopted approach in Ethereum and other EVM chains is proxy delegation. Here, a user interacts with a proxy contract that forwards logic execution to a separate implementation contract using delegatecall.
There are a few common variants:
Transparent Proxy
Uses an admin account to authorize upgrades. Only the admin can call upgrade functions; others are forwarded to the logic contract. The biggest risk here is accidentally exposing upgrade functions to the public or messing up storage slots.
UUPS (Universal Upgradeable Proxy Standard)
Instead of relying on an external admin proxy, the implementation contract contains the upgrade logic. It’s more gas-efficient and modular, but also riskier if the upgrade authorization logic isn’t properly protected.
Beacon Proxy
Designed for scaling use cases where multiple proxies share the same implementation logic. A beacon contract stores the address of the current logic contract. When it’s updated, all proxies automatically point to the new implementation. This pattern is harder to manage securely due to the shared logic risk.
Storage Layout Gotchas
Upgradable contracts in EVM environments require precise control over storage layout. Any mismatch between the proxy and the logic contract’s variables can cause catastrophic corruption. Solidity doesn’t enforce layout consistency, so one misplaced variable can silently break everything.
Solana: Upgrades via Program Authorities
Solana smart contracts (called programs) are compiled to BPF bytecode and deployed to the chain with a designated “upgrade authority.” This account can deploy a new version of the program at any time if it holds the necessary key.
While Solana’s upgrade model is simpler in some ways, it comes with centralized risk. If the upgrade authority is a single hot wallet, the entire contract can be swapped out by anyone who compromises it. We’ve seen projects delay decentralization for too long, and some never revoke their authority. That defeats the entire purpose of using Solana’s secure, performant runtime.
Solana also introduces risks around program-derived addresses (PDAs). Some developers use bumps incorrectly or inconsistently across upgrades, causing state conflicts or loss of access to critical data accounts.
Cosmos and CosmWasm: Governance-Driven Migrations
Cosmos contracts built with CosmWasm allow explicit migration through a migrate entry point. The beauty of this pattern is that the upgrade logic is part of the contract itself. When an upgrade is proposed and approved via on-chain governance, the new WASM bytecode is uploaded, and execution continues.
Because the state is preserved automatically and migration logic is defined explicitly, the risks of storage mismatch are lower. However, incorrect migration code or permission errors in the governance layer can still lead to issues.
One common problem we’ve seen is a lack of validation in migration logic; contracts often assume certain values exist in storage without checking, leading to panics or broken invariants after the upgrade.
Substrate/Polkadot: Runtime-Level Upgrades
In Polkadot and other Substrate-based chains, the entire runtime can be swapped through a governance proposal. This allows for powerful, coordinated upgrades but introduces complexity. Rather than upgrading a single contract, you’re upgrading the whole chain logic.
For contracts written in Ink! (Substrate’s smart contract language), Upgrades aren’t native. You’d typically deploy a new contract and migrate state manually, unless you’ve built a custom upgrade mechanism. This gives developers flexibility, but places the burden of safety squarely on them.
Security Pitfalls Across Ecosystems
Regardless of the underlying chain, the risks fall into a few key buckets:
- Improper Access Control: Upgrading functions exposed to unauthorized users is a common critical issue. Whether it’s a leaked admin key in Ethereum or an unrevoked authority on Solana, control is everything.
- State Corruption: In EVM, this happens due to a storage layout mismatch. In CosmWasm, it’s usually a result of unsafe migration logic. In Substrate, it’s often due to developer-defined migration bugs.
- Bricking the Contract: A failed upgrade can leave the contract in an unusable state. For example, a UUPS contract that incorrectly disables the upgrade function can never be upgraded again. Or in Solana, accidentally revoking the upgrade authority before uploading the correct binary can freeze the system.
- Governance Loopholes: On chains that allow governance-controlled upgrades, poor quorum thresholds or vote manipulation can allow malicious actors to force upgrades that users never wanted.
Real-World Cases and Lessons Learned
- In 2022, a major Ethereum DeFi protocol pushed an upgrade that unintentionally overwrote critical storage variables, freezing billions in TVL until a hotfix was deployed.
- A Solana-based project lost access to its PDA vault because it incorrectly calculated bumps across versions.
- In Cosmos, misconfigured migration logic resulted in zeroed-out balances for some users after an upgrade, forcing a chain halt and manual recovery.
Every ecosystem has its stories. And the common theme is always the same: upgradability is powerful, but it’s rarely safe by default.
Designing Safer Upgrades
As security researchers, we tend to treat upgradeability as a privileged, dangerous operation. If you must use it, here’s what we recommend:
- Lock upgrade access behind a multisig or timelock contract.
- Always simulate your upgrade in a forked mainnet environment.
- Explicitly validate storage layouts between versions.
- Document every upgrade path with expected outcomes and failure scenarios.
- If your ecosystem supports governance upgrades, enforce rigorous quorum and voting periods.
And above all: have a rollback plan. Even if you think the upgrade is bulletproof.
Final Thoughts
Upgradability is not a flaw in blockchain systems. It’s a necessity in many production protocols. But it’s also one of the most dangerous features a contract can have. Whether you’re using delegatecall in Ethereum, upgrading authorities in Solana, or migrating hooks in Cosmos, one thing remains true: you’re trading immutability for responsibility.
It’s not just about writing upgradeable contracts. It’s about designing upgrade paths that don’t compromise user trust, decentralization, or safety.
And in that, the real pattern isn’t just code, it’s caution.
