Why you need to be careful when upgrading your proxy contracts

Why you need to be careful when upgrading your proxy contracts

Why You Need to Exercise Caution When Upgrading Your Proxy Contracts

Proxy contracts serve as intermediaries that delegate calls to implementation contracts. Ensuring consistency in storage layout across upgrades is paramount for their proper functioning. The proxy contract maintains the state, while the implementation contract houses the logic. Any alterations to the storage layout in the implementation contract can result in discrepancies when the proxy contract interacts with or modifies state variables.

To elucidate, let's delve into a scenario:

Previous Implementation Storage Layout:

  • address USDC public (slot[0])
  • address USDT public (slot[1])
  • address DAI public (slot[2])

New Implementation Storage Layout:

  • address ETH public (slot[0])
  • address USDC public (slot[1])
  • address USDT public (slot[2])
  • address DAI public (slot[3])

Here, by introducing a new storage variable, ETH, at the contract's outset, we inadvertently shifted the original variables down one slot. Consequently, the ETH variable would now occupy the slot previously reserved for USDC (slot[0]), and so forth.

This misalignment disrupts the storage pattern, thereby compromising the contract's functionality.

How Can You Mitigate This?

To avert such storage collisions, developers should adhere to these principles:

  1. Always append new state variables to the end of the storage layout in the implementation contract. This approach preserves the original storage slots, maintaining alignment with the proxy contract.
  2. For contracts intended to be inherited and extended, incorporate a "gap" array. This array serves as a buffer of reserved storage slots. When a new variable is added in a derived contract, adjust the size of the gap array accordingly. This practice ensures the integrity of the storage layout across different versions.

Notably, OpenZeppelin introduced a specific custom storage slot for their newer versions, effectively addressing this issue.

Proxy collisions pose a significant threat to the upgradeability and functionality of smart contracts. By adhering to best practices, such as meticulous planning of storage layouts and leveraging gap arrays, developers can mitigate these risks. This guarantees that smart contracts remain resilient, secure, and operational across upgrades, safeguarding the interests of all stakeholders involved.

Continue reading