Although smart contract code is immutable, OpenZeppelin Upgradeable Contracts afford developers the ability to deploy improvements, patches, and bug fixes.
A great deal of testing, research, and community engagement went into the adoption of the unstructured storage pattern used by OpenZeppelin upgrades, and it remains a cornerstone of the most secure implementations of the proxy pattern.
Under the Hood
OpenZeppelin Upgradeable Contracts replicate the functionality of the non-upgradeable version. Part of the process involved with making them upgradeable is that they are processed via a special transpiler, which handles contract renaming, the addition of storage gaps to avoid clashes, the removal of immutable variables, and the transformation of constructors into initializers.
A crucial point to emphasize regarding the security of upgradeability involves the prevention of storage clashes. To this end, OpenZeppelin proxies are fully compliant with EIP 1967, which standardizes the way that storage slots are allocated to prevent the risk of storage collision.
OpenZeppelin Upgradeable Contracts use the proxy pattern for upgradeability. By separating the contract the user interacts with from the contract holding the contract’s functionality, the code can effectively be “upgraded” by deploying a new implementation and pointing the proxy to that new address. The user is able to interact with the contract as before, benefiting from the functionality of the new implementation.
Using the UUPS standard adds a function to the implementation contract that contains this upgrade mechanism. Future contract upgrades are free to modify, remove, or add functions and to add storage variables in previously unused locations, but not to interfere with the layout of previous variables in storage. Another important factor for consideration is that because the UUPS standard places the upgrade function in the implementation, it is able to be modified or even irreversibly removed.
To help ensure secure contract upgrades, it is recommended to deploy using the OpenZeppelin Upgrades plugin (available for Hardhat or Truffle), which validates that the contract adheres to the necessary rules for upgradeability and that the storage layout is correctly preserved.
How to Safely Implement Upgradeability
Put simply: Use the standard solutions.
Due to the complexity involved with EVM storage, there are many restrictions that must be accounted for when making smart contracts upgradeable. For this reason, it is not advisable to modify the upgrade mechanism of OpenZeppelin Contracts. It is imperative for developers to thoroughly research the implications of modifying the standard implementation before doing so. Upgradeable contracts have proven to be secure when adhering to the contract standard.
Another important factor when considering smart contract upgradeability is the question of who has access to deploy an upgrade. Using
AccessControl to assign the upgrade function to a trusted party can provide security and transparency. Users should be made aware of such access control and should be able to know that it is being used in accordance with how it has been documented.
Adherence to the OpenZeppelin standard makes it easier for developers, auditors, and white hats to spot bugs, and it also lowers friction for continued iteration, making it possible to build common tools to address developer needs. The DeFi ecosystem thrives when protocols are built to prioritize interoperability.
Teams can keep users in the loop by leveraging the automation capabilities of OpenZeppelin Defender, and can easily set up an automated Sentinel notification feed in the community’s Discord or Telegram channel to notify users when an upgrade takes place.