The following describes a standard for proxy contracts which is universally compatible with all contracts, and does not create incompatibility between the proxy and business-logic contracts. This is achieved by utilizing a unique storage position in the proxy contract to store the Logic Contract's address. A compatibility check ensures successful upgrades. Upgrading can be performed unlimited times, or as determined by custom logic. In addition, a method for selecting from multiple constructors is provided, which does not inhibit the ability to verify bytecode.
## Motivation
- Improve upon existing proxy implementations to improve developer experience for deploying and maintaining Proxy and Logic Contracts.
- Standardize and improve the methods for verifying the bytecode used by the Proxy Contract.
## Terminology
-`delegatecall()` - Function in contract **A** which allows an external contract **B** (delegating) to modify **A**'s storage (see diagram below, [Solidity docs](https://solidity.readthedocs.io/en/v0.5.3/introduction-to-smart-contracts.html#delegatecall-callcode-and-libraries))
- **Proxy Contract** - The contract **A** which stores data, but uses the logic of external contract **B** by way of `delegatecall()`.
- **Logic Contract** - The contract **B** which contains the logic used by Proxy Contract **A**
- **Proxiable Contract** - Inherited in Logic Contract **B** to provide the upgrade functionality
The Proxy Contract proposed here should be deployed _as is_, and used as a drop-in replacement for any existing methods of lifecycle management of contracts. In addition to the Proxy Contract, we propose the Proxiable Contract interface/base which establishes a pattern for the upgrade which does not interfere with existing business rules. The logic for allowing upgrades can be implemented as needed.
### Proxy Contract
#### Functions
##### `fallback`
The proposed fallback function follows the common pattern seen in other Proxy Contract implementations such as [Zeppelin][1] or [Gnosis][2].
However, rather than forcing use of a variable, the address of the Logic Contract is stored at the defined storage position `keccak256("PROXIABLE")`. This eliminates the possibility of collision between variables in the Proxy and Logic Contracts, thus providing "universal" compatibility with any Logic Contract.
```javascript
function() external payable {
assembly { // solium-disable-line
let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7)
calldatacopy(0x0, 0x0, calldatasize)
let success := delegatecall(sub(gas, 10000), contractLogic, 0x0, calldatasize, 0, 0)
let retSz := returndatasize
returndatacopy(0, 0, retSz)
switch success
case 0 {
revert(0, retSz)
}
default {
return(0, retSz)
}
}
}
```
#### `constructor`
The proposed constructor accepts any number of arguments of any type, and thus is compatible with any Logic Contract constructor function.
In addition, the arbitrary nature of the Proxy Contract's constructor provides the ability to select from one or more constructor functions available in the Logic Contract source code (e.g., `constructor1`, `constructor2`, ... etc. ). Note that if multiple constructors are included in the Logic Contract, a check should be included to prohibit calling a constructor again post-initialization.
It's worth noting that the added functionality of supporting multiple constructors does not inhibit verification of the Proxy Contract's bytecode, since the initialization tx call data (input) can be decoded by first using the Proxy Contract ABI, and then using the Logic Contract ABI.
The contract below shows the proposed implementation of the Proxy Contract.
```javascript
contract Proxy {
// Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"
constructor(bytes memory constructData, address contractLogic) public {
let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7)
calldatacopy(0x0, 0x0, calldatasize)
let success := delegatecall(sub(gas, 10000), contractLogic, 0x0, calldatasize, 0, 0)
let retSz := returndatasize
returndatacopy(0, 0, retSz)
switch success
case 0 {
revert(0, retSz)
}
default {
return(0, retSz)
}
}
}
}
```
### Proxiable Contract
The Proxiable Contract is included in the Logic Contract, and provides the functions needed to perform an upgrade. The compatibility check `proxiable` prevents irreparable updates during an upgrade.
> :warning: Warning: `updateCodeAddress` and `proxiable` must be present in the Logic Contract. Failure to include these may prevent upgrades, and could allow the Proxy Contract to become entirely unusable. See below [Restricting dangerous functions](#restricting-dangerous-functions)
Compatibility check to ensure the new Logic Contract implements the Universal Upgradeable Proxy Standard. Note that in order to support future implementations, the `bytes32` comparison could be changed e.g., `keccak256("PROXIABLE-ERC1822-v1")`.
The following common best practices should be employed for all Logic Contracts when using a proxy contract.
### Separating Variables from Logic
Careful consideration should be made when designing a new Logic Contract to prevent incompatibility with the existing storage of the Proxy Contract after an upgrade. Specifically, the order in which variables are instantiated in the new contract should not be modified, and any new variables should be added after all existing variables from the previous Logic Contract
To facilitate this practice, we recommend utilizing a single "base" contract which holds all variables, and which is inherited in subsequent logic contract(s). This practice greatly reduces the chances of accidentally reordering variables or overwriting them in storage.
### Restricting dangerous functions
The compatibility check in the Proxiable Contract is a safety mechanism to prevent upgrading to a Logic Contract which does not implement the Universal Upgradeable Proxy Standard. However, as occurred in the parity wallet hack, it is still possible to perform irreparable damage to the Logic Contract itself.
In order to prevent damage to the Logic Contract, we recommend restricting permissions for any potentially damaging functions to `onlyOwner`, and giving away ownership of the Logic Contract immediately upon deployment to a null address (e.g., address(1)). Potentially damaging functions include native functions such as `SELFDESTRUCT`, as well functions whose code may originate externally such as `CALLCODE`, and `delegatecall()`. In the [ERC-20 Token](#erc-20-token) example below, a `LibraryLock` contract is used to prevent destruction of the logic contract.