Frequently Asked Questions

Can I change Solidity compiler versions when upgrading?

Yes. The Solidity team guarantees that the compiler will preserve the storage layout across versions.

Why am I getting the error "Cannot call fallback function from the proxy admin"?

This is due to the Transparent Proxy Pattern. You shouldn’t get this error when using the OpenZeppelin Upgrades Plugins, since it uses the ProxyAdmin contract for managing your proxies.

However, if you are using OpenZeppelin Contracts proxies programmatically you could potentially run into such error. The solution is to always interact with your proxies from an account that is not the admin of the proxy, unless you want to specifically call the functions of the proxy itself.

What does it mean for a contract to be upgrade safe?

When deploying a proxy for a contract, there are some limitations to the contract code. In particular, the contract cannot have a constructor, and should not use the selfdestruct or delegatecall operations for security reasons.

As a replacement for the constructor, it is common to set up an initialize function to take care of the contract’s initialization. You can use the Initializable base contract to have access to an initializer modifier that ensures the function is only called once.

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract MyContract is Initializable {
  uint256 value;
  function initialize(uint256 initialValue) public initializer {
    value = initialValue;
  }
}

Both plugins will validate that the contract you are trying to deploy complies with these rules. You can read more about how to write upgrade safe contracts here.

How can I disable some of the checks?

Deployment and upgrade related functions come with an optional opts object, which includes an unsafeAllow option. This can be set to disable any check performed by the plugin. The list of checks that can individually be disabled is:

  • state-variable-assignment

  • state-variable-immutable

  • external-library-linking

  • struct-definition

  • enum-definition

  • constructor

  • delegatecall

  • selfdestruct

  • missing-public-upgradeto

  • internal-function-storage

This function is a generalized version of the original unsafeAllowCustomTypes and unsafeAllowLinkedLibraries allowing any check to be manually disabled.

For example, in order to upgrade to an implementation that contains a delegate call, you would call:

await upgradeProxy(proxyAddress, implementationFactory, { unsafeAllow: ['delegatecall'] });

Additionally, it is possible to precisely disable checks directly from the Solidity source code using NatSpec comments. This requires Solidity >=0.8.2.

contract SomeContract {
  function some_dangerous_function() public {
    ...
    /// @custom:oz-upgrades-unsafe-allow delegatecall
    (bool success, bytes memory returndata) = msg.sender.delegatecall("");
    ...
  }
}

This syntax can be used with the following errors:

  • /// @custom:oz-upgrades-unsafe-allow state-variable-immutable

  • /// @custom:oz-upgrades-unsafe-allow state-variable-assignment

  • /// @custom:oz-upgrades-unsafe-allow external-library-linking

  • /// @custom:oz-upgrades-unsafe-allow constructor

  • /// @custom:oz-upgrades-unsafe-allow delegatecall

  • /// @custom:oz-upgrades-unsafe-allow selfdestruct

In some cases you may want to allow multiple errors in a single line.

/// @custom:oz-upgrades-unsafe-allow constructor state-variable-immutable
contract SomeOtherContract {
  uint256 immutable x;
  constructor() {
    x = block.number;
  }
}

You can also use the following to allow specific errors in reachable code, which includes any referenced contracts, functions, and libraries:

  • /// @custom:oz-upgrades-unsafe-allow-reachable delegatecall

  • /// @custom:oz-upgrades-unsafe-allow-reachable selfdestruct

Can I safely use delegatecall and selfdestruct?

This is an advanced technique and can put funds at risk of permanent loss.

It may be possible to safely use delegatecall and selfdestruct if they are guarded so that they can only be triggered through proxies and not on the implementation contract itself. A way to achieve this in Solidity is as follows.

abstract contract OnlyDelegateCall {
    /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    address private immutable self = address(this);

    function checkDelegateCall() private view {
        require(address(this) != self);
    }

    modifier onlyDelegateCall() {
        checkDelegateCall();
        _;
    }
}
contract UsesUnsafeOperations is OnlyDelegateCall {
    /// @custom:oz-upgrades-unsafe-allow selfdestruct
    function destroyProxy() onlyDelegateCall {
        selfdestruct(msg.sender);
    }
}

