Skip to content

Commit

Permalink
EIP-1967: Standard Proxy Storage Slots (ethereum#1967)
Browse files Browse the repository at this point in the history
* Proxy storage slots EIP

* Apply suggestions from code review

Co-Authored-By: spalladino <spalladino@gmail.com>

* Add reference to vyper storage slots

* Fix links to EIPs

Co-Authored-By: spalladino <spalladino@gmail.com>

* Apply suggestions from code review

Co-Authored-By: spalladino <spalladino@gmail.com>

* Applied suggestions by elopio and nventuro

* Add discussions-to

* Add EIP number

* Rename eip file

* Fix type attribute

* Use zOS independent hash preimage

* Implement suggestion by axic
  • Loading branch information
spalladino authored and tkstanczak committed Nov 7, 2020
1 parent 638d12d commit 5375396
Showing 1 changed file with 78 additions and 0 deletions.
78 changes: 78 additions & 0 deletions EIPS/eip-1967.md
@@ -0,0 +1,78 @@
---
eip: 1967
title: Standard Proxy Storage Slots
author: Santiago Palladino (@spalladino)
discussions-to: https://ethereum-magicians.org/t/eip-1967-standard-proxy-storage-slots/3185
status: Draft
type: Standards Track
category: ERC
created: 2019-04-24
---

<!--You can leave these HTML comments in your merged EIP and delete the visible duplicate text guides, they will not appear and may be helpful to refer to if you edit it again. This is the suggested template for new EIPs. Note that an EIP number will be assigned by an editor. When opening a pull request to submit your EIP, please use an abbreviated title in the filename, `eip-draft_title_abbrev.md`. The title should be 44 characters or less.-->

## Simple Summary
<!--"If you can't explain it simply, you don't understand it well enough." Provide a simplified and layman-accessible explanation of the EIP.-->
Standardise how proxies store the address of the logic contract they delegate to, and other proxy specific information.

## Abstract
<!--A short (~200 word) description of the technical issue being addressed.-->
Delegating **proxy contracts** are widely used for both upgradeability and gas savings. These proxies rely on a **logic contract** (also known as implementation contract or master copy) that is called using `delegatecall`. This allows proxies to keep a persistent state (storage and balance) while the code is delegated to the logic contract.

To avoid clashes in storage usage between the proxy and logic contract, the address of the logic contract is typically saved in a [specific storage slot](https://blog.zeppelinos.org/upgradeability-using-unstructured-storage/) guaranteed to be never allocated by a compiler. This EIP proposes a set of standard slots to store proxy information. This allows clients like block explorers to properly extract and show this information to end users, and logic contracts to optionally act upon it.

## Motivation
<!--The motivation is critical for EIPs that want to change the Ethereum protocol. It should clearly explain why the existing protocol specification is inadequate to address the problem that the EIP solves. EIP submissions without sufficient motivation may be rejected outright.-->
Delegating proxies are widely in use, as a means to both support upgrades and reduce gas costs of deployments. Examples of these proxies are found in [ZeppelinOS](https://blog.zeppelinos.org/the-transparent-proxy-pattern/), [Terminal](https://medium.com/terminaldotco/escape-hatch-proxy-efb681de108d), [Gnosis](https://blog.gnosis.pm/solidity-delegateproxy-contracts-e09957d0f201), [AragonOS](https://github.com/aragon/aragonOS/blob/dev/contracts/common/DelegateProxy.sol), [Melonport](https://github.com/melonproject/melon-mail/blob/782aeff9418ac8cdd80875fd6c400bf96f3b03b3/solidity/contracts/DelegateProxy.sol), [Limechain](https://github.com/LimeChain/UpgradeableSolidityContract/blob/14bcabc338130fb2aba2ce8bd27b885305566fce/contracts/Upgradeability/Forwardable.sol), [WindingTree](https://github.com/windingtree/upgradeable-token-labs/blob/af3b66096091d8282d5c9c55c33365315d85f3e1/contracts/upgradable/DelegateProxy.sol), [Decentraland](https://github.com/decentraland/land/blob/5154046844f6f94a5074e82abe01381e6fd7c39d/contracts/upgradable/DelegateProxy.sol), and many others.

However, the lack of a common interface for obtaining the logic address for a proxy makes it impossible to build common tools that act upon this information.

A classic example of this is a block explorer. Here, the end user wants to interact with the underlying logic contract and not the proxy itself. Having a common way to retrieve the logic contract address from a proxy would allow a block explorer, among other things, to show the ABI of the logic contract and not that of the proxy (see [this proxy](https://etherscan.io/token/0x00fdae9174357424a78afaad98da36fd66dd9e03#readContract) for an example). The explorer should check the storage of the contract at the distinguished slots to determine if it is indeed a proxy, in which case it should show information on both the proxy and the logic contract.

Another example are logic contracts that explicitly act upon the fact that they are being proxied. This allows them to potentially trigger a code update as part of their logic, as is the case of [Universal Upgradeable Proxy Standard (EIP1822)](https://eips.ethereum.org/EIPS/eip-1822). A common storage slot allows these use cases independently of the specific proxy implementation being used.

## Specification
<!--The technical specification should describe the syntax and semantics of any new feature. The specification should be detailed enough to allow competing, interoperable implementations for any of the current Ethereum platforms (go-ethereum, parity, cpp-ethereum, ethereumj, ethereumjs, and [others](https://github.com/ethereum/wiki/wiki/Clients)).-->
The main requirement for the storage slots chosen is that they must never be picked by the compiler to store any contract state variable. Otherwise, a logic contract could inadvertently overwrite this information on the proxy when writing to a variable of its own.

[Solidity](https://solidity.readthedocs.io/en/v0.4.21/miscellaneous.html#layout-of-state-variables-in-storage) maps variables to storage based on the order in which they were declared, after the contract inheritance chain is linearized: the first variable is assigned the first slot, and so on. The exception are values in dynamic arrays and mappings, which are stored in the hash of the concatenation of the key and the storage slot. The Solidity development team has [confirmed](https://twitter.com/ethchris/status/1073692785176444928) that the storage layout is to be preserved among new versions. Vyper seems to [follow the same strategy as Solidity](https://github.com/ethereum/vyper/issues/769). Note that contracts written in other languages, or directly in assembly, may incur in clashes.

As such, the proposed storage slots for proxy-specific information are the following. They are chosen in such a way so they are guaranteed to not clash with state variables allocated by the compiler, since they depend on the hash of a string that does not start with a storage index. Furthermore, a `-1` offset is added so the preimage of the hash cannot be known, further reducing the chances of a possible attack.

More slots for additional information can be added in subsequent ERCs as needed.

### Logic contract address

Storage slot `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
(obtained as `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)`).

Holds the address of the logic contract that this proxy delegates to.

### Admin address

Storage slot `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
(obtained as `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)`).

Holds the address that is allowed to upgrade the logic contract address for this proxy (optional).

## Rationale
<!--The rationale fleshes out the specification by describing what motivated the design and why particular design decisions were made. It should describe alternate designs that were considered and related work, e.g. how the feature is supported in other languages. The rationale may also provide evidence of consensus within the community, and should discuss important objections or concerns raised during discussion.-->

This EIP standardises the **storage slot** for the logic contract address, instead of a public method on the proxy contract as [DelegateProxy (EIP897)](https://eips.ethereum.org/EIPS/eip-897) does. The rationale for this is that proxies should never expose functions to end users that could potentially clash with those of the logic contract.

Note that a clash may occur even among functions with different names, since the ABI relies on just four bytes for the function selector. This can lead to unexpected errors, or even exploits, where a call to a proxied contract returns a different value than expected, since the proxy intercepts the call and answers with a value of its own.

From [_Malicious backdoors in Ethereum proxies_](https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357) by Nomic Labs:

> Any function in the Proxy contract whose selector matches with one in the implementation contract will be called directly, completely skipping the implementation code.
>
> Because the function selectors use a fixed amount of bytes, there will always be the possibility of a clash. This isn’t an issue for day to day development, given that the Solidity compiler will detect a selector clash within a contract, but this becomes exploitable when selectors are used for cross-contract interaction. Clashes can be abused to create a seemingly well-behaved contract that’s actually concealing a backdoor.
The fact that proxy public functions are potentially exploitable makes it necessary to standardise the logic contract address in a different way. This approach is also used as part of [Universal Upgradeable Proxy Standard (EIP1822)](https://eips.ethereum.org/EIPS/eip-1822), which could become a specialization of this EIP.

## Implementation
<!--The implementations must be completed before any EIP is given status "Final", but it need not be completed before the EIP is accepted. While there is merit to the approach of reaching consensus on the specification and rationale before writing code, the principle of "rough consensus and running code" is still useful when it comes to resolving many discussions of API details.-->
Sample proxy implementations that follow this standard can be found in the [ZeppelinOS repository](https://github.com/zeppelinos/zos/blob/dc9e4ed/packages/lib/contracts/upgradeability/BaseUpgradeabilityProxy.sol), albeit with a different set of slots.

## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).

0 comments on commit 5375396

Please sign in to comment.