eip-1822.md
1 --- 2 eip: 1822 3 title: Universal Upgradeable Proxy Standard (UUPS) 4 author: Gabriel Barros <gabriel@terminal.co>, Patrick Gallagher <blockchainbuddha@gmail.com> 5 discussions-to: https://ethereum-magicians.org/t/eip-1822-universal-upgradeable-proxy-standard-uups 6 status: Draft 7 type: Standards Track 8 category: ERC 9 created: 2019-03-04 10 --- 11 12 ## Table of contents 13 14 <!-- TOC --> 15 16 - [Table of contents](#table-of-contents) 17 - [Simple Summary](#simple-summary) 18 - [Abstract](#abstract) 19 - [Motivation](#motivation) 20 - [Terminology](#terminology) 21 - [Specification](#specification) 22 - [Proxy Contract](#proxy-contract) 23 - [Functions](#functions) 24 - [`fallback`](#fallback) 25 - [`constructor`](#constructor) 26 - [Proxiable Contract](#proxiable-contract) 27 - [Functions](#functions-1) 28 - [`proxiable`](#proxiable) 29 - [`updateCodeAddress`](#updatecodeaddress) 30 - [Pitfalls when using a proxy](#pitfalls-when-using-a-proxy) 31 - [Separating Variables from Logic](#separating-variables-from-logic) 32 - [Restricting dangerous functions](#restricting-dangerous-functions) 33 - [Examples](#examples) 34 - [Owned](#owned) 35 - [ERC-20 Token](#erc-20-token) 36 - [Proxy Contract](#proxy-contract-1) 37 - [Token Logic Contract](#token-logic-contract) 38 - [References](#references) 39 - [Copyright](#copyright) 40 <!-- /TOC --> 41 42 ## Simple Summary 43 44 Standard upgradeable proxy contract. 45 46 ## Abstract 47 48 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. 49 50 ## Motivation 51 52 - Improve upon existing proxy implementations to improve developer experience for deploying and maintaining Proxy and Logic Contracts. 53 54 - Standardize and improve the methods for verifying the bytecode used by the Proxy Contract. 55 56 ## Terminology 57 58 - `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)) 59 - **Proxy Contract** - The contract **A** which stores data, but uses the logic of external contract **B** by way of `delegatecall()`. 60 - **Logic Contract** - The contract **B** which contains the logic used by Proxy Contract **A** 61 - **Proxiable Contract** - Inherited in Logic Contract **B** to provide the upgrade functionality 62 63 <p align="center"><img src="../assets/eip-1822/proxy-diagram.png" alt="diagram" width="600"/></p> 64 65 ## Specification 66 67 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. 68 69 ### Proxy Contract 70 71 #### Functions 72 73 ##### `fallback` 74 75 The proposed fallback function follows the common pattern seen in other Proxy Contract implementations such as [Zeppelin][1] or [Gnosis][2]. 76 77 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. 78 79 ```javascript 80 function() external payable { 81 assembly { // solium-disable-line 82 let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) 83 calldatacopy(0x0, 0x0, calldatasize) 84 let success := delegatecall(sub(gas, 10000), contractLogic, 0x0, calldatasize, 0, 0) 85 let retSz := returndatasize 86 returndatacopy(0, 0, retSz) 87 switch success 88 case 0 { 89 revert(0, retSz) 90 } 91 default { 92 return(0, retSz) 93 } 94 } 95 } 96 ``` 97 98 #### `constructor` 99 100 The proposed constructor accepts any number of arguments of any type, and thus is compatible with any Logic Contract constructor function. 101 102 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. 103 104 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. 105 106 The contract below shows the proposed implementation of the Proxy Contract. 107 108 ```javascript 109 contract Proxy { 110 // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" 111 constructor(bytes memory constructData, address contractLogic) public { 112 // save the code address 113 assembly { // solium-disable-line 114 sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, contractLogic) 115 } 116 (bool success, bytes memory _ ) = contractLogic.delegatecall(constructData); // solium-disable-line 117 require(success, "Construction failed"); 118 } 119 120 function() external payable { 121 assembly { // solium-disable-line 122 let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) 123 calldatacopy(0x0, 0x0, calldatasize) 124 let success := delegatecall(sub(gas, 10000), contractLogic, 0x0, calldatasize, 0, 0) 125 let retSz := returndatasize 126 returndatacopy(0, 0, retSz) 127 switch success 128 case 0 { 129 revert(0, retSz) 130 } 131 default { 132 return(0, retSz) 133 } 134 } 135 } 136 } 137 ``` 138 139 ### Proxiable Contract 140 141 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. 142 143 > :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) 144 145 #### Functions 146 147 ##### `proxiable` 148 149 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")`. 150 151 ##### `updateCodeAddress` 152 153 Stores the Logic Contract's address at storage `keccak256("PROXIABLE")` in the Proxy Contract. 154 155 The contract below shows the proposed implementation of the Proxiable Contract. 156 157 ```javascript 158 contract Proxiable { 159 // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" 160 161 function updateCodeAddress(address newAddress) internal { 162 require( 163 bytes32(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) == Proxiable(newAddress).proxiableUUID(), 164 "Not compatible" 165 ); 166 assembly { // solium-disable-line 167 sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, newAddress) 168 } 169 } 170 function proxiableUUID() public pure returns (bytes32) { 171 return 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7; 172 } 173 } 174 ``` 175 176 ## Pitfalls when using a proxy 177 178 The following common best practices should be employed for all Logic Contracts when using a proxy contract. 179 180 ### Separating Variables from Logic 181 182 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 183 184 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. 185 186 ### Restricting dangerous functions 187 188 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. 189 190 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. 191 192 ## Examples 193 194 ### Owned 195 196 In this example, we show the standard ownership example, and restrict the `updateCodeAddress` to only the owner. 197 198 ```javascript 199 contract Owned is Proxiable { 200 // ensures no one can manipulate this contract once it is deployed 201 address public owner = address(1); 202 203 function constructor1() public{ 204 // ensures this can be called only once per *proxy* contract deployed 205 require(owner == address(0)); 206 owner = msg.sender; 207 } 208 209 function updateCode(address newCode) onlyOwner public { 210 updateCodeAddress(newCode); 211 } 212 213 modifier onlyOwner() { 214 require(msg.sender == owner, "Only owner is allowed to perform this action"); 215 _; 216 } 217 } 218 ``` 219 220 ### ERC-20 Token 221 222 #### Proxy Contract 223 224 ```javascript 225 pragma solidity ^0.5.1; 226 227 contract Proxy { 228 // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" 229 constructor(bytes memory constructData, address contractLogic) public { 230 // save the code address 231 assembly { // solium-disable-line 232 sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, contractLogic) 233 } 234 (bool success, bytes memory _ ) = contractLogic.delegatecall(constructData); // solium-disable-line 235 require(success, "Construction failed"); 236 } 237 238 function() external payable { 239 assembly { // solium-disable-line 240 let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) 241 calldatacopy(0x0, 0x0, calldatasize) 242 let success := delegatecall(sub(gas, 10000), contractLogic, 0x0, calldatasize, 0, 0) 243 let retSz := returndatasize 244 returndatacopy(0, 0, retSz) 245 switch success 246 case 0 { 247 revert(0, retSz) 248 } 249 default { 250 return(0, retSz) 251 } 252 } 253 } 254 } 255 ``` 256 257 #### Token Logic Contract 258 259 ``` javascript 260 261 contract Proxiable { 262 // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" 263 264 function updateCodeAddress(address newAddress) internal { 265 require( 266 bytes32(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) == Proxiable(newAddress).proxiableUUID(), 267 "Not compatible" 268 ); 269 assembly { // solium-disable-line 270 sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, newAddress) 271 } 272 } 273 function proxiableUUID() public pure returns (bytes32) { 274 return 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7; 275 } 276 } 277 278 279 contract Owned { 280 281 address owner; 282 283 function setOwner(address _owner) internal { 284 owner = _owner; 285 } 286 modifier onlyOwner() { 287 require(msg.sender == owner, "Only owner is allowed to perform this action"); 288 _; 289 } 290 } 291 292 contract LibraryLockDataLayout { 293 bool public initialized = false; 294 } 295 296 contract LibraryLock is LibraryLockDataLayout { 297 // Ensures no one can manipulate the Logic Contract once it is deployed. 298 // PARITY WALLET HACK PREVENTION 299 300 modifier delegatedOnly() { 301 require(initialized == true, "The library is locked. No direct 'call' is allowed"); 302 _; 303 } 304 function initialize() internal { 305 initialized = true; 306 } 307 } 308 309 contact ERC20DataLayout is LibraryLockDataLayout { 310 uint256 public totalSupply; 311 mapping(address=>uint256) public tokens; 312 } 313 314 contract ERC20 { 315 // ... 316 function transfer(address to, uint256 amount) public { 317 require(tokens[msg.sender] >= amount, "Not enough funds for transfer"); 318 tokens[to] += amount; 319 tokens[msg.sender] -= amount; 320 } 321 } 322 323 contract MyToken is ERC20DataLayout, ERC20, Owned, Proxiable, LibraryLock { 324 325 function constructor1(uint256 _initialSupply) public { 326 totalSupply = _initialSupply; 327 tokens[msg.sender] = _initialSupply; 328 initialize(); 329 setOwner(msg.sender); 330 } 331 function updateCode(address newCode) public onlyOwner delegatedOnly { 332 updateCodeAddress(newCode); 333 } 334 function transfer(address to, uint256 amount) public delegatedOnly { 335 ERC20.transfer(to, amount); 336 } 337 } 338 ``` 339 340 ## References 341 342 - ["Escape-hatch" proxy Medium Post](https://medium.com/terminaldotco/escape-hatch-proxy-efb681de108d) 343 344 ## Copyright 345 346 Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). 347 348 [1]: https://github.com/maraoz/solidity-proxy/blob/master/contracts/Dispatcher.sol 349 [2]: https://blog.gnosis.pm/solidity-delegateproxy-contracts-e09957d0f201