Congratulations to Zepplin for his work on proxy techniques. Important: If your logical contract relies on its constructor to configure an initial state, it must be rerun after upgrading the proxy to your logical contract. For example, it is common for logical contracts to inherit the implementation of Zeppelin-specific contracts. If your logical contract inherits from Ownable, it also inherits from the ownable builder, which determines who the owner is when the contract is created. If you bind the proxy contract to use your logical contract, the value of who is the owner from the proxy perspective is lost. Because delegatecall is used to delegate the call, the called function is executed in the context of the proxy. This also means that proxy storage is used for performing the function, resulting in the restriction that proxy contract storage only needs to be added. This means that in the event of an upgrade, existing memory variables cannot be omitted or modified, but only new variables can be added. This is because changing the storage structure in the delegate would waste the memory of the proxy waiting for the previous structure. For an example of this behavior, see the GitHub repository. Aside from the scalability debate, I`m going to focus on proxy technology, which was first popularized by Nick Johnson in this article.
After a while, we decide that we want to add features to our contract. In this guide, we`re going to add a whitelist feature. This is one of the most advanced ways to make contracts scalable. It works by storing implementation and owner addresses in fixed locations in memory so that they are not overwritten by data fed by the implementation/logic contract. We can use sload and sstore opcodes to read directly and write to specific memory locations referenced by fixed pointers. The idea of this approach is to have the same generic and immutable storage structure for each contract. This is a set of solidity mappings for each type variable, and you cannot change this memory structure of the mappings. For the contract to work, we must first deploy the proxy and ImplementationV1, and then call the upgradeTo(address) function of the proxy contract while passing the address of our ImplementationV1 contract.
We can now forget the address of the ImplementationV1 contract and treat the address of the power of attorney contract as our primary address. For this reason, it is necessary to have a special « Initializer » function that contains all the source code of the constructor`s implementation. In addition, you must ensure that this function can only be called once (from the proxy contract). Unstructured storage proxy contracts are one of the most advanced techniques for creating scalable smart contracts, but they`re still not perfect. At GovBlocks, we don`t want dApp owners to have undue control over dApps. After all, these are decentralized applications! Therefore, we decided to use a network-wide allower in our proxy contracts instead of a simple ProxyOwner. I will explain in a future article how we did this. In the meantime, I recommend reading Nitika`s argument against using onlyOwner. You can also take a look at our proxy contract on GitHub. As part of Zeppelin`s efforts to implement zeppelin_os, a distributed platform of tools and services in addition to evM, the Zeppelin team is currently advancing the unstructured storage approach. The unstructured storage approach has huge advantages because it requires minimal implementation of logical contracts by introducing a new method of maintaining the required proxy memory.
Feel free to learn more about Zeppelin`s decision to use Unstructured Memory for their next version of zeppelin_os Kernel. Smart contracts have become more than just basic contracts. Now we have entire ecosystems powered by smart contracts! No matter how careful we are or how well tested our code is, when we build a complex system, there`s a good chance we`ll need to update the logic to fix a bug, fix an exploit, or add a necessary missing feature. Sometimes we even need to update our smart contracts due to changes in EVM or newly discovered vulnerabilities. Depending on your deployment strategy, you can either have a volunteer contract or deploy the proxy and logical contracts separately. If you deploy separately, you use upgradeToAndCall to bind the proxy to your logical contract, which may look like the following: This example uses a special message call named delegatecall. Using this new message call allows a contract to forward the function call to the delegate without having to explicitly know the function`s signature, a critical point for scalability. Another difference from a normal message call is that when a delegated call, the code at the destination address is executed in the context of the call contract. This means that the storage and status of the call contract are used. In addition, transaction properties such as msg.sender and msg.value remain those of the original caller. To move on to implementation, we deploy the ImplementationV2 contract over the network and then call the upgradeTo(address) function of the proxy contract while passing the implementationV2 contract address.
The registration contract must know which logical contract to talk to. We can configure this using the setLogicContract function. We used a simple Ownable.sol file to ensure that only an administrator can call the setLogicContract function. The fallback function in Assembly may seem unknown to some, but this particular code is actually quite standard for proxy contracts. Basically, it allows an external contract to change its internal memory. Note that it is also very important to initialize storage before the proprietary contract. Getting it wrong in this sequence is catastrophic. Guess why? Delegatecall assembly code is convenient, but also dangerous.
So make sure you know what`s going on before you go live. This approach takes advantage of the arrangement of state variables in memory to prevent fixed positions from being overwritten by the logical contract. If we set the fixed position to something like 0x7, it will be overwritten shortly after using the first 7 memory slots. To avoid this, we set the fixed memory position to something like keccak256 (« org.govblocks.implemenation.address »). The storage contract simply contains the state and let`s simplify it as much as possible: as shown in the Participants and Collaboration section, the upgrade mechanism, i.e. the storage of the current version of the delegate, can be done either in the external storage or in the proxy itself. If the address is stored in the proxy, a protected feature must be implemented that allows an authorized address to update the proxy address. The concept of proxy models has been around for some time, but has not been widely accepted due to its complexity, fear of introducing security vulnerabilities, and controversy surrounding bypassing blockchain immutability. Previous solutions are also quite rigid, so future logical contracts will have to be severely limited in what they can change and add. However, from a developer`s point of view, it is clear that there is a huge need to be able to update contracts.