Upgrades with Peace of Mind: "Structs" Edition

Last year we launched the OpenZeppelin Upgrades Plugins for Hardhat and Truffle, a new tool that is focused solely on providing security and peace of mind around smart contract upgrades, integrated into your existing development workflow. For a deep dive on the topic of upgrades see The State of Smart Contract Upgrades.

We achieve this goal through the automated analysis of your contract, initially ensuring it is apt for an upgradeable proxy deployment, protecting from a situation like the Parity Wallet selfdestruct bug, and then verifying that subsequent upgrades don’t corrupt the storage layout of the proxy.

For an excellent writeup about storage layout compatibility (in the context of a high-profile upgradeable contract) make sure to read USDC v2: Upgrading a multi-billion dollar ERC-20 token. The article describes a runtime approach to validating storage integrity as an upgrade is being executed. The OpenZeppelin Upgrades Plugins offer a different approach, by verifying ahead of time that there won’t be any data corruption. A contract upgrade is analyzed before being pushed to the blockchain, and if the upgraded storage layout is not compatible with the current one, the upgrade is aborted. We see the two approaches as complementary, and we may start to see developers combining them.

The fact that this verification happens ahead of time is key to the value provided by our Upgrades Plugins. By being integrated in the development cycle, and having access to the source code, the plugins can provide detailed diagnostics of the problems in a contract, together with recommendations on how to fix them.

With the latest release, these safety checks and diagnostics have become even better!


Those who are already using the plugins are familiar with the lack of support for structs and enums, and the need to use a manual override in those cases. This was initially left out of scope so we could focus on the problem we wanted to tackle at a higher level.

We’re happy to announce that in the latest release of the plugins, structs and enums are now fully supported and it’s no longer necessary to use a manual override. Let’s look at an example where we try to add a new struct member.

contract LoyaltyV1 {
    struct Account {
        uint balance;
    }

    mapping (address => Account) accounts;
}

contract LoyaltyV2 {
    struct Account {
        uint points; // <-- New struct member shifting the location of balance
        uint balance;
    }

    mapping (address => Account) accounts;
}

Trying to upgrade a proxy from LoyaltyV1 to LoyaltyV2 will show the following error.

contracts/Loyalty.sol:24: Upgraded `accounts` to an incompatible type
  - In mapping(address => struct LoyaltyV2.Account)
    - Bad upgrade from struct LoyaltyV1.Account to struct LoyaltyV2.Account
  - In struct LoyaltyV2.Account
    - Inserted `points`
      > New struct members should be placed after existing ones

This lets us know we should place the new struct member points after the existing one, balance. If we make this change, the upgrade will go through!

We’re really excited about this feature and think it will become essential for all developers working on smart contract upgrades, by providing assurance about the correctness of their work through more sophisticated tooling.


To get started with OpenZeppelin Upgrades Plugins have a look at one of the following short guides:

If you’re already a user, update to the latest version with:

  • npm install @openzeppelin/hardhat-upgrades@latest
  • npm install @openzeppelin/truffle-upgrades@latest

Feel free to leave any comments below. Let us know your thoughts!

9 Likes

Awesome work!! congrats!!

5 Likes