In our previous lesson, you successfully upgraded a smart contract on a live testnet, seeing firsthand how the proxy pattern preserves state while allowing logic to change. We concluded by noting that this entire powerful process was controlled by a single private key—an "all-powerful admin." This raises a critical question: what are the risks of holding such power?
This lesson directly addresses that question. We will explore the security considerations and trade-offs inherent in both the mechanics of upgradeability and the centralized administrative roles that govern them. For anyone building systems for tokenized money or securities, where trust and predictability are paramount, understanding these risks is not just an academic exercise; it's a fundamental requirement for responsible development.
We will dissect this topic into two main parts: first, the technical pitfalls of the upgrade mechanism itself, and second, the operational and governance risks of the admin roles that control these upgrades.
1. The Dangers of the Upgrade Mechanism
The proxy pattern is powerful, but it introduces specific classes of vulnerabilities that don't exist in immutable contracts. These are subtle and can have catastrophic consequences if overlooked. Your experience with data warehousing and managing schema evolution will provide a good mental model for understanding the first and most critical risk: data corruption.
Storage Collisions
In Solidity, state variables are laid out in storage slots sequentially, starting from slot 0. When a proxy delegatecalls to an implementation, the implementation's code operates on the proxy's storage. A storage collision occurs if the layout of state variables in a new implementation version is incompatible with the previous one, causing data to be misinterpreted or overwritten.
Consider this scenario:

To avoid this, you must follow strict rules when modifying state variables in an upgradeable contract:
- Never reorder existing state variables.
- Never remove an existing state variable. You can deprecate it in your logic, but it must remain in the code to preserve the storage layout.
- Only append new state variables at the end of the contract's variable declarations.
- Be careful with inheritance. Adding a new base contract with state variables can insert them before existing variables, changing the entire layout.
To manage this, OpenZeppelin contracts often include a __gap array, which reserves a block of storage slots for future use, allowing you to "fill the gap" in later versions without appending.
A second type of collision can happen between the proxy and the implementation. If the proxy contract itself defines state variables (e.g., the implementation address) and the logic contract also declares variables at the same slots (e.g., slot 0), the logic contract could accidentally overwrite the proxy's critical data. EIP-1967, which you've been using via the OpenZeppelin plugins, solves this by defining specific, pseudo-random storage slots for the proxy's data, ensuring it stays clear of the implementation's typical slot-0-onward layout.
The following video from OpenZeppelin provides an excellent deep dive into this topic.
Security in Upgrades of Smart Contracts
This video by an OpenZeppelin security researcher explains common pitfalls in upgradeable contracts. We'll focus on the section discussing storage layout.
Watch the segment from storage layout. Pay close attention to the visual explanation of how proxy and implementation slots can clash, how EIP-1967 mitigates this, and the detailed rules for preserving storage layout between contract versions.
Initializer Flaws
As you know, upgradeable contracts use an initialize function instead of a constructor. This function, being a regular public function, introduces its own set of risks if not handled correctly.
- Uninitialized Contracts: If you deploy a proxy and its implementation but fail to call
initializein the same transaction, an attacker can front-run you. They can callinitializethemselves, potentially setting themselves as the owner and taking full control. This was a key factor in the famous 2017 Parity Multisig Wallet hack. - Re-Initialization: Imagine an
initializefunction that sets the contract owner. If you later renounce ownership, an attacker could callinitializeagain to claim ownership. To prevent this, initializers must be protected. OpenZeppelin'sinitializermodifier provides this protection by ensuring a function can only be run once.
Security in Upgrades of Smart Contracts
Let's return to the OpenZeppelin video to see a breakdown of initializer vulnerabilities.
Watch the section from initializers. The presenter walks through several incorrect initializer patterns and explains how inheritance complicates initialization, requiring manual calls to the parent initializers.
Bricked Upgrades
The UUPS (Universal Upgradeable Proxy Standard) pattern places the upgrade logic within the implementation contract itself. This is more gas-efficient but introduces a significant risk: if you upgrade to a new implementation that forgets to include the upgrade logic, you can never upgrade again. The proxy becomes "bricked"—its logic is frozen forever. OpenZeppelin's UUPS implementation has safeguards to prevent this, but it's a critical pattern-specific risk to be aware of.
2. The Risks of Centralized Admin Roles
The technical risks above are critical, but perhaps the more profound risk comes from the entity that has the power to authorize an upgrade. In your testnet deployment, this was a single account. This represents a highly centralized point of failure.
The Trail of Bits blog offers an excellent framework for thinking about the maturity of a project's access controls, which we'll use to structure this discussion.
Maturing your smart contracts beyond private key risk
This article from Trail of Bits, a leading security firm, provides a maturity model for smart contract access controls. It's an essential read for understanding how to manage administrative power securely.
Please read the article from the beginning up to the end of Level 3. As you read, focus on understanding the weaknesses of each level and the improvements offered by the next: Level 1: The single EOA controller you used in the last lesson. Level 2: The introduction of a multisig wallet. Level 3: The addition of timelocks and role separation (Principle of Least Privilege).
Let's summarize the key trade-offs at each level of maturity:
-
Level 1: Single Admin Key. This is the simplest but most dangerous model. The trade-off is maximum convenience for maximum risk. If that one key is compromised, the entire system is lost. This is unacceptable for any system managing significant value.
-
Level 2: Centralized Multisig. Using a multisig wallet (like Gnosis Safe) requires M-of-N parties to approve an action. This distributes trust and prevents a single point of failure. The trade-off is increased operational complexity for a significant reduction in single-key risk. However, it's still a single point of control; if the multisig threshold is met, the action is executed immediately, and the multisig can still do everything.
-
Level 3: Timelocks and Role Separation. This is the standard for mature DeFi protocols.
- Timelocks introduce a mandatory delay between when an action is approved and when it can be executed. This gives users a window to react, withdraw funds, or have a "Cancel Guardian" role veto a malicious proposal.
- Role Separation applies the Principle of Least Privilege. Instead of one "god mode" admin, you create distinct roles for different functions, each with its own security parameters (multisig threshold, timelock delay).

This model should feel familiar from your work with enterprise software, where Role-Based Access Control (RBAC) is a cornerstone of security. The trade-off here is a significant increase in design complexity for a robust, defense-in-depth security posture.
3. The Ultimate Trade-off: Upgradeability vs. Immutability
So far, we've assumed upgradeability is desirable. But is it always? This brings us to the final and most fundamental trade-off.
-
Upgradeability offers flexibility. You can fix bugs, respond to security threats, and add new features. However, it introduces technical complexity and, most importantly, requires users to trust the admin/governance process not to act maliciously. Every upgrade carries risk.
-
Immutability offers finality. The rules are set in stone. Users have a perfect guarantee that the contract logic will never change. This is the highest form of trustlessness. However, it is completely inflexible. A bug is permanent. New features require deploying an entirely new version of the protocol and convincing users to migrate.
Many mature projects adopt a hybrid approach. The core logic of a token or a lending protocol might be made immutable ("ossified") after a period of battle-testing to provide maximum trust. Meanwhile, more peripheral contracts that handle things like fee parameters or asset listings might remain upgradeable.
This decision is not purely technical; it's a strategic choice about what promises you are making to your users and where you want them to place their trust—in the code, or in the people who control it.
Conclusion
You now have a framework for analyzing the profound security implications of making a smart contract upgradeable. This power is not to be taken lightly, especially in the context of tokenized money where stability and trust are the entire product.
Key Takeaways:
- Technical Risks: Upgrades introduce dangers like storage collisions, initializer flaws, and the potential for bricking a contract, which require disciplined coding practices to mitigate.
- Administrative Risks: Centralized admin keys are a massive liability. Maturing your access control model with multisigs, timelocks, and role separation is essential for any serious project.
- The Flexibility vs. Trust Trade-off: The decision to make a contract upgradeable is a fundamental choice between retaining flexibility and providing the absolute trust guarantee of immutability.
In our next module, we will begin building our fiat-backed stablecoin. Armed with the knowledge from this lesson, you'll understand why we will use tools like OpenZeppelin's AccessControl and Pausable to implement carefully scoped admin and guardian roles. You'll be putting this security theory directly into practice.