What does it mean for an implementation to be compatible?

When upgrading a proxy from one implementation to another, the storage layout of both implementations must be compatible. This means that, even though you can completely change the code of the implementation, you cannot modify the existing contract state variables. The only operation allowed is to append new state variables after the ones already declared.

Both plugins will validate that the new implementation contract is compatible with the previous one.

You can read more about how to make storage-compatible changes to an implementation contract here.

What is a proxy admin?

A ProxyAdmin is an intermediary contract that acts as the upgrader of a transparent proxy. Each ProxyAdmin is owned by the deployer address, or by the initialOwner address when deploying a transparent proxy from OpenZeppelin Contracts 5.0 or above. You can transfer the ownership of a proxy admin by calling transferOwnership.

What is an implementation contract?

Upgradeable deployments require at least two contracts: a proxy and an implementation. The proxy contract is the instance you and your users will interact with, and the implementation is the contract that holds the code. If you call deployProxy several times for the same implementation contract, several proxies will be deployed, but only one implementation contract will be used.

When you upgrade a proxy to a new version, a new implementation contract is deployed if needed, and the proxy is set to use the new implementation contract. You can read more about the proxy upgrade pattern here.

What is a proxy?

A proxy is a contract that delegates all of its calls to a second contract, named an implementation contract. All state and funds are held in the proxy, but the code actually executed is that of the implementation. A proxy can be upgraded by its admin to use a different implementation contract.

You can read more about the proxy upgrade pattern here.

Why can’t I use immutable variables?

Solidity 0.6.5 introduced the immutable keyword to declare a variable that can be assigned only once during construction and can be read only after construction. It does so by calculating its value during contract creation and storing its value directly into the bytecode.

Notice that this behavior is incompatible with the way upgradeable contracts work for two reasons:

  1. Upgradeable contracts have no constructors but initializers, therefore they can’t handle immutable variables.

  2. Since the immutable variable value is stored in the bytecode its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage.

In some cases immutable variables are upgrade safe. The plugins cannot currently detect these cases automatically so they will point it out as an error anyway. You can manually disable the check using the option unsafeAllow: ['state-variable-immutable'], or in Solidity >=0.8.2 placing the comment /// @custom:oz-upgrades-unsafe-allow state-variable-immutable before the variable declaration.

Why can’t I use external libraries?

At the moment, the plugins only have partial support for upgradeable contracts linked to external libraries. This is because it’s not known at compile time what implementation is going to be linked, thus making it very difficult to guarantee the safety of the upgrade operation.

There are plans to add this functionality in the near future with certain constraints that make the issue easier to address like assuming that the external library’s source code is either present in the codebase or that it’s been deployed and mined so it can be fetched from the blockchain for analysis.

In the meantime, you can deploy upgradeable contracts linked to external libraries by setting the unsafeAllowLinkedLibraries flag to true in the deployProxy or upgradeProxy calls, or including 'external-library-linking' in the unsafeAllow array. Keep in mind the plugins will not verify that the linked libraries are upgrade safe. This has to be done manually for now until the full support for external libraries is implemented.

You can follow or contribute to this issue in GitHub.

Why do I need a public upgradeTo or upgradeToAndCall function?

When using UUPS proxies (through the kind: 'uups' option), the implementation contract must include one or both of the public functions upgradeTo(address newImplementation) or upgradeToAndCall(address newImplementation, bytes memory data). This is because in the UUPS pattern the proxy does not contain an upgrading function itself, and the entire upgradeability mechanism lives on the implementation side. Thus, on every deploy and upgrade we have to make sure to include it, otherwise we may permanently disable the upgradeability of the contract.

