Proxy (aka Contract Relay)
Disclaimer: This is a summary of patterns we have observed during our research and should not be considered any form of technical or investment advice. Also, the given “known examples” do not imply they are the best implementations of the said pattern or any superior to any other implementation of the pattern not listed.
Summary
Users interact with the same proxy contract that relays all requests to the latest contract version.
Context
Like any software application, a smart contract may need to be eventually upgraded to fix bugs, overcome security weaknesses, or add new functionality. In general, business logic and data changes are required at different times and frequencies.
Problem
A smart contract deployed on a blockchain cannot be upgraded because the smart contract (binary) code is stored as immutable ledger data. Therefore, when an updated smart contract is deployed, it will be assigned a different address. Not all transaction issuers may know the existence of the updated smart contract version or its address unless these changes are reflected somewhere. Also, when deploying a new smart contract, there is a possibility of losing data associated with the previous smart contract. How to reach the latest smart contracts version and preserve the data/state of the previous one?
Forces
- Immutability – Every bit of data, including deployed smart contracts, stored on the blockchain is immutable.
- Upgradability – There is a fundamental need to upgrade all but short-lived applications and their smart contracts over time.
- Coupling – Data are embedded in a smart contract. A smart contract can live forever on the blockchain if not explicitly terminated. If a smart contract is deactivated in this way, the data stored in the smart contract cannot be accessed through the smart contract functions anymore – although it can still be accessed with some effort for provenance or audit purposes.
- Cost – If a public blockchain is used, storing data costs money. Thus, copying data from an old smart contract version to a new version will incur a high cost and should be avoided or minimised.
Solution
The idea of separating an interface from its implementation can solve this problem. A stable proxy contract presents the smart contract interface, and its address is well known to all the transaction issuers. All external transactions call the proxy contract, which in turn calls the latest version of the implementation contract, regardless of the smart contract function being invoked. Proxy smart contract serves as an intermediary between the users and the implementation logic. Whenever an upgrade is required, a newer version of the implementation contract is deployed, and only the proxy contract needs an update to point to the new address of the implementation contract.
The above figure reflects a proxy deployment and smart contract upgradability scenario. First, deploy the implementation contract. Second, deploy the proxy contract while specifying the implementation contract’s address. Whenever an updated implementation contract is deployed, update the implementation contract’s address in the proxy to the newer one.
The proxy contract should avoid exposing the entire implementation contract interface as functions by having a one-to-one mapping of the entire implementation contract’s interface. This is because any changes to the implementation contract’s function signatures require redeploying the proxy to update the interface definition. This breaks its well-known address. Instead, one should rely on the fallback function supported by languages such as Solidity. The fallback function gets executed when a contract does not have a matching function that the transaction is trying to invoke. Therefore, the proxy’s fallback function can be implemented to redirect any calls to the implementation contract.
Even though the implementation contract can store data, it is recommended to data on the proxy using the data segregation pattern. For example, depending on the smart contract language, data on the proxy can be manipulated via delegate calls without explicit authorisation management like in the data segregation pattern. A delegate call is like a regular call, except that all code is executed in the context of the caller, not of the callee. Thus, the proxy is stateful while the implementation contract is stateless. This also allows multiple proxies to use the same implementation contract, but manage their state independently. It is crucial, however, that any changes to the data schema on the implementation contract do not break how the data is stored in the context of the storage layer.
Benefits
- Fixed proxy contract address – The users/transaction issuers will always interact with the same interface that the proxy exposes. The implementation contract can be updated without breaking the proxy’s address.
- Upgradability – By separating the interface from the implementation, the application logic can be upgraded without affecting the proxy contract. If delegate calls are used, some data schema updates can be performed without breaking the existing data stored on the proxy.
- Cost – Because the proxy is stateful, no cost to migrate data as data migration is not required.
- Transparency – The implementation contract could be replaced by an updated version while ensuring it is visible to all stakeholders. Also, all the previous addresses of the implementation contract are still stored on the blockchain.
- Flexibility – Multiple proxies can expose different versions of implementation contracts.
- Security – Proxy checks for permission before invoking any internal function. This ensures the right function is called with respect to authorization.
Drawbacks
- Cost – If a public blockchain is used, it is costly to deploy two contracts and call one smart contract from another.
- Upgradability – Data schema changes that are inconsistent with how data are already stored on the proxy cannot be supported. The only option is to update the proxy and migrate data to the updated proxy contract.
- Complexity – If data are stored on the proxy, developers need to be careful not to implement a constructor in the implementation contract as it will initialise the state in the implementation contract not on the proxy. Also, the implementation contract should not allow self-destructing the contract either directly or via a delegate call.
Related Patterns
- The data segregation pattern can be used to separate business logic from data.
- Contract registry and this pattern can work together to improve smart contracts’ upgradability.
- The diamond proxy pattern can be used to support granular updates and large contracts.
Known uses
- Augur 2.0 prediction marketplace uses a proxy contract.
- OpenZeppelin’s TransparentUpgradeableProxy contract implements upgrade and admin logic in the proxy itself. This allows transactions by admin address to be handled by the proxy while forwarding transactions from other addresses to the implementation contract.
- In OpenZeppelin’s Universal Upgradable Proxy Standard (UUPS), the upgrade is handled by the implementation contract, and can eventually be removed.
- Compound Finance – a decentralized lending protocol on Ethereum – uses Openzepplin’s Proxy patterns for governance operations.
- MakerDAO decentralized stablecoin platform uses proxy contracts to allow for continuous updates of its smart contracts and system parameters.