EIP-2535: Diamonds, Multi-Facet Proxy
Author | Nick Mudge |
---|---|
Discussions-To | https://github.com/ethereum/EIPs/issues/2535 |
Status | Last Call |
Last Call Deadline | 2020-08-15 |
Type | Standards Track |
Category | ERC |
Created | 2020-02-22 |
Table of Contents
- Simple Summary
- Motivation
- What is a Diamond?
- How a Diamond Works
- Upgrades and Immutability
- Organizing State Variables in Diamonds
- Facets, State Variables and Diamond Storage
- AppStorage
- Diamonds Can Use Other Contract Storage Strategies
- Facets Can Share State and Functionality
- Facets are Reusable and Composable
- Diagrams
- Some Diamond Benefits
- Specification
- Implementation
- Rationale
- Security Considerations
- Backwards Compatibility
- Learning & References
- Inspiration & Development
- Copyright
Simple Summary
Standard for creating modular smart contract systems that can be extended after deployment.
Enables people to write smart contracts with virtually no size limit.
Diamonds can be upgraded without having to redeploy existing functionality. Parts of a diamond can be added/replaced/removed while leaving other parts alone.
Standardizes contract interfaces and implementation details of diamonds, enabling software integration and interoperability.
A diamond is a contract that implements the Specification in this standard.
Diamond analogy helps conceptualize development.
Motivation
There are a number of different reasons to use diamonds. Here are some of them:
- A single address for unlimited contract functionality. Using a single address for contract functionality makes deployment, testing and integration with other smart contracts, software and user interfaces easier.
- Your contract exceeds the 24KB maximum contract size. You may have related functionality that it makes sense to keep in a single contract, or at a single contract address. A diamond does not have a max contract size.
- A diamond provides a way to organize contract code and data. You may want to build a contract system with a lot of functionality. A diamond provides a systematic way to isolate different functionality and connect them together and share data between them as needed in a gas-efficient way.
- A diamond provides a way to upgrade functionality. Upgradeable diamonds can be upgraded to add/replace/remove functionality. Because diamonds have no max contract size, there is no limit to the amount of functionality that can be added to diamonds over time. Diamonds can be upgradeable or immutable. It is also possible to make an upgradeable diamond and then at a later time remove its upgrade capability.
Diamonds Support Transparency
To find out what functions a regular smart contract has it is only necessary to look at its verified source code.
The verified source code of a diamond does not include what functions it has so a different mechanism is needed.
A diamond has four standard functions called the loupe functions that are used to show what functions a diamond has.
The loupe functions can be used for many things including:
- To show all functions used by a diamond.
- To query services like Etherscan or files to retrieve and show all source code used by a diamond.
- To query services like Etherscan or files to retrieve ABI information for a diamond.
- To test or verify that a transaction that adds/replaces/removes functions on a diamond succeeded.
- To find out what functions a diamond has before calling functions on it.
- To be used by tools and programming libraries to deploy and upgrade diamonds.
- To be used by user interfaces to show information about diamonds.
- To be used by user interfaces to enable users to call functions on diamonds.
Diamonds support another form of transparency which is a historical record of all upgrades on a diamond. This is done with the DiamondCut event which is used to record all functions that are added, replaced or removed on a diamond.
This standard is an improvement of EIP-1538 Transparent Contract Standard. The same motivations of that standard apply to this standard.
See the Learning & References section for additional information and uses of diamonds.
What is a Diamond?
A diamond is a contract with external functions that are supplied by contracts called facets.
Facets are separate, independent contracts that can share internal functions, libraries and state variables.
How a Diamond Works
A diamond stores within it a mapping of function selector to facet address, for example selectorToFacet
.
When an external function is called on a diamond its fallback function is executed. The fallback function finds in the selectorToFacet
mapping which facet has the function that has been called and then executes that function from the facet using delegatecall
.
A diamond’s fallback function and delegatecall
enable a diamond to execute a facet’s external function as its own external function. The msg.sender
and msg.value
values do not change and only the diamond’s contract storage is read and written to.
Here is a simple example of a diamond’s fallback function:
// Find facet for function that is called and execute the
// function if a facet is found and return any value.
fallback() external payable {
// get facet from function selector
address facet = selectorTofacet[msg.sig];
require(facet != address(0));
// Execute external function from facet using delegatecall and return any value.
assembly {
// copy function selector and any arguments
calldatacopy(0, 0, calldatasize())
// execute function call using the facet
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
// get any return value
returndatacopy(0, 0, returndatasize())
// return any return value or error back to the caller
switch result
case 0 {revert(0, returndatasize())}
default {return (0, returndatasize())}
}
}
A diamond can use a diamondCut
function to add/replace/remove any number of functions from any number of facets in a single transaction. diamondCut
updates the mapping of function selector to facet address. Other such functions can be used.
An event is emitted any time external functions are added, replaced or removed to report what changed.
A diamond has four standard external functions that can be called to show what facets and functions it currently has.
Upgrades and Immutability
A diamond can be immutable from inception by not adding any external functions that can add/replace/remove functions. There are number of reasons to do this.
A diamond that is mutable can be made immutable by removing such functions.
A diamond can be upgraded if it has a diamondCut external function or other function(s) that can add/replace/remove functions.
Organizing State Variables in Diamonds
A state variable or storage layout organizational pattern is needed because Solidity’s builtin storage layout system doesn’t support proxy contracts or diamonds.
Described below are Diamond Storage and AppStorage which are two state variable organizational patterns that have been successfully used in diamonds.
Another successful pattern has been to write a storage contract that contains all state variables and is inherited by all facets.
Facets, State Variables and Diamond Storage
A facet defines external functions and can define or use internal functions, libraries, and state variables.
A facet can declare state variables in structs. Each struct is given a specific position in contract storage. This technique is called Diamond Storage.
Here is a simple example that shows Diamond Storage and its use in a facet:
// A contract that implements Diamond Storage.
library LibA {
// This struct contains state variables we care about.
struct DiamondStorage {
address owner;
bytes32 dataA;
}
// Returns the struct from a specified position in contract storage
// ds is short for DiamondStorage
function diamondStorage() internal pure returns(DiamondStorage storage ds) {
// Specifies a random position in contract storage
// This can be done with a keccak256 hash of a unique string as is
// done here or other schemes can be used such as this:
// bytes32 storagePosition = keccak256(abi.encodePacked(ERC1155.interfaceId, ERC1155.name, address(this)));
bytes32 storagePosition = keccak256("diamond.storage.LibA");
// Set the position of our struct in contract storage
assembly {ds.slot := storagePosition}
}
}
// Our facet uses the Diamond Storage defined above.
contract FacetA {
function setDataA(bytes32 _dataA) external {
LibA.DiamondStorage storage ds = LibA.diamondStorage();
require(ds.owner == msg.sender, "Must be owner.");
ds.dataA = _dataA;
}
function getDataA() external view returns (bytes32) {
return LibA.diamondStorage().dataA;
}
}
Any number of structs, each with a different storage position, can be used by a facet.
By using Diamond Storage facets can declare their own state variables that do not conflict with the storage locations of state variables declared in other facets.
By using Diamond Storage facets can be developed independently, without connection or concern for other facets.
AppStorage
A specialized version of Diamond Storage is AppStorage. This pattern is used to more conveniently and easily share state variables between facets.
The AppStorage pattern is implemented by defining a struct called AppStorage that contains the state variables of your application. It can contain any number of state variables of any type, including arrays and mappings, and more can be added in upgrades.
The AppStorage struct is defined or imported and declared as the first and only state variable directly (or via inheritance) in any facet that uses it. This means that AppStorage is always located at position 0 in contract storage.
The AppStorage state variable is often named s
to provide easy access to it and to distinguish state variables from function arguments, function names, and local variables.
Here is a simple example of a contract that uses AppStorage:
import "./AppStorage.sol"
contract StakingFacet {
AppStorage internal s;
function myFacetFunction(uint256 _nextVar) external {
s.total = s.firstVar + _nextVar;
}
The above example accesses the s.firstVar
state variable and stores a computation in the s.total
state variable.
Read this article to learn more about AppStorage: AppStorage Pattern for State Variables in Solidity.
Diamonds Can Use Other Contract Storage Strategies
Diamonds and facets don’t have to use Diamond Storage or AppStorage. They can use or mix with other contract storage strategies such as contract inheritance.
Diamonds only need to implement the Specification section of this standard.
Facets Can Share State and Functionality
Facets can share state variables by using the same structs at the same storage positions.
Facets can share internal functions and libraries by inheriting the same contracts or using the same libraries.
In these ways facets are separate, independent units but can share state and functionality.
Facets are Reusable and Composable
A deployed facet can be used by any number of diamonds.
Different combinations of facets can be used with different diamonds.
It is possible to create and deploy a set of facets that are reused by different diamonds over time.
The ability to use the same deployed facets for many diamonds reduces deployment costs.
A limitation is that two external functions with the same function signature can’t be added to a diamond at the same time because a diamond, or any contract, cannot have two external functions with the same function signature.
Diagrams
Diamond Structure
This diagram shows the structure of a diamond:
Diamond Storage
The diagram below shows facets with their own data and data shared between them.
Notice that all data is stored in the diamond. But different facets have different access to data.
In this diagram
- Only FacetA can access DataA
- Only FacetB can access DataB
- Only the diamond’s own code can access DataD.
- FacetA and FacetB share access to DataAB.
- The diamond’s own code, FacetA and FacetB share access to DataABD.
Deployed Facets Can Be Reused
A deployed facet can be used by any number of diamonds.
The diagram below shows two diamonds using the same two facets.
- FacetA is used by Diamond1
- FacetA is used by Diamond2
- FacetB is used by Diamond1
- FacetB is used by Diamond2
Some Diamond Benefits
- A stable contract address that provides needed functionality.
- A single address with the functionality of multiple contracts (facets) that are independent from each other but can share internal functions, libraries and state variables.
- A way to add, replace and remove multiple external functions atomically (in the same transaction).
- Fine-grained upgrades, so you can change just the parts of a diamond that need to be changed.
- Have greater control over when and what functions exist.
- Decentralized Autonomous Organizations (DAOs) and other governance systems can be used to upgrade diamonds.
- An event that shows what functions are added, replaced and removed.
- The ability to show all changes made to a diamond.
- Increase trust over time by showing all changes made to a diamond.
- A way to look at a diamond to see its current facets and functions.
- Have an immutable, trustless diamond.
- Solves the 24KB maximum contract size limitation. Diamonds can be any size.
- Separate functionality can be implemented in separate facets and used together in a diamond.
- Larger contracts have to reduce their size by removing error messages and other things. You can keep your error messages and the full functionality that you need by implementing a diamond.
- Enables zero, partial or full diamond immutability as desired, and when desired.
- The ability to develop and improve an application over time with an upgradeable diamond and then make it immutable and trustless if desired.
- Develop incrementally and let your diamond grow with your application.
- Upgrade diamonds to fix bugs, add functionality and implement new standards.
- Organize your code with a diamond and facets.
- Diamonds can be large (have many functions) but still be modular because they are compartmented with facets.
- Contract architectures that call multiple contracts in a single transaction can save gas by condensing those contracts into a single diamond and accessing state variables directly.
- Save gas by creating external functions for specific use cases, such as bulk transfers.
- Diamonds are designed for tooling and user-interface software.
New User-Interface Software & Libraries
User-interface software can be written to show all documentation, 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 deploy, show, upgrade and use diamonds.
Upgradeable Diamond vs. Centralized Private Database
Why have an upgradeable diamond instead of a centralized, private, mutable database?
- Decentralized Autonomous Organizations (DAOs) and other governance systems can be used to upgrade diamonds.
- Wide interaction and integration with the Ethereum ecosystem.
- With open storage data and verified source code it is possible to show a provable history of trustworthiness.
- With openness bad behavior can be spotted and reported when it happens.
- Independent security and domain experts can review the change history of contracts and vouch for their history of trustworthiness.
- It is possible for an upgradeable diamond to become immutable and trustless.
Different Kinds of Diamonds
Many designs of diamonds are possible. Here are a few kinds of diamonds and their uses.
Upgradeable Diamond
An upgradeable diamond has the diamondCut
function and/or possibly other functions to add/replace/remove functions. It is useful for iterative development or improving an application over time.
Finished Diamond
A finished diamond was an upgradeable diamond and had a number of upgrades. Then its diamondCut
function and/or other upgrade functions were removed and upgrades are no longer possible. It is no longer possible to add/replace/remove functions. It has become an immutable diamond.
Single Cut Diamond
A single cut diamond adds all functions to itself in its constructor function, but it does not add the diamondCut
function or any other function that can add/replace/remove functions. This means that a single cut diamond is fully created in its constructor and once created can never be upgraded. It has the same immutability and trustless guarantees as a regular vanilla contract. Why would someone do this? There may be a number of reasons. The two use cases below are good reasons.
- Your contract hits the max contract size limit. Make it into a single cut diamond. You still break your big contract into smaller facets, modularizing your code.
- You start with an upgradeable diamond in your development and testing and upgrade it to your heart’s delight. Reap the advantages of easy upgrading and a stable address as you work out new features, bugs and kinks. Release the upgradeable diamond on a test network with your application for beta testing and upgrade it when needed. This is iterative development. When it is solid then deploy it as a single cut diamond on the main network.
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. Essentiallydelegatecall
enables a contract to “borrow” another contract’s function. Functions executed withdelegatecall
affect the contract storage of the calling contract, not the contract where the functions are defined.
Note: This specification specifies what needs to be implemented for a contract to be a diamond.
Terms
- A diamond is a contract that uses functions from its facets to execute function calls. A diamond can have one or more facets.
- 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.
- 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.
- 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 because all upgrade functions have been removed from a diamond.
General Summary
A diamond calls functions from its facets using delegatecall
.
In the diamond industry diamonds are created and shaped by being cut, creating facets. In this standard diamonds are cut by adding, replacing or removing facets and their functions.
The diamondCut Function
The standard diamondCut
function specified below can be used to add/replace/remove any number of functions from/to a diamond in a single transaction.
The standard diamondCut
function below is specified for the purpose of interoperability. Diamond tools, software and user-interfaces should expect and use the standard diamondCut
function. Diamonds that might work with diamond specific tooling to add/replace/remove functions should implement the standard diamondCut
function.
The diamondCut
function is optional. It does not have to be implemented in a diamond. For example an immutable diamond wouldn’t have this function.
You can implement your own custom functions that add or replace or remove functions. You can also implement your own non-standard versions of diamondCut
that have different parameters.
If you want to create your own custom function(s) for adding/replacing/removing functions you might also want to implement the standard diamondCut
function for interoperability with tools.
In ALL cases any function or code that adds or replaces or removes one or more functions MUST emit the standard DiamondCut event specified below.
The DiamondCut
event records all changes to a diamond.
Diamond Interface
interface IDiamondCut {
enum FacetCutAction {Add, Replace, Remove}
// Add=0, Replace=1, Remove=2
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
/// @notice Add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}
Adding/Replacing/Removing Functions
A diamond contains within it a mapping of function selectors to facet addresses. Functions are added/replaced/removed by modifying this mapping.
The _diamondCut
argument is an array of FacetCut structs.
Each FacetCut struct contains a facet address and array of function selectors that are updated in a diamond.
To add new functions create a FacetCut struct with facetAddress
set to the facet that has the new functions and functionSelectors
set with the function selectors to add. Set the action
enum to Add
.
To replace functions create a FacetCut struct with facetAddress
set to the facet that has the replacement functions and functionSelectors
set with the function selectors to replace. Set the action
enum to Replace
.
To remove functions create a FacetCut struct with facetAddress
set to address(0)
and functionSelectors
set with the function selectors to remove. Set the action
enum to Remove
.
It is the design of the diamondCut
function that cuts are explicit and intentional.
The diamondCut
function reverts when attempting to add a function that already exists.
The diamondCut
function reverts when attempting to replace a function with a facet it is already using.
The diamondCut
function reverts when attempting to remove a function that already does not exist.
Executing _calldata
After adding/replacing/removing functions the _calldata
argument is executed with delegatecall
on _init
. This execution is done to initialize data or setup or remove anything needed or no longer needed after adding, replacing and/or removing functions.
If the _init
value is address(0)
then _calldata
execution is skipped. In this case _calldata
can contain 0 bytes or custom information.
If the _init
value is not address(0)
then _calldata
must contain more than 0 bytes or the transaction reverts.
DiamondCut Event
The _diamondCut
, _init
, and _calldata
arguments are passed directly to the DiamondCut
event.
Any time one or more functions are added, replaced or removed the DiamondCut
event MUST be emitted to record changes.
Diamond Loupe
A loupe is a small magnifying glass used to look at diamonds. These functions look at diamonds.
The function selectors used by a diamond are queried to get what functions the diamond has and what facets are used.
A diamond loupe is a facet that implements this interface:
// A loupe is a small magnifying glass used to look at diamonds.
// These functions look at diamonds
interface IDiamondLoupe {
/// These functions are expected to be called frequently
/// by tools.
struct Facet {
address facetAddress;
bytes4[] functionSelectors;
}
/// @notice Gets all facet addresses and their four byte function selectors.
/// @return facets_ Facet
function facets() external view returns (Facet[] memory facets_);
/// @notice Gets all the function selectors supported by a specific facet.
/// @param _facet The facet address.
/// @return facetFunctionSelectors_
function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);
/// @notice Get all the facet addresses used by a diamond.
/// @return facetAddresses_
function facetAddresses() external view returns (address[] memory facetAddresses_);
/// @notice Gets the facet that supports the given selector.
/// @dev If facet is not found return address(0).
/// @param _functionSelector The function selector.
/// @return facetAddress_ The facet address.
function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
}
See a reference implementation to see how this can be implemented.
The loupe functions can be used in user-interface software. A user interface calls these functions to provide information about and visualize diamonds.
The loupe functions can be used in deployment functionality, upgrade functionality, testing and other software.
Some loupe implementations are not gas efficient and should not be called in on-chain transactions. Some loupe implementations may be gas efficient and can be called in on-chain transactions. Read the documentation of the loupe implementation you use.
Implementation Points
A diamond implements the following implementation points:
- A diamond contains a fallback function and zero or more immutable functions that are defined within it.
- A diamond associates function selectors with facets.
- 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 optionally a default function may be executed. If there is no facet for the function and no default function and no other mechanism to handle it then execution reverts. - Each time functions are added, replaced or removed a
DiamondCut
event is emitted to record it. - A diamond implements the DiamondLoupe interface.
- All immutable functions must be emitted in the
DiamondCut
event as new functions added. And the loupe functions must return information about immutable functions if they exist. The facet address for an immutable function is the diamond’s address. - Optionally a diamond implements ERC165. If a diamond has the
diamondCut
function then the interface ID used for it isIDiamondCut.diamondCut.selector
. The interface ID used for the diamond loupe interface isIDiamondLoupe.facets.selector ^ IDiamondLoupe.facetFunctionSelectors.selector ^ IDiamondLoupe.facetAddresses.selector ^ IDiamondLoupe.facetAddress.selector
.
The diamond address is the address that users interact with. The diamond address does not change. Only facet addresses can change by using the diamondCut
function, or other function.
Implementation
Reference diamond implementations exist in the Diamond repository.
Rationale
Using Function Selectors
User interface software can be used to retrieve function selectors and face addresses from a diamond in order show what functions a diamond has.
This standard is designed to make diamonds work well with user-interface software. Function selectors with the ABI of a contract provide enough information about functions to be useful for user-interface software.
Gas Considerations
Delegating function calls does have some gas overhead. This is mitigated in several ways:
- 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 to reduce gas (and make batch transfers more convenient).
- Some contract architectures require calling many contracts in one transaction. Gas savings can be realized by condensing those contracts into a single diamond and accessing contract storage directly.
- 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.
- The Solidity optimizer can be set to a high setting causing more bytecode to be generated but the facets will use less gas when executed.
Diamond Storage
Since Solidity 0.6.4 it is possible to create pointers to structs in arbitrary places in contract storage. This enables diamonds and their facets to create their own storage layouts that are separate from each other and do not conflict with each other, but can still be shared between them. See this blog post for more information: New Storage Layout For Proxy Contracts and Diamonds. The reference implementations for EIP-2535 uses Diamond Storage.
Diamond Storage is not the same thing as unstructured storage. Unstructured storage reads and writes specific values like unsigned integers and addresses at specified locations in contract storage. Diamond Storage uses structs at specified locations in contract storage for reading and writing. Structs can hold any number of state variables of any type.
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 DiamondLoupe 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:
- Copy internal function code in one facet to the other facet.
- Put common internal functions in a contract that is inherited by multiple facets.
- Put common internal functions in a Solidity library and use the library in facets.
- A type safe way to call an external function defined in another facet is to do this: MyOtherFacet(address(this)).myFunction(arg1, arg2)
- A more gas-efficient way to call an external function defined in another facet is to use
delegatecall
. Here is an example of doing that:DiamondStorage storage ds = diamondStorage(); bytes4 functionSelector = bytes4(keccak256("myFunction(uint256)")); // get facet address of function address facet = ds.selectorToFacet[functionSelector]; bytes memory myFunctionCall = abi.encodeWithSelector(functionSelector, 4); (bool success, uint result) = address(facet).delegatecall(myFunctionCall); require(success, "myFunction failed");
- Instead of calling an external function defined in another facet you can instead create an internal function version of the external function. Add the internal version of the function to the facet that needs to use it.
Default Function
Solidity provides the fallback
function so that specific functionality can be executed when a function is called on a contract that does not exist in the contract. This same behavior can optionally be implemented in a diamond by implementing and using a default function, which is a function that is executed when a function is called on a diamond that does not exist in the diamond.
A default function can be implemented a number of ways and this standard does not specify how it must be implemented.
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 EIP-2535. Authentication schemes can be very simple or complex, fine grained or coarse. EIP-2535 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 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 diamondCut
function. These are just examples.
The development of standards and implementations of ownership, control and authentication of diamonds is encouraged.
Security of Diamond Storage
If a person can add/replace functions then that person can alter storage willy-nilly. This is very powerful and very dangerous. However the capability can be used while eliminating or reducing the danger. The danger is eliminated or reduced by limiting who can add/replace/remove functions, limiting when functions can be added/replaced/removed and by transparency.
Who Here are some ways who can be limited:
- Only allow a trusted individual or organization to make diamond upgrades.
- Only allow a distributed autonomous organization to make diamond upgrades.
- Only allow multi-signature upgrades.
- Only allow the end user who owns his own diamond to make upgrades. This enables users to opt-in to upgrades.
- Don’t allow anybody to make upgrades by making a single cut diamond.
When Here are some ways when can be limited:
- Only allow upgrades during development and testing. Make a single cut diamond for main network release.
- Use an upgradeable diamond until it is certain that no new features are needed and then make it a finished diamond by removing the ability to add/replace/remove functions.
- Program into the
diamondCut
function certain periods of time that the diamond can be upgraded. For example thediamondCut
function could be programmed so that a diamond could only be upgraded during a specific five hour period each year. Attention and transparency could be applied to that five hour period to ensure upgrades are done right.
Transparency Transparency provides certainty and proof that upgrades are done correctly and honestly.
- Publish and make available verified source code used by diamonds and facets.
- Provide documentation for diamonds, facets, upgrade plans, and results of upgrades.
- Provide tools and/or user interfaces that make your diamonds more visible and understandable.
Function Selector Clash
A function selector clash occurs when two different function signatures hash to the same four-byte hash. This has the unintended consequence of replacing an existing function in a diamond when the intention was to add a new function. This scenario is not possible with the diamondCut
function because it prevents adding function selectors that already exist.
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 upgradeable diamonds compatible with future standards and functionality because new functions can be added and existing functions can be replaced or removed.
Learning & References
Diamond Articles
Introduction to EIP-2535 Diamonds
Smart Contract Security Audits for EIP-2535 Diamonds Implementations
Why Ethereum Diamonds Need A Standard
Ethereum’s Maximum Contract Size Limit is Solved with EIP-2535 Diamonds
Why Ethereum Diamonds Need A Standard
EIP-2535 Diamonds: A new paradigm for upgradeability
Upgradeable smart contracts using EIP-2535 Diamonds
Ethereum Diamonds for On-Chain Decentralized Governance
How to Share Functions Between Facets of a Diamond
Diamond Storage Articles
AppStorage Pattern for State Variables in Solidity
New Storage Layout For Proxy Contracts and Diamonds
Solidity Libraries Can’t Have State Variables – Oh Yes They Can!
Smart Contracts Sharing Common Data
Sharing Common Data Using Libraries
Diamond Tools
Louper A user interface for all diamonds.
Implementations
Diamond reference implementations
Help
Inspiration & Development
EIP-2535 is an improved design over EIP-1538 using ABIEncoderV2 and function selectors.
EIP-2535 replaces EIP-1538.
This standard was inspired by EIP-1538 and ZeppelinOS’s implementation of Upgradeability with vtables.
This standard was also inspired by the design and implementation of the Mokens contract.
Copyright
Copyright and related rights waived via CC0.
Citation
Please cite this document as:
Nick Mudge, "EIP-2535: Diamonds, Multi-Facet Proxy [DRAFT]," Ethereum Improvement Proposals, no. 2535, February 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2535.