Smart contracts are often considered to be a more secure and permanent form of agreement. However, because their source code cannot be changed or updated without causing an issue with the blockchain itself – which would lead to potential vulnerabilities- there needs to exist some degree of mutability for bug fixations as well as new features and improvements.
The solution? Proxy smart contracts. A proxy is an object that refers to another object. The proxy can be used to simplify complex contracts or to store data more efficiently. Essentially, a proxy contract stores an address of the latest deployed contract and redirects calls to that, currently valid, logic. If contract logic is upgraded, deploying a new smart contract, the creator would just need to update the reference variable in a proxy contract with that new contract address.
Table of Contents
Smart contracts are computer codes that automatically execute all or parts of an agreement without the need for a third party. They can be stored on blockchains and executed when certain conditions are met, such as transferring funds from Party A to B
The code itself is replicated across multiple nodes so it benefits greatly with security, permanence & immutability offered by blockchain technology. Once the replication occurs, the code is executed as a new block is added to the chain. By initiating a transaction, both parties are indicating the parameters outlined by the contract have been met. If no transaction is initiated, the code will not take any steps. A smart contract is typically written in one of the specialized programming languages suitable for such computer programs, such as Solidity.
Currently, the input parameters and execution steps for a smart contract have to be specific and objective, meaning, if “x” occurs, then execute step “y” which are pretty simple tasks. In essence, the smart contract is automatically moving an amount of cryptocurrency from one party’s wallet to another when certain criteria are met. But, as things change and blockchain becomes widespread causing more assets to become tokenized or go “on-chain”, smart contracts will need to meet the complex demands and handle more sophisticated transactions.
Types of Smart Contracts
Smart Legal Contract
A smart legal contract is among the most common types of smart contracts in use today. They’re put into place to hold concerned parties accountable for fulfilling their end agreement, When set up properly, they can be legally enforceable. If one party breaches an obligation then legal action can come action against them which is automatically triggered by the smart contract.
Decentralized Autonomous Organizations
The powers of a decentralized autonomous organization, or DAO, are in its rules. This community exists on the blockchain and can be defined by agreed-upon smart contracts which enforce their set code, subjecting each member’s activities to it with a task at hand.
Application Logic Contract
In the world of blockchain technology, ALCs are a key element. Application logic contracts contain application-based code that remains in step with other blockchain contracts and enables communication across different devices with blockchain technology. ALCs work under management by way of their programming language allowing them more flexibility when it comes down to deciding how they want to operate within certain parameters while also giving others options on what type of functionality could take place.
Smart contracts are a powerful new way to bring transparency, efficiency, and precision into transactions that have traditionally lacked these qualities. The need for Smart Contracts comes from their ability to provide visibility in all aspects – financial or otherwise-of any business deal while also bringing an unprecedented level of safety through consensus mechanisms that enforce terms using blockchain technology; this makes them uniquely suited for industries with high risk, due diligence requirements.
Why Upgrade a Smart Contract?
Smart contracts are immutable and blockchain technology profits highly off this benefit however, not being able to update, change or even withdraw the terms of a transaction can create a few challenges.
The immutable nature of Smart Contracts ensures that they are reliable and secure. However, the ability to upgrade smart contracts allows for utmost protection.
Creating Upgradeable Contracts
The basic idea of a proxy is to utilize it for upgrades to smart contracts. Two different contracts will work together to execute the logic. The first is a simple wrapper or “proxy” which users interact with directly and is in charge of forwarding transactions from one party on behalf of another- for example, if I send you 1 ETH then your account will be credited accordingly instead after receiving 2 confirmations (which could take hours). However these aren’t just any old backups; rather than replacing the entire codebase like most updates, only certain sections are swapped out leaving the contracts immutable still since the code itself cannot be changed.
The use of a proxy contract is to delegate calls to another contract (the caller) and have access through that point. To interact with the actual contract, you have to go through the proxy which delegates the call to the target contract.
A proxy pattern is used when you want upgradability for your contracts. This way the proxy contract stays immutable, but you can deploy a new contract behind the proxy contract – simply change the target address inside the proxy contract.
It can be somewhat of a risk to use a proxy contract since there are no guarantees that the underlying target contract hasn’t been changed to a malicious one. There is no strict definition on how to detect a proxy contract, but it’s anything that delegates the functionality to another contract. You have to analyze the source code to decide.
Smart contracts have evolved into being more than just basic contracts. Now we have whole ecosystems powered by Smart Contracts! No matter how careful we are or how well tested our code is, if we are creating a complex system, there is a good chance that we will need to update the logic to patch a bug, fix an exploit or add a necessary missing feature. Sometimes, we may even need to upgrade our smart contracts due to changes in EVM or newly found vulnerabilities.
Generally, developers can easily upgrade their software but blockchains are different as they are immutable. If we deploy a contract then it is out there with turning back no longer an option. However, if we use proper techniques, we can deploy a new contract at a different address and render the old contract useless. Following are some of the most common techniques for creating upgradable smart contracts.
Obstacles while writing Upgradeable Contracts
An upgradeable contract will have immense complexities that need to be considered during its development, even if it is just a basic upgrade.
For instance, the creation and deployment of the new version with modified code will require developers not only to create an upgraded version but also to migrate all imperative states over from the old contract. Handling Unpreserved Contract Address by deploying a new contract will completely change the address of the contract that was previously used for contract interaction.
Because this procedure lacks effectiveness, the community has taken notice of other approaches to develop upgradeable smart contracts.
Techniques for Creating Upgradeable Smart Contracts
Separate Logic Contract
This involves separating the smart contract into a data contract which contains the data (variables, structures, mappings, etc) with appropriate getters and setters, and a logic contract which contains all of the business logic of how to update this data. The logic contract updates the data through the setters and the data contract only allows the logic contract to call the setters. This allows the logic to be replaced while keeping the data in the same place, allowing for a fully upgradeable system.
The contract can be updated by pointing users to use the new logic contract (through a resolver such as ENS) and updating the data contract permissions to allow the new logic contract to be able to execute the setters.
Master-Slave technique is one of the most basic and easy-to-understand techniques for making smart contracts upgradeable. In this technique, a master contract is deployed along with all of the other contracts. The master contract stores the addresses of all other contracts and returns the required address whenever needed. The contracts fetch the latest address of other contracts from the master whenever they need to communicate with other contracts. To upgrade a smart contract, it is simply deployed onto the network and the address in the master contract is changed.
This isn’t the best method to develop upgradable contracts, but it is the simplest. One of the many limitations of this method is that the data or assets can’t complete the migration of the contract to a new contract easily.
Eternal Storage Contract
Eternal Storage without proxy causes loss of data in deployed contracts. To fix this issue, one would have to separate logic from the storage contract.
In the Eternal Storage pattern, move the storage with setters and getters to a separate smart contract and let only read/write the logic smart contract from it.
Using a voting contract as an example to call vote() and increase a number;
- Deploy the Eternal Storage; this contract remains a constant and isn’t changed at all.
- Then deploy the Ballot Smart Contract, which will take the library and the Ballot Contract to do the actual logic.
- Under the hood, a library does a delegate call, which executes the library’s code in the context of the Ballot Smart Contract. If you were to use msg. the sender in the library, then it has the same value as in the Ballot Smart Contract itself.
If a bug was present allowing everyone to vote as many times as they wanted, you would fix it and re-deploy only the Ballot Smart Contract (neglecting that the old version still runs and that there is no way to stop it without extra code). In this case, only the library changed, the storage is the same as before.
To deploy the update, re-deploy the “Ballot” Smart Contract and give it the address of the Storage Contract.
Proxy Contract Methodology
By making the eternal storage contracts act as a proxy to our logic contract, we can prevent having extra gas spent on unnecessary transactions as only one delegate call is needed no matter how many changes are made in the data.
The two will inherit each other’s values so that both are aligned in essence – this way nothing needs changing when it comes down to deciding who gets modified.
With one fallback function in the proxy contract, shared by all three parties involved (the EVM+two others), there won’t be much work left unknown about how these smart contracts operate
There are three components of this technique:
Proxy contract: It will act as eternal storage and delegate call the logic contract.
Logic contract: It will do all the processing of the data.
Storage structure: It contains the storage structure and is inherited by both proxy and logic contracts so that their storage pointers remain in sync on the blockchain.
An incredibly important concept to understand in the proxy contract approach is the delegate call. It is an opcode provided by the EVM that enables us to execute the code at the target contract address but in the context of the calling contract.
Since the logic contract is separated, the logic can be changed while keeping the proxy contract the same for the user.
Thus, the proxy contract in this procedure acts as immutable storage while the logic contract will include all the functionalities.
To upgrade the logic of the contract, one would simply need to make the proxy contract aware of the address of the new delegate contract. Therefore, whenever a particular function in the proxy contract is invoked, it simply delegate calls the logic contract(which contains the function logic).
Thus, enabling the modified code to be used while interacting with the same old proxy contract with the state and address of the contract completely preserved.
Proxies are a way to solve the problem of exposing the interface of the logic contract without needing one-to-one mapping. This can be difficult, prone to errors, and makes upgrading interfaces very challenging. That’s where dynamic forwarding comes into play; it allows you to update certain parts or all depending on what best suits each situation.
The code can be put in the fallback function of a proxy and will forward any call to any function with any set of parameters to the logic contract without it needing to know anything in particular of the logic contract’s interface. In essence, (1) the call data is copied to memory, (2) the call is forwarded to the logic contract, (3) the return data from the call to the logic contract is retrieved, and (4) the returned data is forwarded back to the caller.
The EVM’s delegate call opcode allows for a proxy to be executed in the context of its caller. The logic contract controls this new state and therefore cannot know anything about what is happening outside of it, but by executing through these proxies we can still have some control over our original implementation while being able to execute transactions directly on top of an addressable interface which has many utilities not present before such as tracing or viewing how much gas was spent when submitting orders.
Shared storage is a high concern in proxy contracts. When discussing storage in a smart contract, solidity will be the term most utilized as its memory is comparable to that of a computer’s RAM. Solidity is an object-oriented, high-level language for implementing smart contracts.
During execution, memory for the solidity smart contract is unlimited but once the execution is complete, the memory is wiped clean for the next execution. However, storage is persistent throughout executions and previously stored data is readily available.
Storage layout begins at position 0 and increments for each new state variable. So the first state variable is stored at position 0, the second state variable is stored at position 1, and the third is stored at position 2, etc. Each struct or array element uses the next storage position as if each one was defined separately on its own.
*A proxy contract and its delegate/logic contract share the same storage layout.
The three main proxy patterns each attempt to answer the same question: how to ensure that the logic contract does not overwrite state variables that are used in the proxy for upgradeability.
- Inherited Storage
- Eternal Storage
- Unstructured Storage
If a Proxy contract has state variables to keep track of the latest logic contract address at some storage slots, and neither party knows about it then there’s potential for error. The proxy may store other data in these same spots which could overwrite critical information related directly to their function.
The idea behind inherited storage is to start with one storage structure and each version will follow the previous one. Each new version of the upgrade cannot change the storage structure of the previous implementations but can add new state variables on top of that storage inheriting it.
Both the proxy and the logic contract inherit the same storage structure to ensure that both adhere to storing the necessary proxy state variables.
A few drawbacks to this would be a new version would need to inherit storage contracts that may contain many state variables not being used as well as becoming closely coupled to specific proxy contracts keeping them from being used by other proxy contracts that declare state variables.
The goal of this approach is to have the same generic, immutable storage structure for any contract. This is a set of solidity mappings for each type variable and cannot be changed.
All versions of the logic contract must always use the eternal storage structure.
Its issues are a clumsy syntax for state variables, working directly for simple values and arrays but not working in a generic framework for mapping and values. Also, state variables are not declared together anywhere so they wouldn’t be easily recognizable.
The Unstructured Storage pattern is similar to Inherited Storage but doesn’t require the logic contract to inherit any state variables associated with upgradeability. This pattern uses an unstructured storage slot defined in the proxy contract to save the data required for upgradeability.
Instead of storing the implementation address at the proxy’s first storage slot, the unstructured storage pattern chooses a pseudo-random slot instead. This principle is the same as for any other variable that a proxy contract may have.
Proxies are a valuable tool for smart contracts. They can help to reduce transaction costs by reducing the number of transactions that need to be made by storing data locally.
They simplify complex contracts by eliminating the need to repeat different parts of a contract since data is stored in a proxy object. This can make a contract easier to understand and less likely for errors.
Finally, proxies can be used to improve the efficiency of data storage. By storing data in a proxy object, you can reduce the amount of storage that is required by your contract. This can be helpful in situations where storage is limited, or when data needs to be accessed frequently.
All in all, being familiar with proxies is important to understand why and how to upgrade your contract. Although this concept is still fairly new, the possibilities of change make it worth all the effort.