-
The OZ upgrade tool for hardhat defends against 6 kinds of mistakes. What are they and why do they matter?
-
No
constructor()
. This cannot be applied to proxy. It has to usedinitialize()
. The reason whyconstructor()
not working is that the constructor is called only once during the deployment of the implement contract. But the interaction between Proxy and implement contracts is done bydelegatecall
which can't call the constructor. Therefore any code in constructor won't be applied to the proxy contract. Howeverconstant
andimmutable
can be used in constructor exceptionally.EX:
uint public immutable X; /// @custom:oz-upgrades-unsafe-allow not valid for contracts constructor(uint _x) { X = _x; // This works }
-
No calling
initialize()
more than once. -
No storage variable with an initial value.
EX 1:
uint public x = 100; // (X)
EX 2:
uint public; // (O)
EX 3:
uint public constant x = 100; // (O)
EX 4:
uint public immutable x = 100; // (O)
constant
andimmutable
are compiled into contract byte code. Therefore it's possible to set a value. -
Don't reorder variables when you upgrade a contract (adding new under variable is OK). Changing order of variable means changing storage layout. It will cause unexpected result after
delegatecall
. -
Proxy contract and Implementation contract both should have the same set of variables.
-
No
seltdestruct()
.
-
-
What is a beacon proxy used for?
Useful when there are multiple proxies pointing to one single implementation contract. For example, let's say there are 10 proxies pointing to a implementation contract called X. And it's using Transparent pattern which each proxy holds the X's address as variable. When there is a new version of X (let's call it X2), then the 10 proxies should update its variables as X2's address which is cumbersome. Beacon pattern can make things easier by creating a new contract that holds address. The process is going to be like below:
1. Call to a proxy 1. proxy calls to beacon 1. beacon returns implementation's address to proxy 1. proxy calls to implementation by delegate call
It's the same as transparent pattern that storage variables are stored in each proxy contract.
-
Why does the openzeppelin upgradeable tool insert something like __gap[48] inside the contracts? To see it, create an upgradeable smart contract that has a parent contract and look in the parent. It's to preserve storage space from parent contract. Otherwise there is risk of stroage crash. If there is no such
__gap[48]
, the potential problem can occur during upgrading proxy contract.contract Base { uint256 base1; // storage layout 0 } contract Child is Base { uint256 child; // storage layout 1 }
When it is complied, it will become one single bytecode. And it will be deployed and interact with Proxy contract and Proxy contract has the same storage variables.
Then let's say there is a new version of
Base
contract.contract Base { uint256 base1; // storage layout 0 uint256 base2; // storage layout 1 <- note: a new variable added } contract Child is Base { uint256 child; // storage layout 2 }
Imagine this is deployed.
child
is no longer in in slot 1 but 2. Then on the proxy contract, Then the previouschild
will be gone because it's overriden bybase2
.To prevent this, it should be like:
contract Base { uint256 base1; // storage layout 0 uint256[49] __gap; // storage layout 1 - 50 } contract Child is Base { uint256 child; // storage layout 51 }
Then when add a new variable:
contract Base { uint256 base1; // storage layout 0 uint256 base2; // storage layout 1 uint256[48] __gap; // storage layout 2 - 50 <- note: 1 reduced } contract Child is Base { uint256 child; // storage layout 51 <- note: still 51 }
-
What is the difference between initializing the proxy and initializing the implementation? Do you need to do both? When do they need to be done?
-
"What is the difference between initializing the proxy and initializing the implementation?"
There is no specific things to do for initializing the proxy. On the other hand, the implementation contract needs a specific initializing process. Look at the example below:
contract myImplContract { uint256 x; function initialize(uint256 _x) { x = _x; } }
It's like
constructor
but theconstructor
cannot be applicable for the proxy pattern since we usedeligatecall
from the proxy contract (deligatecall cannot call constructor).Thus we need constructor but not constructor ;) We should use just a normal custom function and it can be like the above
initialize
. But to be like constructor, it should be called only once. To do that, we can play with abool
variable.OZ provides libraries for upgradeable contracts.
-
"Do you need to do both? When do they need to be done?"
Both are needed in order to implemented upgradeable contract.
-
-
What is the use for the reinitializer? Provide a minimal example of proper use in Solidity
reinitializer
is used to for subsequent upgradeable contract.- Develop a new version of implementation contract ( logic change &version update )
- Develop a new version of implementation contract and introduce a new storage variables.
// let's say there is already deployed contract V1. contract MyImplContract { function initializeV2() reinitializer(2) { //<- 2 is for new version // introduce new logic or assign variable value here } }