diff --git a/EIPS/eip-2535.md b/EIPS/eip-2535.md new file mode 100644 index 0000000000000..441643f7f3de0 --- /dev/null +++ b/EIPS/eip-2535.md @@ -0,0 +1,478 @@ +--- +eip: 2535 +title: Diamond Standard +author: Nick Mudge +discussions-to: https://github.com/ethereum/EIPs/issues/2535 +status: Draft +type: Standards Track +category: ERC +created: 2020-02-22 +replaces: 1538 +--- + + + +## Simple Summary + + +A contract architecture that makes upgradeable contracts flexible, unlimited in size, and transparent. + +New terminology from the diamond industry. + +Improved design over [ERC1538](https://eips.ethereum.org/EIPS/eip-1538) using ABIEncoderV2 and function selectors. + + + +## Abstract + +A diamond is a contract that implements this standard. + +A diamond provides the following: + +1. A way to add, replace and remove multiple functions atomically (in the same transaction). +2. An event that shows what functions are added, replaced, removed and one or more messages describing changes. +3. A way to look at a diamond to see its functions. +4. Solves the 24KB maximum contract size limitation. Diamonds can be any size. +5. Enables zero, partial or full contract immutablity as desired, and when desired. +6. Designed for tooling and user-interface software. + +A diamond is a proxy contract that supports using multiple logic/delegate contracts at the same time. In this standard the term for logic/delegate contract is facet. A diamond can have many facets. Which facet is used depends on which function is called. Each facet supplies one or more functions. + +This standard supersedes [ERC1538: Transparent Contract Standard](https://eips.ethereum.org/EIPS/eip-1538). + +## Motivation + +A fundamental benefit of Ethereum contracts is that their code is immutable, thereby acquiring trust by trustlessness. People do not have to trust others if it is not possible for a contract to be changed. + +However, there are liabilities to contracts that can't be changed. + +### Bugs + +Bugs and security vulnerabilities are unwittingly written into immutable contracts that ruin them. Immutable contracts can't be fixed. + +### Improvements + +Immutable, trustless contracts cannot be improved, resulting in increasingly inferior contracts over time. Contracts rot. + +Contract standards evolve, new ones come out. People learn over time what people want and what is better and what should be built next. Contracts that can be improved advance with the times. + +### Immutability + +In some cases immutable, trustless contracts are the right fit. This is the case when a contract is only needed for a short time or it is known ahead of time that there will never be any reason to change or improve it. Diamonds [can be immutable](#immutable-diamond). + +### Middle Ground + +Through transparency diamonds can provide a middle ground between immutable trustless contracts that can't be improved and upgradeable contracts that can't be trusted. + +### Benefits & Use Cases +1. The ability to add, replace or remove multiple functions of a contract atomically (in the same transaction). +2. Any time one or more functions are added, replaced or removed, it is documented with an event. +3. Build trust over time by showing all changes made to a contract. +4. Unlimited contract size. +5. The ability to query information about functions currently supported by the contract. +6. One contract address that provides all needed functionality and never needs to be replaced by another contract address. +7. The ability for a contract to be upgradeable for a time, and then become immutable. +8. Add trustless guarantees to a contract with immutable functions. + +### New User-Interface Software & Libraries + +User-interface software can be written to show all functions and source code used by a diamond. + +Diamond events can be filtered from the Ethereum blockchain to show all changes to a diamond. + +Existing and new programming libraries and software can be used to interact with and use diamonds. + +### Why Make a Diamond? + +The flexibility of this standard makes a lot of designs possible. There are reasons to make diamonds that the author of this standard doesn't know about and reasons to make diamonds that have not been discovered yet. Here are some known reasons: + +1. You exceed the max size of a contract and you have related functionality that needs to access and use the same storage variables. Make a diamond. +2. Diamonds can be large but still modular because they are compartmented with facets. +3. Multiple small contracts calling each other increases complexity. A diamond handling its storage and functionality is simpler. +4. You need or want greater control over when and what functions exist. + +### Upgradeable Contracts vs. Centralized Private Database +Why have an upgradeable contract instead of a centralized, private, mutable database? + +1. Wide interaction and integration with the Ethereum ecosystem. +2. With open storage data and verified source code it is possible to show a provable history of trustworthiness. +3. With openness bad behavior can be spotted and reported when it happens. +4. Independent security and domain experts can review the change history of contracts and vouch for their history of trustworthiness. +5. It is possible for an upgradeable contract to become immutable and trustless. +6. An upgradeable contract can have parts of it that are not upgradeable and so are partially immutable and trustless. + +## Specification + +> **Note:** +The solidity `delegatecall` opcode enables a contract to execute a function from another contract, but it is executed as if the function was from the calling contract. Essentially `delegatecall` enables a contract to "borrow" another contract's function. Functions executed with `delegatecall` affect the storage variables of the calling contract, not the contract where the functions are defined. + +### Terms +1. A **diamond** is a contract that forwards function calls to facets. A diamond can have one or more facets. +2. The word **facet** comes from the diamond industry. It is a side, or flat surface of a diamond. A diamond can have many facets. In this standard a facet is a contract with one or more functions that executes functionality of a diamond. +3. A **loupe** is a magnifying glass that is used to look at diamonds. In this standard a loupe is a facet that provides functions to look at a diamond and its facets. +3. An **immutable function** is a function that is defined directly in a diamond and so cannot be replaced or removed. Or it is a function that is defined in a facet that cannot be replaced or removed. + +### General Summary + +A diamond delegates or forwards functions to facets using `delegatecode`. + +In the diamond industry diamonds are created and shaped by being cut, creating facets. In this standard diamonds are created, shaped or cut by adding, replacing or removing facets and their functions. + +A diamond has a `cut` function that adds, replaces and removes facets and functions. + +A `DiamondCuts` event is emitted that records all changes to a diamond. + +### Design Points + +A diamond implements the following design points: + +1. A diamond contains a fallback function, a constructor, and zero or more immutable functions that are defined directly within it. +2. The constructor of a diamond associates the `cut` function with a contract that implements the `Diamond` interface. The `cut` function can be an "immutable function" that is defined directly in the diamond or it can be defined in a facet. Other facets and functions can be added to a diamond in the constructor. +3. Functions are added, replaced and removed by calling the `cut` function. +4. The `cut` function associates functions with facets that implement those functions, and emits the `DiamondCuts` event that documents changes. +5. When a function is called on a diamond it executes immediately if it is an "immutable function" defined directly in the diamond. Otherwise the diamond's fallback function is executed. The fallback function finds the facet associated with the function and executes the function using `delegatecall`. If there is no facet for the function then execution reverts. +6. The `DiamondCut[] memory _diamondCuts` argument to the `cut` function specifies the functions to add, replace and remove. All changes specified in this argument must +be executed exactly or the transaction reverts. This argument is emitted in the `DiamondCuts` event to record all changes. The `cut` function reverts if functions to be added already exist. The `cut` function reverts if functions to replace do not exist. The `cut` function reverts if functions to remove do not exist. + +The diamond address is the address that users interact with. The diamond address does not change. Only facet addresses can change by using the `cut` function. + +### Diamond Interface +The `cut` function is marked `external` in the interface below but it can be a public function when implemented. + +```Solidity +pragma solidity ^0.6.3; +pragma experimental ABIEncoderV2; + +enum CutAction {Add, Replace, Remove} + +/// @notice Specifies an action on a set of functions. +/// `facet` is the address of the contract that contains the +/// code of the functions. +/// `action` specifies what to do: add, replace or remove functions. +/// `functionSelectors` is an array of function selectors to act on. +struct FacetCut { + address facet; + CutAction action; + bytes4[] functionSelectors; +} + +/// @notice Holds an array of actions on functions +/// and specifies a descriptive message for the changes. +/// `facetCuts` is an array of facets and their selectors +/// and actions to take on them: add, replace or remove. +/// `message` is a string message describing the changes. +struct DiamondCut { + FacetCut[] facetCuts; + string message; +} + +interface Diamond { + // @notice Adds/replaces/removes one or more functions, + // with descriptive messages of the changes. + // @param _diamondCuts Specifies what functions and facets + // to add/replace/remove and descriptive messages + // of the changes. This argument is emmitted in the + // DiamondCuts event as a record of what was done. + function cut(DiamondCut[] calldata _diamondCuts) external; + + // @notice Records all changes done in the cut function + // and messages explaining the changes. + event DiamondCuts(DiamondCut[] _diamondCuts); +} +``` +The `_diamondCuts` argument tells the `cut` function what to do and is passed to the `DiamondCuts` event to record what was done. + +With the `cut` function any number of functions can be added/replaced/removed in one transaction. And all changes in one transaction are recorded with one `DiamondCuts` event. + +The `_diamondCuts` argument can contain multiple descriptive messages explaining the changes. + +Behavior of Add/Replace/Remove: + +1. When adding functions the facet address in the facetCut struct is associated with the function selectors in `functionSelectors`. +2. When replacing functions the facet address in the facetCut struct is associated with the function selectors in `functionSelectors`. +3. When removing functions the facet address in the facetCut struct is ignored. By convention the facet address is address(0). Functions selectors in `functionSelectors` are removed from the diamond. + +### Diamond Example +The diamond example below is taken from the [reference implementation of the standard](https://github.com/mudgen/Diamond). + +The diamond adds the `cut` function and diamond loupe functions to itself. + +More specifically here is what it does: + +1. The constructor creates a new DiamondFacet contract. DiamondFacet is a contract that implements the Diamond interface and the DiamondLoup interface. The Diamond interface has the `cut` function used to add/replace/remove functions. The DiamondLoup interface, described later in this standard, is a set of functions for inspecting and showing what facets and functions a diamond has. +3. The constructor specifies two diamond cuts. Two cuts are used in this case because each cut gets its own descriptive message. The first cut adds the `cut` function. The second cut adds the diamond loupe functions. +4. The two cuts are stored in the `diamondCuts` array. +5. The `cut` function, with the `diamondCuts` array, is called using `delegatecall` on diamondFacet to execute the cuts. + +The end result is that when a new DiamondExample contract is created it will have the `cut` function and the diamond loupe functions. The `cut` function is used to add more functions and the loupe functions show what functions have been added. + + +```Solidity +pragma solidity ^0.6.3; +pragma experimental ABIEncoderV2; + +import "./Storage.sol"; +import "./DiamondFacet.sol"; + +enum CutAction {Add, Replace, Remove} + +struct FacetCut { + address facet; + CutAction action; + bytes4[] functionSelectors; +} + +struct DiamondCut { + FacetCut[] facetCuts; + string message; +} + +interface Diamond { + function cut(DiamondCut[] calldata _diamondCuts) external; + event DiamondCuts(DiamondCut[] _diamondCuts); +} + +contract DiamondExample is Storage { + + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + constructor() public { + $contractOwner = msg.sender; + emit OwnershipTransferred(address(0), msg.sender); + + // Create a DiamondFacet contract which implements the Diamond and + // DiamondLoupe interfaces + DiamondFacet diamondFacet = new DiamondFacet(); + + // Two cuts will be created and stored in this array + DiamondCut[] memory diamondCuts = new DiamondCut[](2); + + FacetCut[] memory facetCuts; + bytes4[] memory functionSelectors; + + // First Diamond Cut + // Adding cut function + functionSelectors = new bytes4[](1); + functionSelectors[0] = Diamond.cut.selector; + facetCuts = new FacetCut[](1); + facetCuts[0] = FacetCut({ + facet: address(diamondFacet), + action: CutAction.Add, + functionSelectors: functionSelectors + }); + diamondCuts[0] = DiamondCut({ + facetCuts: facetCuts, + message: "Adding diamond cut function." + }); + + // Second Diamond Cut + // Adding diamond loupe functions + functionSelectors = new bytes4[](6); + functionSelectors[0] = DiamondLoupe.totalFunctions.selector; + functionSelectors[1] = DiamondLoupe.functionSelectorByIndex.selector; + functionSelectors[2] = DiamondLoupe.facetFunctionSelectors.selector; + functionSelectors[3] = DiamondLoupe.facets.selector; + functionSelectors[4] = DiamondLoupe.facetAddress.selector; + functionSelectors[5] = DiamondLoupe.facetAddresses.selector; + facetCuts = new FacetCut[](1); + facetCuts[0] = FacetCut({ + facet: address(diamondFacet), + action: CutAction.Add, + functionSelectors: functionSelectors + }); + diamondCuts[1] = DiamondCut({ + facetCuts: facetCuts, + message: "Adding diamond loupe functions." + }); + + // execute cut function + bytes memory cutFunction = abi.encodeWithSelector(Diamond.cut.selector, diamondCuts); + (bool success,) = address(diamondFacet).delegatecall(cutFunction); + require(success, "Adding functions failed."); + } + + // Finds facet for function that is called and executes the + // function if it is found and returns any value. + fallback() external payable { + address facet = $facets[msg.sig]; + require(facet != address(0), "Function does not exist."); + assembly { + let ptr := mload(0x40) + calldatacopy(ptr, 0, calldatasize()) + let result := delegatecall(gas(), facet, ptr, calldatasize(), 0, 0) + let size := returndatasize() + returndatacopy(ptr, 0, size) + switch result + case 0 {revert(ptr, size)} + default {return (ptr, size)} + } + } + + receive() external payable { + } +} + +``` + + +### Diamond Loupe + +The function selectors used by a diamond can be stored in an array and queried to get what functions the diamond supports and what facets are used. + +A diamond loupe is a contract that implements this interface: +```Solidity +// A loupe is a small magnifying glass used to look at diamonds. +// These functions look at diamonds. +interface DiamondLoupe { + + /// @notice Gets the total number of functions the diamond has. + /// @return The number of functions the diamond has, + /// not including the fallback function. + function totalFunctions() external view returns(uint); + + /// @notice Gets information about a specific function + /// @dev Throws if `_index` >= `totalFunctions()` + /// @param _index The index position of a function selector that is stored in an array + /// @return The function selector and the facet address + function functionSelectorByIndex(uint _index) + external + view + returns( + bytes4 functionSelector, + address facet + ); + + /// @notice Gets all the function selectors supported by a specific facet + /// @param _facet The facet address + /// @return An array of function selectors + function facetFunctionSelectors(address _facet) + external + view + returns(bytes4[] memory); + + struct Facet { + address facet; + bytes4[] functionSelectors; + } + /// @notice Gets all facets and their selectors + /// @return An array containing each facet and each facet's selectors + function facets() external view returns(Facet[] memory); + + /// @notice Gets the facet that supports the given selector + /// @param _functionSelector The function selector + /// @return The facet address + function facetAddress(bytes4 _functionSelector) external view returns(address); + + /// @notice Get all the facet addresses used by the diamond + /// @return An array of all facet addresses + function facetAddresses() external view returns(address[] memory); +} +``` +See the [reference implementation](https://github.com/mudgen/Diamond) to see how this is implemented. + +These functions are designed for user-interface software. A user interface calls these functions to provide information about and visualize diamonds. + +The `facetFunctionsSelectors`, `facets`, `facetAddresses` functions are designed to be called by off-chain software and so are not gas efficient. + +## Rationale + +### bytes4[] Array of Function Selectors Instead of String of Function Signatures + +This standard is designed to make diamonds work well with user-interface software. Function selecters with the ABI of a contract provide enough information about functions to be useful for user-interface software. Function signatures by themselves do not provide information about their return values or if they are read-only or not. Function selectors are used because they are more gas efficient and contract ABIs need to be used anyway. + +ABIEncoderV2 is no longer experimental and some function signatures contain structs/tuples making parsing function signatures within contracts more difficult and/or costly. This is avoided by using function selectors. + +### Gas Considerations + +Delegating function calls does have some gas overhead. This is mitigated in two ways: +1. Facets can be small, reducing gas costs. Because it costs more gas to call a function in a contract with many functions than a contract with few functions. +2. Because diamonds do not have a max size limitation it is possible to add gas optimizing functions for use cases. For example someone could use a diamond to implement the ERC721 standard and implement batch transfer functions from the [ERC1412 standard](https://github.com/ethereum/EIPs/issues/1412) to reduce gas (and make batch transfers more convenient). + +### Storage + +The standard does not specify how data is stored or organized by a diamond. But here are some suggestions: + +**Inherited Storage** + +A diamond and all of its facets use the same storage layout space. So in the diamond source code and facet source code the same storage variables should be declared in the same order. + +Here is a way to implement inherited storage: + +1. All storage variables should be `internal`. +2. Create a storage contract that contains the storage variables that your diamond will use. +3. Make your diamond inherit the storage contract. +4. Make your facets inherit the storage contract. +5. If you want to add a new facet that adds new storage variables then create a new storage contract that inherits the old storage contract and adds the new storage variables. Use the new storage contract with the new facet. +6. Repeat step 5 for every new facet that adds new storage variables. + + +**Unstructured Storage** + +Assembly is used to store and read data at specific storage locations. An advantage to this approach is that previously used storage locations don't have to be defined or mentioned in a facet if they aren't used by it. + +**Eternal Storage** + +Data can be stored using a generic API based on the type of data. [See ERC930 for more information.](https://github.com/ethereum/EIPs/issues/930) + +### Immutable Diamond +It is possible to make a diamond immutable. This is done by calling the `cut` function to remove the `cut` function. With this gone it is no longer possible to add, replace or remove functions. + +### Versions of Functions + +Software or a user can verify what version of a function is called by getting the facet address of the function. This can be done by calling the `facetAddress` function from the DiamondLoop interface. This function takes a function selector as an argument and returns the facet address where it is implemented. + +### Sharing Functions Between Facets +In some cases it might be necessary to call a function defined in a different facet. +Here are some solutions to this: +1. Copy the function code in one facet to the other facet. +2. Put common functions in a contract that is inherited by multiple facets. +3. Use `delegatecall` to call functions defined in other facets. Here is an example of doing that: +```Solidity +bytes memory myFunction = abi.encodeWithSignature("myFunction(uint256)", 4); +(bool success, uint result) = address(this).delegatecall(myFunction); +require(success, "myFunction failed."); +``` + + +## Security Considerations + +### Ownership and Authentication + +>**Note:** The design and implementation of diamond ownership/authentication is **not** part of this standard. The examples given in this standard and in the reference implementation are just **examples** of how it could be done. + +It is possible to create many different authentication or ownership schemes with the diamond standard. Authentication schemes can be very simple or complex, fine grained or coarse. The diamond standard does not limit it in any way. For example ownership/authentication could be as simple as a single account address having the authority to add/replace/remove any functions. Or a decentralized autonomous organization could have the authority to only add/replace/remove certain functions. + +Consensus functionality could be implemented such as an approval function that multiple different people call to approve changes before they are executed with the `cut` function. These are just examples. + +The development of standards and implementations of ownership, control and authentication of diamonds is encouraged. + +### Function Selector Clash +A function selector clash occurs when a function is added to a contract that hashes to the same four-byte hash as an existing function. By following [design point 6](#design-points) of this standard a function selector clash is not possible. Any attempt to add a function that clashes with the selector of an existing function will revert. + + +### Transparency + +Diamonds emit an event every time one or more functions are added, replaced or removed. All source code can be verified. This enables people and software to monitor changes to a contract. If any bad acting function is added to a diamond then it can be seen. + +Security and domain experts can review the history of change of a diamond to detect any history of foul play. + +## Backwards Compatibility + +This standard makes a contract compatible with future standards and functionality because new functions can be added and existing functions can be replaced or removed. + + +## Implementation + +A reference implementation is given in the [Diamond repository](https://github.com/mudgen/Diamond). + +## Inspiration + +This standard was inspired by [ERC1538](https://eips.ethereum.org/EIPS/eip-1538) and ZeppelinOS's implementation of [Upgradeability with vtables](https://github.com/zeppelinos/labs/tree/master/upgradeability_with_vtable). + +This standard was also inspired by the design and implementation of the [Mokens contract](https://etherscan.io/address/0xc1eab49cf9d2e23e43bcf23b36b2be14fc2f8838#code). + + +## Copyright +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/assets/eip-2535/diamond.svg b/assets/eip-2535/diamond.svg new file mode 100644 index 0000000000000..a2880579335bc --- /dev/null +++ b/assets/eip-2535/diamond.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/eip-2535/magnifying-glass.svg b/assets/eip-2535/magnifying-glass.svg new file mode 100644 index 0000000000000..1d45764dc6022 --- /dev/null +++ b/assets/eip-2535/magnifying-glass.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +