OpenZeppelin Upgrades

repository·master·Indexed 20 days ago

https://github.com/openzeppelin/openzeppelin-upgrades

Plugins for Hardhat and Foundry designed to simplify the deployment and management of upgradeable smart contracts on Ethereum. It supports Transparent Proxy and UUPS patterns, providing tools for deploying proxies, upgrading implementations, and managing beacons. The library offers dedicated support for both ethers and viem, and includes integration for Defender to propose upgrades and verify deployments.

Tokens
56.2K
Snippets
168
Records
233
Agent score
68%

What's inside openzeppelin-upgrades

  1. Choose the correct Upgrades library for your version

    master

    Select the library based on your OpenZeppelin Contracts version and whether you need safety validations or Defender support.

    VersionValidations/DefenderImport Path
    v5Upgrades (Validations + Defender)import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
    v5UnsafeUpgrades (No validations)import {UnsafeUpgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
    v4LegacyUpgrades (Validations + Defender)import {Upgrades} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol";
    v4UnsafeUpgrades (No validations)import {UnsafeUpgrades} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol";
  2. What it means for a contract to be upgrade safe

    master

    To be upgrade safe, a contract must follow specific rules to ensure compatibility with the proxy pattern:

    1. No Constructors: Contracts cannot use constructor. Instead, use an initialize function. It is highly recommended to use the Initializable base contract from @openzeppelin/contracts-upgradeable and the initializer modifier to ensure the function can only be called once.
    2. Avoid Dangerous Operations: For security and compatibility, contracts should avoid using selfdestruct or delegatecall unless they are specifically guarded.
    3. Storage Layout Compatibility: When upgrading, you can change the code but cannot modify existing state variables. You may only append new state variables after the existing ones to maintain a compatible storage layout.
    import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
    
    contract MyContract is Initializable {
      uint256 value;
    
      function initialize(uint256 initialValue) public initializer {
        value = initialValue;
      }
    }
  3. How deployProxy and upgradeProxy work

    master

    The plugins automate the management of upgradeable deployments through high-level functions.

    deployProxy workflow:

    1. Validation: Checks that the implementation contract is upgrade safe.
    2. Implementation Deployment: Deploys the implementation contract. The Hardhat plugin optimizes this by checking if a contract with the same bytecode is already deployed to avoid redundant deployments.
    3. Proxy Setup: Creates the proxy contract and initializes it, deploying a proxy admin if required.

    upgradeProxy workflow:

    1. Validation: Ensures the new implementation is upgrade safe and compatible with the existing implementation.
    2. Implementation Deployment: Deploys the new implementation contract (skipping if bytecode already exists in Hardhat).
    3. Proxy Upgrade: Updates the proxy to point to the new implementation.
  4. Why UUPS proxies require `upgradeTo` or `upgradeToAndCall`

    master

    When using the UUPS (Universal Upgradeable Proxy Standard) pattern (kind: 'uups'), the upgrade logic resides in the implementation contract rather than the proxy. Therefore, your implementation contract must include at least one of these public functions:

    • upgradeTo(address newImplementation)
    • upgradeToAndCall(address newImplementation, bytes memory data)

    If these are missing, you will permanently disable the ability to upgrade the contract. The recommended approach is to inherit the UUPSUpgradeable contract from OpenZeppelin Contracts, which provides these functions and includes on-chain safety checks.

    import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
    
    contract MyContract is Initializable, ..., UUPSUpgradeable {
        ...
    }
  5. How Transparent Proxies prevent function clashes

    master

    A common issue in proxy patterns is a function clash, where a function in the logic contract has the same name and signature as a function in the proxy (e.g., both have upgradeTo(address)).

    OpenZeppelin Upgrades uses the Transparent Proxy pattern to resolve this. The proxy decides which contract to call based on the msg.sender:

    1. If the caller is the Admin (the address with rights to upgrade the proxy): The proxy only responds to its own administrative functions and does not delegate calls to the logic contract. This prevents the admin from accidentally triggering logic functions that clash with proxy functions.
    2. If the caller is any other address: The proxy always delegates the call to the logic contract, regardless of whether the function name matches a proxy function.

    Note for Developers: When using OpenZeppelin Upgrades, the ProxyAdmin contract is automatically used as the admin. This means you can interact with the proxy from your standard accounts without being blocked by the transparent proxy logic.

  6. Track implementation contracts in Hardhat

    master

    The Hardhat plugin maintains a record of all deployed implementation contracts in an .openzeppelin folder at your project root.

    • Each network has its own JSON file in this folder.
    • Best Practice: Commit these files to source control for all networks except development networks (which may appear as .openzeppelin/unknown-*.json).
  7. Understand the Fail-Closed Upgrade-Safety Validation Principle

    master

    OpenZeppelin Upgrades operates on a Fail-Closed principle for all critical operations. This means that if any part of the upgrade-safety validation process is compromised, the operation must fail loudly and immediately rather than proceeding with potentially unsafe defaults.

    Critical Operations Subject to Validation

    • deploy* (e.g., deployProxy)
    • upgrade* (e.g., upgradeProxy)
    • validateImplementation
    • validateUpgrade
    • forceImport

    When Validation Fails

    The system will trigger a hard failure if validation data is:

    • Missing or incomplete.
    • Outdated.
    • Unable to be matched to the contract (e.g., due to a missing or malformed build-info companion file or absent storageLayout output).
    • Mismatched in name or encoding.

    Bypassing Validation

    Validation can only be bypassed through explicit, documented, and user-facing opt-out mechanisms. Examples include:

    • unsafeAllow* methods
    • unsafeSkipStorageCheck
    • unsafeAllowRenames
    • Specific NatSpec annotations

    Note: A silent skip of validation is considered a critical failure of the tool's safety guarantees.

  8. Use Namespaced Storage Layout (ERC-7201)

    master

    Starting with version 5.0 of OpenZeppelin Contracts, you can use Namespaced Storage Layout (ERC-7201) to avoid storage layout errors.

    This involves placing all storage variables of a contract into one or more structs and annotating them with @custom:storage-location erc7201:<NAMESPACE_ID>. Each namespace ID must be unique within the contract and its hierarchy.

    Requirements:

    • Requires Solidity version 0.8.20 or higher for plugin validation.
    • The OpenZeppelin Upgrades plugins will automatically detect and validate these namespaces during upgrades.
  9. How the Proxy Upgrade Pattern works

    master

    The proxy pattern enables smart contract upgrades by separating the contract's state from its logic.

    1. Proxy Contract: A permanent wrapper that users interact with. It holds the contract's state (storage) and acts as the entry point.
    2. Implementation (Logic) Contract: Contains the actual business logic. This contract can be replaced by a new version without changing the proxy's address.

    When a user calls the proxy, the proxy uses the delegatecall opcode to execute the logic contract's code within the context of the proxy's own storage. This means the logic contract's own state is ignored, and it operates directly on the proxy's state.

  10. Manage ownership of Transparent Proxies

    master

    Transparent proxies use an admin address to manage upgrades. By default, this is a ProxyAdmin contract deployed by the plugin.

    Admin vs Owner

    • Admin: Has rights to upgrade the proxy but cannot interact with the implementation contract functions (to prevent function clashes).
    • Owner: The address that has rights to operate the ProxyAdmin contract itself.

    Changing the Owner

    • Hardhat: Call admin.transferProxyAdminOwnership.
    • Foundry: Call transferOwnership on the ProxyAdmin contract.
    WARNING

    Do not reuse an already deployed ProxyAdmin. Before @openzeppelin/contracts version 5.x, transparent proxies required an initialAdmin. Reusing a ProxyAdmin will disable upgradeability in your contract.

  11. Use the UnsafeUpgrades library for Forge tests

    master

    The UnsafeUpgrades library is a lightweight alternative to Upgrades designed for Forge tests and coverage. It skips all safety validations.

    When to use:

    • When running forge coverage.
    • When you have already instantiated implementation contracts and want to avoid the overhead of validation.
    • When you do not require --ffi or clean compilations between runs.

    Warnings:

    • NOT recommended for Forge scripts.
    • Does not validate upgrade safety or implementation compatibility.
    • Not supported for OpenZeppelin Defender deployments.

    Key Differences:

    • Upgrades uses contractName (string) to find and validate contracts.
    • UnsafeUpgrades uses impl (address) for the implementation, assuming it is already deployed.
    import { UnsafeUpgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol";
    
    // Example: Deploying without validation
    address proxy = UnsafeUpgrades.deployUUPSProxy(implAddress, initializerData);
  12. Use UnsafeUpgrades for Forge tests and coverage

    master

    The UnsafeUpgrades library allows managing upgradeable contracts in Forge tests without running validations.

    When to use:

    • When running forge coverage.
    • When you want to avoid the overhead of --ffi or clean compilations before every run.

    WARNINGS:

    • No Validations: It does not check if contracts are upgrade-safe or if implementations are compatible. Use Upgrades if you need safety checks.
    • Not for Scripts: It is not recommended for use in Forge scripts.
    • Not for Defender: Not supported for OpenZeppelin Defender deployments.
    • Requirement: Implementation contracts must be instantiated first.
    import { UnsafeUpgrades } from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol";
    
    // Example: Upgrading to a known address without validation
    UnsafeUpgrades.upgradeProxy(proxy, newImplAddress, data);