This is an implementation for EIP-2535 Diamond Standard. To learn about other implementations go here: https://github.com/mudgen/diamond
The standard loupe functions have been gas-optimized in this implementation and can be called in on-chain transactions. However keep in mind that a diamond can have any number of functions and facets so it is still possible to get out-of-gas errors when calling loupe functions. Except for the facetAddress
loupe function which has a fixed gas cost.
Note: The loupe functions in DiamondLoupeFacet.sol MUST be added to a diamond and are required by the EIP-2535 Diamonds standard.
- Clone this repo:
git clone git@github.com:xxxijustwei/erc2535-contract.git
- Install NPM packages:
cd erc2535-contract
yarn
npx hardhat run scripts/deploy.js
- DiamondCutFacet is deployed.
- The diamond is deployed, passing as arguments to the diamond constructor the owner address of the diamond and the DiamondCutFacet address. DiamondCutFacet has the
diamondCut
external function which is used to upgrade the diamond to add more functions. - The
DiamondInit
contract is deployed. This contains aninit
function which is called on the first diamond upgrade to initialize state of some state variables. Information on how thediamondCut
function works is here: https://eips.ethereum.org/EIPS/eip-2535#diamond-interface - Facets are deployed.
- The diamond is upgraded. The
diamondCut
function is used to add functions from facets to the diamond. In addition thediamondCut
function calls theinit
function from theDiamondInit
contract usingdelegatecall
to initialize state variables.
How a diamond is deployed is not part of the EIP-2535 Diamonds standard. This implementation shows a usable example.
npx hardhat test
Check the scripts/deploy.ts
and or the test/diamond.test.ts
file for examples of upgrades.
Note that upgrade functionality is optional. It is possible to deploy a diamond that can't be upgraded, which is a 'Single Cut Diamond'. It is also possible to deploy an upgradeable diamond and at a later date remove its diamondCut
function so it can't be upgraded any more.
Note that any number of functions from any number of facets can be added/replaced/removed on a diamond in a single transaction. In addition an initialization function can be executed in the same transaction as an upgrade to initialize any state variables required for an upgrade. This 'everything done in a single transaction' capability ensures a diamond maintains a correct and consistent state during upgrades.
The contracts/Diamond.sol
file shows an example of implementing a diamond.
The contracts/facets/DiamondCutFacet.sol
file shows how to implement the diamondCut
external function.
The contracts/facets/DiamondLoupeFacet.sol
file shows how to implement the four standard loupe functions.
The contracts/libraries/LibDiamond.sol
file shows how to implement Diamond Storage and a diamondCut
internal function.
The scripts/deploy.js
file shows how to deploy a diamond.
The test/diamondTest.js
file gives tests for the diamondCut
function and the Diamond Loupe functions.
-
Reading and understand EIP-2535 Diamonds. If something is unclear let me know!
-
Use a diamond reference implementation. You are at the right place because this is the README for a diamond reference implementation.
This diamond implementation is boilerplate code that makes a diamond compliant with EIP-2535 Diamonds.
Specifically you can copy and use the DiamondCutFacet.sol and DiamondLoupeFacet.sol contracts. They implement the diamondCut
function and the loupe functions.
The Diamond.sol contract could be used as is, or it could be used as a starting point and customized. This contract is the diamond. Its deployment creates a diamond. It's address is a stable diamond address that does not change.
The LibDiamond.sol library could be used as is. It shows how to implement Diamond Storage. This contract includes contract ownership which you might want to change if you want to implement DAO-based ownership or other form of contract ownership. Go for it. Diamonds can work with any kind of contract ownership strategy. This library contains an internal function version of diamondCut
that can be used in the constructor of a diamond or other places.
In order to call a function that exists in a diamond you need to use the ABI information of the facet that has the function.
Here is an example that uses viem:
const publicClient = await hre.viem.getPublicClient();
const diamondLoupeFacet = await hre.viem.getContractAt("DiamondLoupeFacet", diamondAddress);
In the code above we create a contract variable so we can call contract functions with it.
In this example we know we will use a diamond because we pass a diamond's address as the second argument. But we are using an ABI from the MyUsefulFacet facet so we can call functions that are defined in that facet. MyUsefulFacet's functions must have been added to the diamond (using diamondCut) in order for the diamond to use the function information provided by the ABI of course.
Similarly you need to use the ABI of a facet in Solidity code in order to call functions from a diamond. Here's an example of Solidity code that calls a function from a diamond:
address[] memory result = DiamondLoupeFacet(address(diamondContract)).facetAddresses()
If you need help or would like to discuss diamonds then send me a message on twitter, or email me. Or join the EIP-2535 Diamonds Discord server.
- Introduction to the Diamond Standard, EIP-2535 Diamonds
- EIP-2535 Diamonds
- Understanding Diamonds on Ethereum
- Solidity Storage Layout For Proxy Contracts and Diamonds
- New Storage Layout For Proxy Contracts and Diamonds
- Upgradeable smart contracts using the Diamond Standard
- buidler-deploy supports diamonds
MIT license. See the license file. Anyone can use or modify this software for their purposes.