Skip to content

Commit

Permalink
editor revisions (ethereum#5061)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamWilsn authored and PowerStream3604 committed May 19, 2022
1 parent bbc3c73 commit fc4eb85
Showing 1 changed file with 18 additions and 20 deletions.
38 changes: 18 additions & 20 deletions EIPS/eip-1967.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Standardise where proxies store the address of the logic contract t
author: Santiago Palladino (@spalladino), Francisco Giordano (@frangio)
discussions-to: https://ethereum-magicians.org/t/eip-1967-standard-proxy-storage-slots/3185
status: Last Call
last-call-deadline: 2022-04-24
last-call-deadline: 2022-05-14
type: Standards Track
category: ERC
created: 2019-04-24
Expand All @@ -14,34 +14,24 @@ created: 2019-04-24
## Abstract
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.openzeppelin.com/the-state-of-smart-contract-upgrades/#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.
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 (for example `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` in OpenZeppelin contracts) 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
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 [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts/4.x/api/proxy), [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.
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 OpenZeppelin Contracts, Gnosis, AragonOS, Melonport, Limechain, WindingTree, Decentraland, 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 allows a block explorer to show the ABI of the logic contract and not that of the proxy. The explorer checks the storage of the contract at the distinguished slots to determine if it is indeed a proxy, in which case it shows information on both the proxy and the logic contract. As an example, this is how `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` is shown on Etherscan:

![Sample proxy on Etherscan](../assets/eip-1967/Sample-proxy-on-etherscan.png)

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 [EIP-1822](./eip-1822.md). A common storage slot allows these use cases independently of the specific proxy implementation being used.
Another example is 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. A common storage slot allows these use cases independently of the specific proxy implementation being used.

## Specification
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 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 that the storage layout is to be preserved among new versions:

> The layout of state variables in storage is considered to be part of the external interface of Solidity due to the fact that storage pointers can be passed to libraries. This means that any change to the rules outlined in this section is considered a breaking change of the language and due to its critical nature should be considered very carefully before being executed. In the event of such a breaking change, we would want to release a compatibility mode in which the compiler would generate bytecode supporting the old layout.
Vyper seems to follow the same strategy as Solidity. 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.

Monitoring of proxies is essential to the security of many applications. It is thus essential to have the ability to track changes to the implementation and admin slots. Unfortunately, tracking changes to storage slots is not easy. Consequently, it is recommended that any function that changes any of these slots SHOULD also emit the corresponding event. This includes initialization, from `0x0` to the first non-zero value.

The proposed storage slots for proxy-specific information are the following. More slots for additional information can be added in subsequent ERCs as needed.

### Logic contract address

Storage slot `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
Expand Down Expand Up @@ -94,17 +84,25 @@ This EIP standardises the **storage slot** for the logic contract address, inste

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:
From _Malicious backdoors in Ethereum proxies_ 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 [EIP-1822](./eip-1822.md).
The fact that proxy public functions are potentially exploitable makes it necessary to standardise the logic contract address in a different way.

## Reference Implementation
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 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 is 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 that the storage layout is to be preserved among new versions:

> The layout of state variables in storage is considered to be part of the external interface of Solidity due to the fact that storage pointers can be passed to libraries. This means that any change to the rules outlined in this section is considered a breaking change of the language and due to its critical nature should be considered very carefully before being executed. In the event of such a breaking change, we would want to release a compatibility mode in which the compiler would generate bytecode supporting the old layout.
A reference implementation for this standard can be found in the [OpenZeppelin Contracts repository](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.3.0/contracts/proxy/ERC1967/ERC1967Proxy.sol).
Vyper seems to follow the same strategy as Solidity. Note that contracts written in other languages, or directly in assembly, may incur in clashes.

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.

## Reference Implementation

```solidity
/**
Expand Down

0 comments on commit fc4eb85

Please sign in to comment.