1. Why do you need to upgrade smart contract?
A smart contract once deployed to the network, it will exist permanently on it and cannot be modified. This is the biggest advantage of Blockchain, however, in some cases it is limited.
For example, a programmer discovered a flaw in his smart contract logic, it could be exploited by a hacker and caused damage to him, now he wants to find a way to both patch. The vulnerability is both keeping all the data of that smart contract.
In a normal mindset, he would back up all the data of the old smart contract somewhere off the network, deploy a new smart contract that fixed the vulnerability, and then migrate the backed up data to it, but this way. the following limitations:
- this is only possible when the data is relatively small
- force new smart contract to have a function to call migrate data => cause trouble for smart contract
- consume a lot of gas
So, how does a smart contract have to be deployed network, after running for a while, a lot of data is stored on it, if suddenly discovered that there is a hole in logic, it is still possible for the smart contract to run in Other logic more secure without deploying a new version and then migrating the data? It has been studied a very nice way based on the function of delegatecall()
function to do this.
2. How the delegatecall updates the storage of the contract
As you know, in solidity
there are many ways to call low-level call
from contract A
to contract B
including:
- call ()
- staticcall ()
- delegatecall ()
In which, delegatecall()
operates in a special way, which can be understood as follows: When a contract X
performs delegatecall()
to a contract Y
, it will perform the logic
of contract Y
but under the context
and storage
of contract X
. The example has the below source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token comment">// Contract A</span> pragma solidity <span class="token operator">>=</span> <span class="token number">0.6</span> <span class="token number">.2</span> <span class="token operator"><</span> <span class="token number">0.8</span> <span class="token number">.0</span> <span class="token punctuation">;</span> contract <span class="token constant">A</span> <span class="token punctuation">{</span> address <span class="token keyword">public</span> sender <span class="token punctuation">;</span> uint256 <span class="token keyword">public</span> balance <span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">delegateCallToB</span> <span class="token punctuation">(</span> <span class="token parameter">address _contractLogic <span class="token punctuation">,</span> uint256 _balance</span> <span class="token punctuation">)</span> external <span class="token punctuation">{</span> <span class="token punctuation">(</span> bool success <span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=</span> _contractLogic <span class="token punctuation">.</span> <span class="token function">delegatecall</span> <span class="token punctuation">(</span> abi <span class="token punctuation">.</span> <span class="token function">encodePacked</span> <span class="token punctuation">(</span> <span class="token function">bytes4</span> <span class="token punctuation">(</span> <span class="token function">keccak256</span> <span class="token punctuation">(</span> <span class="token string">"setBalance(uint256)"</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> _balance <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">require</span> <span class="token punctuation">(</span> success <span class="token punctuation">,</span> <span class="token string">"Delegatecall failed"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token comment">// Contract B</span> pragma solidity <span class="token operator">>=</span> <span class="token number">0.6</span> <span class="token number">.2</span> <span class="token operator"><</span> <span class="token number">0.8</span> <span class="token number">.0</span> <span class="token punctuation">;</span> contract <span class="token constant">B</span> <span class="token punctuation">{</span> address <span class="token keyword">public</span> sender <span class="token punctuation">;</span> uint256 <span class="token keyword">public</span> balance <span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">setBalance</span> <span class="token punctuation">(</span> <span class="token parameter">uint256 _balance</span> <span class="token punctuation">)</span> external <span class="token punctuation">{</span> sender <span class="token operator">=</span> msg <span class="token punctuation">.</span> sender <span class="token punctuation">;</span> balance <span class="token operator">=</span> _balance <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Both contract A and B declare variable as _balances
, in contract A whose function is delegateCallToB
will delegatecall()
to function setBalance()
of B.
We deploy 2 contracts to the network using Remix
then:
- use some
user_address
(address0x8f287eA4DAD62A3A626942d149509D6457c2516C
for example) calldelegateCallToB()
function of A with parameters of B’s deploy address and an integer of 10 for example =>delegateCallToB(B_contract_address, 10)
- querying the
sender
andbalance
of B, the result is0x0000000000000000000000000000000000000000
and0
. - query the
sender
andbalance
of A, the result is0x8f287eA4DAD62A3A626942d149509D6457c2516C
and10
.
If we now deploy to add a C contract with the following source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 | pragma solidity <span class="token operator">>=</span> <span class="token number">0.6</span> <span class="token number">.2</span> <span class="token operator"><</span> <span class="token number">0.8</span> <span class="token number">.0</span> <span class="token punctuation">;</span> contract <span class="token constant">C</span> <span class="token punctuation">{</span> address <span class="token keyword">public</span> sender <span class="token punctuation">;</span> uint256 <span class="token keyword">public</span> balance <span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">setBalance</span> <span class="token punctuation">(</span> <span class="token parameter">uint256 _balance</span> <span class="token punctuation">)</span> external <span class="token punctuation">{</span> sender <span class="token operator">=</span> msg <span class="token punctuation">.</span> sender <span class="token punctuation">;</span> balance <span class="token operator">=</span> balance <span class="token operator">+</span> _balance <span class="token operator">*</span> <span class="token number">2</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Perform:
- call
delegateCallTo(B)
function of A with parametersC_contract_address
and3
=>delegateCallToB(C_contract_address, 3)
. - querying
sender
andbalance
of C, the result is0x0000000000000000000000000000000000000000
and0
. - query the
sender
andbalance
of A, the result is0x8f287eA4DAD62A3A626942d149509D6457c2516C
and16
.
Thus, that is completely true with the properties of delegatecall()
that we mentioned above.
When a
contract X
performsdelegatecall()
to acontract Y
, it will execute thelogic
ofcontract Y
but under thecontext
andstorage
ofcontract X
3. Deploying a upgradeable contract
in the Inherited Storage way
Thus, from the properties of delegatecall()
, we have the possibility to upgrade a contract. However, how to properly deploy it is another matter.
In essence, the way that delegatecall()
affects the storage
of contract X
is that it affects the order of the corresponding slot between X and Y. For example, contract D is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 | pragma solidity <span class="token operator">>=</span> <span class="token number">0.6</span> <span class="token number">.2</span> <span class="token operator"><</span> <span class="token number">0.8</span> <span class="token number">.0</span> <span class="token punctuation">;</span> contract <span class="token constant">D</span> uint256 <span class="token keyword">public</span> balance <span class="token punctuation">;</span> address <span class="token keyword">public</span> sender <span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">setBalance</span> <span class="token punctuation">(</span> <span class="token parameter">uint256 _balance</span> <span class="token punctuation">)</span> external <span class="token punctuation">{</span> sender <span class="token operator">=</span> msg <span class="token punctuation">.</span> sender <span class="token punctuation">;</span> balance <span class="token operator">=</span> _balance <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
the order of declaring the two variables balance
and sender
of D is different from A, B, C. Implementation:
- call
delegateCallToB(B)
function of A with parametersD_contract_address
and10
=>delegateCallToB(D_contract_address, 10)
- querying
sender
andbalance
of D, the result is0x0000000000000000000000000000000000000000
and0
- query the
sender
andbalance
of A, the result is0x000000000000000000000000000000000000000A
(the result of address (10)) and817288742280969564811718162174206570979710357868
(the result of uint256 (0x8f287eA4DAD62A3A626942d149509D6457c). Should be sender =0x8f287eA4DAD62A3A626942d149509D6457c2516C
and balance =10
?
The reason is, between contract A and contract D due to conflicts in the order of declaring variables, resulting in false results. Thus, when we want to implement an upgradeable contract, we must agree on the order of declaring the signs holding the contract as storage
(contract A) and the contract as a logic
(contract B, C , D). It can be implemented in short as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | pragma solidity <span class="token operator">>=</span> <span class="token number">0.6</span> <span class="token number">.2</span> <span class="token operator"><</span> <span class="token number">0.8</span> <span class="token number">.0</span> <span class="token punctuation">;</span> contract StorageContract <span class="token punctuation">{</span> address <span class="token keyword">public</span> sender <span class="token punctuation">;</span> uint256 <span class="token keyword">public</span> balance <span class="token punctuation">;</span> <span class="token punctuation">}</span> contract <span class="token constant">A</span> is StorageContract <span class="token punctuation">{</span> <span class="token keyword">function</span> <span class="token function">delegateCallToB</span> <span class="token punctuation">(</span> <span class="token parameter">address _contractLogic <span class="token punctuation">,</span> uint256 _balance</span> <span class="token punctuation">)</span> external <span class="token punctuation">{</span> <span class="token punctuation">(</span> bool success <span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=</span> _contractLogic <span class="token punctuation">.</span> <span class="token function">delegatecall</span> <span class="token punctuation">(</span> abi <span class="token punctuation">.</span> <span class="token function">encodePacked</span> <span class="token punctuation">(</span> <span class="token function">bytes4</span> <span class="token punctuation">(</span> <span class="token function">keccak256</span> <span class="token punctuation">(</span> <span class="token string">"setBalance(uint256)"</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> _balance <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">require</span> <span class="token punctuation">(</span> success <span class="token punctuation">,</span> <span class="token string">"Delegatecall failed"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> contract <span class="token constant">B</span> is StorageContract <span class="token punctuation">{</span> <span class="token keyword">function</span> <span class="token function">setBalance</span> <span class="token punctuation">(</span> <span class="token parameter">uint256 _balance</span> <span class="token punctuation">)</span> external <span class="token punctuation">{</span> sender <span class="token operator">=</span> msg <span class="token punctuation">.</span> sender <span class="token punctuation">;</span> balance <span class="token operator">=</span> _balance <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> contract <span class="token constant">C</span> is StorageContract <span class="token punctuation">{</span> <span class="token keyword">function</span> <span class="token function">setBalance</span> <span class="token punctuation">(</span> <span class="token parameter">uint256 _balance</span> <span class="token punctuation">)</span> external <span class="token punctuation">{</span> sender <span class="token operator">=</span> msg <span class="token punctuation">.</span> sender <span class="token punctuation">;</span> balance <span class="token operator">=</span> balance <span class="token operator">+</span> _balance <span class="token operator">*</span> <span class="token number">2</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> contract <span class="token constant">D</span> is StorageContract <span class="token punctuation">{</span> <span class="token keyword">function</span> <span class="token function">setBalance</span> <span class="token punctuation">(</span> <span class="token parameter">uint256 _balance</span> <span class="token punctuation">)</span> external <span class="token punctuation">{</span> sender <span class="token operator">=</span> msg <span class="token punctuation">.</span> sender <span class="token punctuation">;</span> balance <span class="token operator">=</span> _balance <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
This implementation is called Inherited Storage
. However it does have some disadvantages:
- Logical contracts can inherit a storage in which there can be variables it does not use.
- Logical contracts must be tightly linked with the storage contract
- Difficulty in managing private seas
- Easy to create storage conflicts
- It is imperative that the storage contract has the necessary get-set functions
There is also a way that you can use to deploy an upgradeable contract
similar to Inherited Storage
is Eternal Storage
you can refer to here .
At the end of part 1, I would like to introduce to you:
- Why must upgrade contract
- delegatecall () is the main technique people use to make a contract upgradeable (upgradeable contract).
- The simplest implementation is Inherited Storage.
In part 2, I will go later on the way to Unstructured Storage, which has been standardized and published by Openzeppelin on my github.