Smart Contract Upgradeability - Part 1

Smart Contract Upgradeability - Part 1

?

Before starting, you need to know what is DELEGATECALL .in the regular call when contract A makes the call with a data payload to contract B, Contract B executes its code in response to the data payload from contract A; the most important is that reading or writing from its storage and returns a response to A. Furthermore, in this call, msg.sender is contract A.

But in the DELEGATECALL, while the code is executed in contract B, execution happens in the context of contract A. This means that any reads or writes to storage affect the storage of A, not B, and in this call, msg.sender is set to the address that had called contract A.

DELEGATECALL is a big help to design upgradeable contracts and proxy patterns. In summary, and at the most basic level, proxy patterns included two contracts, one proxy contract, and another implementation contract or logic contract.

The implementation contract is where all logic functions about the project exist. For example, in simple ERC20 token, transfer function, or transferFrom function.

Now, if the user needs to interact with this ERC20 token in a proxy pattern, the user will send all requests and calls to the proxy contract, and the proxy contract will use DELEGATECALL to send these requests and calls to the ERC20 token contract.

As we say above, execution happens in the context of the proxy contract. This means that any reads or writes to storage affect the storage of the proxy contract, not the ERC20 token contract, and in this call, msg.sender is set to the address of the user.

All state variables will be kept in proxy contract storage, and in the implementation contract, we just have logic codes and functions. So, if we make any upgrade, we don’t lose any state value; we just make changes in the code of the implementation contract.

In this pattern, we keep the implementation contract address in the storage of the proxy contract, and in any upgrade, we will change the address of the implementation contract in the proxy contract.

We have the option to use multiple proxy contracts with one implementation contract, which means multiple and separated storages (multiple proxy contracts) with one code (one implementation contract).

Currently, there are three types of proxy patterns:

·???????Diamond pattern: EIP-2532

·???????Transparent proxy pattern

·???????Universal upgradeable proxy standard (UUPS)

?Let’s talk about storage patterns.

Diamond Storage

In this pattern, there is a contract called a diamond storage contract. We can use it to organize and use state variables in the proxy contract simply.?A fundamental point in diamond storage is the solidity struct data type that contains sets of state variables.

We can define the solidity struct in the diamond storage contract and add state variables with different types to it. Then we can create a hash of a unique string by keccak256 and use this hash value to determine the position in contract storage for our struct.

Anytime we need any variable for read and write, we can load the struct at the determined position in contract storage and use it.

Inherited Storage

The inappropriate utilization of delegatecall operations may result in a gap in security. Since delegatecall uses the position of variables in storage to find them and not the name of the variable, the overwrite of variables confronts a coincidental issue.

In this pattern, the storage of the logic contract and proxy contract must be the same. Same order for storage positions. Next, if we need to upgrade our logic contract and add a new variable, the new version can't change the structure and order in storage in logic contract V1. The logic contract V2 should inherit the same storage positions from logic contract V1, and then add a new variable at the end of the old variables and the next slots which are not in use in storage. By using this, we avoid overwriting the previous variables and storage slots.

As you know, this is to avoid storage collision.

The most important point that you should remember is that for example, in logic contract V1, you have two variables, variable 1, and variable 2.

When you upgrade logic contract V1 to the logic contract V2, with only variable1,,, you removed variable2 but the value of variable2 still exists in the second slot in storage or if you remove variable2 and replace it with variable3, the value of variable3 will be the value of variable2. Same storage position for both.

But the main problem with this pattern is that new versions must inherit storage that might include many state variables that they don’t use. Through time, implementing this method can be expensive.

Eternal Storage

Utilizing the inherited storage proxy pattern with the upcoming versions of Logic costs a lot. It is a significant problem. You must inherit from the previous storage, even if you don’t need some states.

There is another kind of storage — eternal storage. In this pattern, both the proxy contract and the Logic contract are inherited from one contract, the eternal storage contract.

In the eternal storage contract, we just use mapping data type, the key to value, in solidity, the key in mapping is always 32 bytes.

For example, we need to set the address variable to the user address. We can define a mapping in an eternal storage contract. The Key is 32 bytes, and the value is the address type. But we need a function to give us 32 bytes unique key. We can use keccak256 and a unique name for the key to make 32 bytes unique key and use it in mapping.

As you can see, storage collision cannot happen in this storage pattern, and for any variable, we have a dedicated storage position.

If we need to upgrade logic contract V1 to logic contract V2, logic contract V2 needs to be inherited from the eternal storage contract.

The main problem with this pattern is that you must define all data types you need for your project and logic contracts during the creation of eternal storage.

Unstructured Storage

This storage pattern is comparable with the inherited storage pattern, but there is a great disparity. There is no requirement for the Logic contract to know the Proxy’s storage layout.

In the unstructured storage pattern, all Proxy’s state variables such as the address of the logic contract have been written in the pseudorandom slot, and it (position in storage) is saved as a constant. Since the constant is not saved in the storage, nothing will rewrite it. The solidity utilizes the storage with 22?? slots. Therefore, the chance of this one slot being overwritten by the Logic is far too low.

Similarly, to inherited storage patterns, new versions need to inherit storage and state variables that they don’t use. Through time, implementing this method can be expensive.

In the next article, we will explain three types of proxy patterns.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了