The recommended way to include one or both of these functions is by inheriting the UUPSUpgradeable contract provided in OpenZeppelin Contracts, as shown below. This contract adds the required function(s), but also contains a built-in mechanism that will check on-chain, at the time of an upgrade, that the new implementation proposed also inherits UUPSUpgradeable or implements the same interface. In this way, when using the Upgrades Plugins there are two layers of mitigations to prevent accidentally disabling upgradeability: an off-chain check by the plugins, and an on-chain fallback in the contract itself.

import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract MyContract is Initializable, ..., UUPSUpgradeable {
    ...
}

Read more about the differences with the Transparent Proxy Pattern in Transparent vs UUPS.

Can I use custom types like structs and enums?

Past versions of the plugins did not support upgradeable contracts that used custom types like structs or enums in their code or linked libraries. This is no longer the case for current versions of the plugins, and structs and enums will be automatically checked for compatibility when upgrading a contract.

Some users who have already deployed proxies with structs and/or enums and who need to upgrade those proxies may need to use the override flag unsafeAllowCustomTypes for their next upgrade, after which it will no longer be necessary. If the project contains the source code for the implementation currently in use by the proxy, the plugin will attempt to recover the metadata that it needs before the upgrade, falling back to the override flag if this is not possible.

How can I rename a variable, or change its type?

Renaming a variable is disallowed by default because there is a chance that a renaming is actually an accidental reordering. For example, if variables uint a; uint b; are upgraded to uint b; uint a;, if renaming was simply allowed this would not be seen as a mistake, but it could have been an accident, especially when multiple inheritance is involved.

It is possible to disable this check by passing the option unsafeAllowRenames: true. A more granular approach is to use a docstring comment /// @custom:oz-renamed-from <previous name> right above the variable that is being renamed, for example:

contract V1 {
    uint x;
}
contract V2 {
    /// @custom:oz-renamed-from x
    uint y;
}

Changing the type of a variable is not allowed either, even in cases where the types have the same size and alignment, for the similar reason explained above. As long as we can guarantee that the rest of the layout is not affected by this type change, it is also possible to override this check by placing a docstring comment /// @custom:oz-retyped-from <previous type>.

contract V1 {
    bool x;
}
contract V2 {
    /// @custom:oz-retyped-from bool
    uint8 x;
}

Docstring comments don’t yet work for struct members, due to a current Solidity limitation.

How can I use internal functions in storage variables?

Internal functions in storage variables are code pointers which will no longer be valid after an upgrade, because the code will move around and the pointer would change. To avoid this issue, you can declare those functions as external, or avoid code pointers in storage altogether and define an enum that you will use with a dispatcher function to select from the list of available functions. If you must use internal functions, those internal functions need to be reassigned during each upgrade.

For example, the messageFunction variable in the following contract is not upgrade safe. Attempting to call showMessage() after an upgrade would likely result in a revert.

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract V1 is Initializable {
  function() internal pure returns (string memory) messageFunction;

  function initialize() initializer public {
    messageFunction = hello;
  }

  function hello() internal pure returns (string memory) {
    return "Hello, World!";
  }

  function showMessage() public view returns (string memory) {
    return messageFunction();
  }
  ...
}

To allow the above contract to be deployed by the Upgrades Plugins, you can disable the internal-function-storage check according to How can I disable some of the checks?, but ensure you follow the steps below to reassign the internal function during upgrades.

In new versions of this contract, assign the internal function in the storage variable again, for example by using a reinitializer:

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "./V1.sol";

contract V2 is V1 {
  function initializeV2() reinitializer(2) public {
    messageFunction = hello;
  }
  ...
}

Then when upgrading, call the reinitializer function as part of the upgrade process, for example in Hardhat:

await upgrades.upgradeProxy(PROXY_ADDRESS, ContractFactoryV2, {
  call: 'initializeV2',
  unsafeAllow: ['internal-function-storage']
});

or in Foundry:

Upgrades.upgradeProxy(
    PROXY_ADDRESS,
    "V2.sol",
    abi.encodeCall(V2.initializeV2, ())
);