Proxy — Inherited Storage

Eszymi
Coinmonks
Published in
4 min readSep 29, 2022

--

As I mentioned in my previous post, the not propriety use of delegatecall function may lead to the gap of security. One of the problem is accidental to overwrite of variable, because delegatcall not use the name of variables, but their position in the storage.

Let’s look at these contracts. The addressOfLogicvariable contains the address of the current implementation of Logic. Only the owneris able to change this variable. The Logic contract gives us two function: to set value var1 i var2. But when we use it by Proxy, the result will be different than we think. If we call the function setVar1 with argument 5, then the value of the second element of storage of Proxy will be equal to 5, so in our case it will be var2. So if we won’t be careful, we can accidentally change not the right variable.

How big consequence could be from the difference between storage in Proxy and Logic we could notice using the second function implemented in the Logic. By the first look, it’s similar to the previous function. But this function change the value of the variable in the third slot of storage. In Logic is var2, but in Proxy it’s owner. This is a great gap in the security of our Proxy. Someone could use setVar2 and as a parameter set his own address, and by that he will be the owner of the Proxy, and as a result, he will be able to set addressOfLogicand implement his own Logic.

How to resize our storage?

We know now that storage of Logic and Proxy have to be the same, but what should we do with storage when we upgrade Logic? Our new Logic has to inherit storage from the previous Logic, so the new version cannot change the structure of storage, but can add new state variable in the next not occupied slots in the storage, so at the end of the existing variable. Let’s see on the examples. If we have LogicV1

contract LogicV1 {
uint256 public var1;
uint256 public var2;
}

we can’t upgrade it to LogicV2 contract like that:

contract LogicV2 {
uint256 public var1;
}

remove of the var2 (theoretically it’s allowed, but we have to remember that the value of the var2 will be still in the second slot of the storage. So if we create LogicV3 (contract which upgrade this contract LogicV2)) with the new variable, the value of the new will be on the beginning equal to the value of the var2

contract LogicV2 {
uint256 public var2;
uint256 public var1;
}

change place of var1 and var2

contract LogicV2 {
uint256 public var1;
address public var2;
}

change type of var2

contract LogicV2 {
uint256 public var1;
uint256 public var3;
uint256 public var2;
}

add a new variable not on the end of the existing variables.

The proper add of the new variable in the new implementation look like

contract LogicV2 {
uint256 public var1;
uint256 public var2;
uint256 public var3;
}

If we want to change the name of the variable, we can make it. But allowed is only the name of it, no type. So this is also proper new contract

contract LogicV2 {
uint256 public slot0;
uint256 public slot1;
uint256 public var3;
}

Upgrading of the Logic contract that inherit from other contracts

Let’s assume that our Logic contract looks like

contract Logic is A, B{
uint256 public var1;
uint256 public var2;
}

and contract A and B look like

contract A {
uint256 public A0;
}
contract B {
uint256 public B0;
}

then the storage of our Logic is (storage[0] is the first slot of the storage, storage[1] the second and so on)

storage[0] -> A0
storage[1] -> B0
storage[2] -> var1
storage[3] -> var2

It’s easy to predict if someone added a new variable in A or B, the order of the storage of Logic will be different. Also, if we write

contract Logic is B, A{
uint256 public var1;
uint256 public var2;
}

the out coming storage will be different.

If we are not sure that we won’t need more variables in parent's contract, we can reserve the storage using a storage gap. To do that, we declare fixed array like that

contract A {
uint256 public A0;
uint256[100] public __gap;
}

If in the future we will have to add a new variable, we’ll just write

contract A {
uint256 public A0;
uint256 public A1;
uint256[99] public __gap;
}

The name __gap is coming from a convention created by OpenZeppelin. When we talk about OpenZeppelin, I used information to this section from their great article.

The other possibility

Other option is creating contract that will contain all state variable. And both Logic and Proxy will inherit from it. Thanks that we are sure both of them have exactly the same order of storage.

Summary

We acquainted with the idea of the inherited storage proxy pattern. Let’s look at the advantage and disadvantage of this.

Advantage

  • It gives us possibility to upgrade our contract
  • It’s simply method

Disadvantage

  • New versions need to inherit storage that may contain many state variables that they don’t use. So with the time, this method could be expensive in implementation.
  • The all implementations of the Logic become tightly coupled to specific proxy contracts and cannot be used by other proxy contracts that declare different state variables.

I hope you find this post useful. If you have any idea, how could I make my posts better, let my know. I am always ready to learn. You can connect with me on LinkedIn and Telegram.

If you would like to talk with me about this or any other topic I wrote, feel free. I’m open to conversation.

Happy learning!

New to trading? Try crypto trading bots or copy trading

--

--