-
Notifications
You must be signed in to change notification settings - Fork 328
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
3,863 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
--- | ||
eip: 7546 | ||
title: Upgradeable Clone for Scalable Contracts | ||
description: An upgradeable, cloneable, horizontally extensible proxy pattern. | ||
author: Shogo Ochiai (@shogochiai) <shogo.ochiai@pm.me>, Kai Hiroi (@KaiHiroi) <kai.hiroi@pm.me> | ||
discussions-to: https://ethereum-magicians.org/t/eip-7546-upgradeable-clone/16256 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2023-10-25 | ||
requires: 165, 1967, 7201 | ||
--- | ||
|
||
## Abstract | ||
It has been a significant challenge for developers attempting to create cloneable and upgradeable contracts on the Ethereum Virtual Machine (EVM). While [ERC-2535](./eip-2535.md) Diamonds and other existing proxy standards offer partial solutions, a comprehensive answer has remained elusive. Our proposal addresses this gap through the introduction of two main features. | ||
|
||
### Function-Level Upgradeability | ||
In alignment with [ERC-2535](./eip-2535.md), this functionality permits the selective redirection of implementation contracts for individual function calls. This granular control over upgrades allows for modifications on a per-function basis. Moreover, segmenting implementation contracts by function helps mitigate the limitations posed by the contract size cap (24.576kB as of EVM version Shanghai or earlier). | ||
|
||
### Factory/Clone-Friendly & Simultaneous Upgradeability | ||
Drawing on the Beacon model from [ERC-1967](./eip-1967.md), our method aims to streamline the process of cloning and updating Proxy contracts simultaneously. This approach is designed to maintain consistent functionality across different instances, each with its own state. Typically, proxies are limited to basic upgradeability features or follow the [ERC-1167](./eip-1167.md) standard. However, our solution combines both functionalities into a compact proxy. | ||
|
||
## Motivation | ||
Smart contract development often encounters hurdles due to the inherent limitations of the Ethereum Virtual Machine (EVM), such as the contract size limit and stack depth. Additionally, addressing vulnerabilities in both the smart contract logic and its compiler are persistent issues. While there is a desire to minimize reliance on trusted third parties for upgradeability, introducing complex governance structures for upgrade management can significantly increase the workload for crypto DevOps, adding to the apprehension developers may feel towards advancing their projects. This apprehension can restrict the complexity and innovation within smart contract development. Our approach seeks to simplify smart contract programming, making it more accessible and enjoyable. It does so by clearly delineating DevOps concerns from business logic, thereby enhancing codebase clarity, facilitating audits, and allowing for more focused analysis through Language Model (LM) techniques, tailored to specific infrastructure and domain needs. | ||
|
||
### Use Cases | ||
Over time, various smart contract design patterns have been proposed and utilized. This *Upgradeable Clone Standard (UCS)* is intended for scenarios where these existing patterns may not suffice. To clarify it, we define some key terms: | ||
|
||
- **Contract-Level Upgradeability**: One Proxy contract corresponds to one Implementation contract, responsible for all logic of the Proxy. | ||
- **Function-Level Upgradeability**: One Proxy contract corresponds to multiple Implementation contracts, basically each responsible for a specific function. | ||
- **Factory**: A contract that clones Proxies with a common Implementation(s). In the context of upgradeability, it allows for the simultaneous upgrade of these cloned Proxies. | ||
|
||
Here are the use cases: | ||
|
||
1. For basic needs without Upgradeability or a Factory, *Regular smart contract deployment* suffices. | ||
2. When a Factory is needed without Upgradeability, [ERC-1167](./eip-1167.md) is suitable. | ||
3. For Contract-Level Upgradeability without a Factory, [ERC-1822](./eip-1822.md) can be used. | ||
4. For Contract-Level Upgradeability with a Factory, the Beacon from [ERC-1967](./eip-1967.md) is applicable. | ||
5. For Function-Level Upgradeability without a Factory, [ERC-2535](./eip-2535.md) is available. | ||
6. For Function-Level Upgradeability with a Factory, this ***Upgradeable Clone Standard*** is the ideal choice. | ||
|
||
![Fig. Use Cases](../assets/eip-7546/images/usecases.svg) | ||
|
||
|
||
## Specification | ||
> The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. | ||
In the EVM, contract accounts are characterized by four primary fields: *nonce*, *balance*, *code*, and *storage*. This ERC's architecture modularizes these functionalities into three distinct types of contracts, each serving a specific purpose when combined to represent a single account: | ||
|
||
1. **Proxy Contract**: Maintains the state of the contract account, such as nonce, balance, and storage. This contract delegatecalls to the _Function Contract_ as registered in the _Dictionary Contract_, ensuring the state and logic are separated but effectively integrated. | ||
2. **Dictionary Contract**: Acts as a dispatcher that routes function calls based on their selectors to the appropriate _Function Contract_. It manages the dynamic aspects of contract behavior, facilitating function upgrades and dynamic addressing. By externalizing this contract from the _Proxy Contract_, it becomes factory/clone-friendly and supports simultaneous upgradeability. | ||
3. **Function (Implementation) Contract**: Implements the executable logic for function calls. When delegatecalled by the _Proxy Contract_, it performs the actual computations or logic as defined in the contract's code. | ||
|
||
This architecture not only aligns with the core attributes of an EVM contract account but also significantly enhances the modularity, upgradeability, and scalability of smart contracts by clarifying account state, function dispatching, and logic implementation. | ||
|
||
### Proxy Contract | ||
This contract requests the _Dictionary Contract_ to retrieve the associated _Function Contract_ address based on its function selector, and then delegatecall to it. | ||
|
||
#### Storage & Events | ||
This contract SHOULD store the _Dictionary Contract_ address in the storage slot `0x267691be3525af8a813d30db0c9e2bad08f63baecf6dceb85e2cf3676cff56f4`, obtained as `bytes32(uint256(keccak256('erc7546.proxy.dictionary')) - 1)`, in accordance with the method defined in [ERC-1967](./eip-1967.md). This ensures that the address is stored in a secure and predictable slot. | ||
|
||
Changes to the Dictionary address SHOULD emit events. When such an event is emitted, it MUST use the signature: | ||
```solidity | ||
event DictionaryUpgraded(address dictionary); | ||
``` | ||
|
||
#### Functions | ||
For every invocation made via `CALL` or `STATICCALL`, this contract MUST perform a delegatecall to the corresponding _Function Contract_ address retrieved from the _Dictionary Contract_ using the `getImplementation(bytes4 functionSelector)` function. This contract MUST also process the return value from this delegatecall to ensure the intended functionality is executed correctly. Furthermore, to avoid potential collisions with function selectors registered in the _Dictionary Contract_, the Proxy SHOULD NOT define any external functions. | ||
|
||
### Dictionary Contract | ||
This contract manages a mapping of function selectors to corresponding _Function Contract_ addresses. It uses this mapping to handle requests from the _Proxy Contract_. | ||
|
||
#### Storage & Events | ||
The Dictionary MUST maintain a mapping of function selectors to _Function Contract_ addresses. | ||
|
||
Changes to this mapping SHOULD be communicated through an event (or log). | ||
|
||
```solidity | ||
event ImplementationUpgraded(bytes4 functionSelector, address implementation); | ||
``` | ||
|
||
#### Functions | ||
##### `getImplementation` | ||
This contract MUST implement this function to return _Function Implementation Contract_ address. | ||
|
||
```solidity | ||
function getImplementation(bytes4 functionSelector) external view returns(address implementation); | ||
``` | ||
|
||
##### `setImplementation` | ||
This contract SHOULD implement this function to update or add new function selectors and their corresponding _Function Implementation Contract_ addresses to the mapping. | ||
|
||
```solidity | ||
function setImplementation(bytes4 functionSelector, address implementation) external; | ||
``` | ||
|
||
##### `supportsInterface` | ||
This contract is RECOMMENDED to implement the `supportsInterface(bytes4 interfaceID)` function defined in [ERC-165](./eip-165.md) to indicate which interfaces are supported by the contracts referenced in the mapping. | ||
|
||
##### `supportsInterfaces` | ||
This contract is RECOMMENDED to implement the `supportsInterfaces()` to return a list of registered interfaceIDs. | ||
```solidity | ||
function supportsInterfaces() public view returns (bytes4[] memory); | ||
``` | ||
|
||
### Function (Implementation) Contract | ||
This contract acts as the logic implementation contract that the _Proxy Contract_ delegatecalls and it's address is registered with the function selector in the _Dictionary Contract_. | ||
|
||
#### Storage & Events | ||
This contract SHOULD NOT use its storage but SHOULD store to the _Proxy Contract_ through delegatecall. | ||
|
||
The _Proxy Contract_ shares storage layout with several _Function Contracts_. For example, using sequential slot allocation starting from slot 0, as is the default compiler option, can lead to storage conflicts. | ||
|
||
In order to prevent storage conflict, this contract MUST manage the storage layout properly. The matter of storage management techniques has been a subject of debate for years, both at the ERC level and the language level. However, there is still no definitive standard. Therefore, this ERC does not go into the specifics of storage management techniques. | ||
|
||
It is RECOMMENDED to choose the storage management method that is considered most appropriate at the time. | ||
|
||
For instance, the storage could be arranged according to useful storage layout patterns, such as ***[ERC-7201](./eip-7201.md)***. | ||
|
||
#### Functions | ||
This contract MUST have the same function selector registered in the _Dictionary Contract_. If not, the Proxy's delegatecall will fail. So it is RECOMMENDED for each _Function Contract_ to implement ERC-165's `supportsInterface(bytes4 interfaceID)` to ensure that it correctly implements the function selector being registered when added to the Dictionary. | ||
|
||
|
||
## Rationale | ||
### Comparison with [ERC-2535](./eip-2535.md) | ||
While both this ERC and ERC-2535 offer [Function-Level Upgradeability](#function-level-upgradeability), there is a key distinction in their approaches. ERC-2535 maintains a mapping of implementation contracts (referred to as Facets in ERC-2535) within the Proxy itself. In contrast, this ERC stores the mapping in an external _Dictionary Contract_. This externalization of the mapping facilitates another significant feature of this standard: [Factory/Clone-Friendly & Simultaneous Upgradeability](#factoryclone-friendly--simultaneous-upgradeability). By separating the mapping from the Proxy, this design allows for easier cloning of contracts and their simultaneous upgrade, which is not as straightforward in the ERC-2535 framework. | ||
|
||
![Fig. Comparison with Diamond](../assets/eip-7546/images/comparison-with-diamond.svg) | ||
|
||
### Separating the Dictionary and Proxy contracts: | ||
The separation of the Dictionary from the Proxy was driven by aligning with [Factory/Clone-Friendly & Simultaneous Upgradeability](#factoryclone-friendly--simultaneous-upgradeability). | ||
|
||
To achieve this, the management functionality of _Function Implementation Contract_ addresses were externalized as the _Dictionary Contract_ instead of including them within the _Proxy Contract_, a concept akin to the Beacon Proxy approach. | ||
|
||
If the functionality is within the _Proxy Contract_, each proxy requires its implementation to be upgraded. | ||
By externalizing this, a common implementation can be cloned and upgraded simultaneously. | ||
|
||
![Fig. Comparison with Beacon](../assets/eip-7546/images/comparison-with-beacon.svg) | ||
|
||
### Utilizing the mapping of function selectors and implementation addresses: | ||
The utilization of the mapping of function selectors to corresponding _Function Implementation Contract_ addresses of the _Dictionary Contract_ by the _Proxy Contract_, followed by delegatecalling to the returned implementation address, aligns with [Function-Level Upgradeability](#function-level-upgradeability). | ||
|
||
By adopting this approach, the Proxy emulates the behavior of possessing a set of _Function Implementation Contracts_ registered within the _Dictionary Contract_. This specification closely resembles the pattern outlined in the Diamond Standard. | ||
|
||
|
||
## Reference Implementation | ||
There are reference implementations and tests as a foundry project. | ||
|
||
It includes the following contents: | ||
- Reference Implementations | ||
- [Proxy Contract](../assets/eip-7546/src/proxy/ERC7546Proxy.sol) | ||
- [Dictionary Contract](../assets/eip-7546/src/dictionary/Dictionary.sol) | ||
- Tests | ||
- [Test suite](../assets/eip-7546/test/ERC7546Test.t.sol) | ||
|
||
|
||
## Security Considerations | ||
### Delegation of Implementation Management | ||
This pattern of delegating all implementations for every call to the _Dictionary Contract_ relies on the assumption that the _Dictionary Contract_'s admin acts in good faith and does not introduce vulnerabilities through negligence. | ||
|
||
You should not connect your proxy with the _Dictionary Contract_ provided by an untrusted admin. Moreover, providing an option to switch to another _Dictionary Contract_ managed by a different (or potentially more trustworthy) admin is recommended. | ||
|
||
While it is possible to store the _Dictionary Contract_ address in the code area (e.g., using Solidity's immutable or constant), it SHOULD be designed with caution, considering the possibility that if the _Dictionary Contract_'s admin is not the same as the _Proxy Contract_'s admin, the ability to manipulate the implementation could be permanently lost. | ||
|
||
### Storage Conflict | ||
As mentioned in the above [Storage section](#storage--events-2). This design pattern involves multiple _Function Implementation Contracts_ sharing a single _Proxy Contract_ storage. Therefore, it's important to take care for preventing storage conflicts by using the storage management method that is considered most appropriate at the time. | ||
|
||
### Mismatch Function Selector | ||
The _Dictionary Contract_ returns the _Function Implementation Contract_ address based on the _Proxy Contract_'s invoked function selector. | ||
|
||
If there is a mismatch between function selectors registered in the _Dictionary Contract_ and those implemented in the _Function Implementation Contract_, the execution will fail. To prevent unexpected behavior, it's recommended to check that the _Function Implementation Contract_ includes the function selector (interface) being registered during the process for setting implementation address to the _Dictionary Contract_. | ||
|
||
### Handling of CALL and STATICCALL | ||
The _Proxy Contract_ is designed primarily to respond to `CALL` and `STATICCALL` opcodes. Should a `DELEGATECALL` be made to this _Proxy Contract_, it will attempt to request the _Dictionary Contract_ for a corresponding implementation via the `getImplementation(bytes4 functionSelector)` function, using the stored _Dictionary Contract_ address within its own storage. Although this action may not lead to the intended outcome if the calling contract's storage layout does not align with expectations, it does not constitute a direct threat to the _Proxy Contract_ itself. Developers are cautioned that invoking this _Proxy Contract_ via `DELEGATECALL` could result in unexpected and potentially non-functional outcomes, making it an unsuitable method for interaction. | ||
|
||
|
||
## Copyright | ||
Copyright and related rights waived via [CC0](../LICENSE.md). |
Oops, something went wrong.