This proposal standardizes diamonds, which are modular smart contract systems that can be upgraded/extended after deployment, and have virtually no size limit. More technically, 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.
There are a number of different reasons to use diamonds. Here are some of them:
This standard is an improvement of EIP-1538. The same motivations of that standard apply to this standard.
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
Why have an upgradeable diamond instead of a centralized, private, mutable database?
delegatecall
s into its facets to execute function calls. A diamond is stateful. Data is stored in the contract storage of a diamond.The term contract is used loosely to mean a smart contract or deployed Solidity library.
When this EIP uses function without specifying internal or external, it means external function.
In this EIP the information that applies to external functions also applies to public functions.
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 functions from facets.
Because of the nature of diamonds, a diamond can implement an interface in one of two ways: directly (contract Contract is Interface
), or by adding functions to it from one or more facets. For the purposes of this proposal, when a diamond is said to implement an interface, either method of implementation is permitted.
When an external function is called on a diamond its fallback function is executed. The fallback function determines which facet to call based on the first four bytes of the call data (known as the function selector) and executes that function from the facet using delegatecall
.
A diamond's fallback function and delegatecall
enable a diamond to execute a facet's function as if it was implemented by the diamond itself. The msg.sender
and msg.value
values do not change and only the diamond's storage is read and written to.
Here is an illustrative example of how a diamond's fallback function might be implemented:
// 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())}
}
}
This diagram shows the structure of a diamond:
A state variable or storage layout organizational pattern is needed because Solidity's builtin storage layout system doesn't support proxy contracts or diamonds. The particular layout of storage is not defined in this EIP, but may be defined by later proposals. Examples of storage layout patterns that work with diamonds are Diamond Storage and AppStorage.
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.
The diagram below shows facets with their own data and data shared between them.
Notice that all data is stored in the diamond's storage, but different facets have different access to data.
In this diagram
FacetA
can access DataA
FacetB
can access DataB
DataD
.FacetA
and FacetB
share access to DataAB
.FacetA
and FacetB
share access to DataABD
.Smart contracts or deployed Solidity libraries can be facets of diamonds.
Only Solidity libraries that have one or more external functions can be deployed to a blockchain and be a facet.
Solidity libraries that contain internal functions only cannot be deployed and cannot be a facet. Internal functions from Solidity libraries are included in the bytecode of facets and contracts that use them. Solidity libraries with internal functions only are useful for sharing internal functions between facets.
Solidity library facets have a few properties that match their use as facets: * They cannot be deleted. * They are stateless. They do not have contract storage. * Their syntax prevents declaring state variables outside Diamond Storage.
IDiamond
InterfaceAll diamonds must implement the IDiamond
interface.
During the deployment of a diamond any immutable functions and any external functions added to the diamond must be emitted in the DiamondCut
event.
A DiamondCut
event must be emitted any time external functions are added, replaced, or removed. This applies to all upgrades, all functions changes, at any time, whether through diamondCut
or not.
interface IDiamond {
enum FacetCutAction {Add, Replace, Remove}
// Add=0, Replace=1, Remove=2
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}
The DiamondCut
event records all function changes to a diamond.
IDiamondCut
InterfaceA diamond contains within it a mapping of function selectors to facet addresses. Functions are added/replaced/removed by modifying this mapping.
Diamonds should implement the IDiamondCut
interface if after their deployment they allow modifications to their function selector mapping.
The diamondCut
function updates any number of functions from any number of facets in a single transaction. Executing all changes within a single transaction prevents data corruption which could occur in upgrades done over multiple transactions.
diamondCut
is specified for the purpose of interoperability. Diamond tools, software and user-interfaces should expect and use the standard diamondCut
function.
interface IDiamondCut is IDiamond {
/// @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;
}
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.
For each FacetCut
struct:
action
is Add
, update the function selector mapping for each functionSelectors
item to the facetAddress
. If any of the functionSelectors
had a mapped facet, revert instead.action
is Replace
, update the function selector mapping for each functionSelectors
item to the facetAddress
. If any of the functionSelectors
had a value equal to facetAddress
or the selector was unset, revert instead.action
is Remove
, remove the function selector mapping for each functionSelectors
item. If any of the functionSelectors
were previously unset, revert instead.Any attempt to replace or remove an immutable function must revert.
Being intentional and explicit about adding/replacing/removing functions helps catch and prevent upgrade mistakes.
_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.
A loupe is a small magnifying glass used to look at diamonds.
Diamonds must support inspecting facets and functions by implementing the IDiamondLoupe
interface.
IDiamondLoupe
Interface// A loupe is a small magnifying glass used to look at diamonds.
// These functions look at diamonds
interface IDiamondLoupe {
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.
A diamond must implement the following:
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.DiamondCut
event is emitted to record it.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. Any attempt to delete or replace an immutable function must revert.A diamond may implement the following:
supportsInterface
. If a diamond has the diamondCut
function then the interface ID used for it is IDiamondCut.diamondCut.selector
. The interface ID used for the diamond loupe interface is IDiamondLoupe.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.
User interface software can be used to retrieve function selectors and facet 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.
Delegating function calls does have some gas overhead. This is mitigated in several ways:
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 IDiamondLoupe
interface. This function takes a function selector as an argument and returns the facet address where it is implemented.
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.
DiamondCut
EventTo find out what functions a regular 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: 1. To show all functions used by a diamond. 1. To query services like Etherscan or files to retrieve and show all source code used by a diamond. 1. To query services like Etherscan or files to retrieve ABI information for a diamond. 1. To test or verify that a transaction that adds/replaces/removes functions on a diamond succeeded. 1. To find out what functions a diamond has before calling functions on it. 1. To be used by tools and programming libraries to deploy and upgrade diamonds. 1. To be used by user interfaces to show information about diamonds. 1. 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.
In some cases it might be necessary to call a function defined in a different facet. Here are ways to do this:
MyOtherFacet(address(this)).myFunction(arg1, arg2)
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, bytes memory result) = address(facet).delegatecall(myFunctionCall);
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.
It is possible to implement facets in a way that makes them usable/composable/compatible with other facets. It is also possible to implement facets in a way that makes them not usable/composable/compatible with other facets.
A function signature is the name of a function and its parameter types. Example function signature: myfunction(uint256)
. A limitation is that two external functions with the same function signature can’t be added to the same diamond at the same time because a diamond, or any contract, cannot have two external functions with the same function signature.
All the functions of a facet do not have to be added to a diamond. Some functions in a facet can be added to a diamond while other functions in the facet are not added to the diamond.
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.
All the Solidity code for a complete reference implementation has been put in a single file here: Diamond.sol
The same reference implementation has been organized into multiple files and directories and also includes a deployment script and tests. Download it as a zip file: EIP2535-Diamonds-Reference-Implementation.zip
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 this proposal. Authentication schemes can be very simple or complex, fine grained or coarse. This proposal 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.
diamondCut
The diamondCut
function allows arbitrary execution with access to the diamond's storage (through delegatecall
). Access to this function must be restricted carefully.
Use of selfdestruct
in a facet is heavily discouraged. Misuse of it can delete a diamond or a facet.
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 a properly implemented diamondCut
function because it prevents adding function selectors that already exist.
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.
Copyright and related rights waived via CC0.