-
Notifications
You must be signed in to change notification settings - Fork 1
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
0 parents
commit cf8a903
Showing
30 changed files
with
26,971 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 @@ | ||
*.sol linguist-language=Solidity |
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,6 @@ | ||
node_modules | ||
|
||
#Hardhat files | ||
cache | ||
artifacts | ||
.env |
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 @@ | ||
*.js |
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,15 @@ | ||
{ | ||
"overrides": [ | ||
{ | ||
"files": "*.sol", | ||
"options": { | ||
"printWidth": 150, | ||
"tabWidth": 4, | ||
"useTabs": false, | ||
"singleQuote": false, | ||
"bracketSpacing": false, | ||
"explicitTypes": "always" | ||
} | ||
} | ||
] | ||
} |
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,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2021 Nick Mudge | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
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,125 @@ | ||
# Diamond-3-Hardhat Implementation | ||
|
||
This is an implementation for [EIP-2535 Diamond Standard](https://github.com/ethereum/EIPs/issues/2535). 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. | ||
|
||
## Installation | ||
|
||
1. Clone this repo: | ||
```console | ||
git clone [email protected]:mudgen/diamond-3-hardhat.git | ||
``` | ||
|
||
2. Install NPM packages: | ||
```console | ||
cd diamond-3-hardhat | ||
npm install | ||
``` | ||
|
||
## Deployment | ||
|
||
```console | ||
npx hardhat run scripts/deploy.js | ||
``` | ||
|
||
### How the scripts/deploy.js script works | ||
|
||
1. DiamondCutFacet is deployed. | ||
1. 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. | ||
1. The `DiamondInit` contract is deployed. This contains an `init` function which is called on the first diamond upgrade to initialize state of some state variables. Information on how the `diamondCut` function works is here: https://eips.ethereum.org/EIPS/eip-2535#diamond-interface | ||
1. Facets are deployed. | ||
1. The diamond is upgraded. The `diamondCut` function is used to add functions from facets to the diamond. In addition the `diamondCut` function calls the `init` function from the `DiamondInit` contract using `delegatecall` to initialize state variables. | ||
|
||
How a diamond is deployed is not part of the EIP-2535 Diamonds standard. This implementation shows a usable example. | ||
|
||
## Run tests: | ||
```console | ||
npx hardhat test | ||
``` | ||
|
||
## Upgrade a diamond | ||
|
||
Check the `scripts/deploy.js` and or the `test/diamondTest.js` 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. | ||
|
||
## Facet Information | ||
|
||
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. | ||
|
||
## How to Get Started Making Your Diamond | ||
|
||
1. Reading and understand [EIP-2535 Diamonds](https://github.com/ethereum/EIPs/issues/2535). If something is unclear let me know! | ||
|
||
2. 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](./contracts/facets/DiamondCutFacet.sol) and [DiamondLoupeFacet.sol](./contracts/facets/DiamondLoupeFacet.sol) contracts. They implement the `diamondCut` function and the loupe functions. | ||
|
||
The [Diamond.sol](./contracts/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](./contracts/libraries/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. | ||
|
||
## Calling Diamond Functions | ||
|
||
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 web3.js: | ||
|
||
```javascript | ||
let myUsefulFacet = new web3.eth.Contract(MyUsefulFacet.abi, 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: | ||
|
||
```solidity | ||
string result = MyUsefulFacet(address(diamondContract)).getResult() | ||
``` | ||
|
||
## Get Help and Join the Community | ||
|
||
If you need help or would like to discuss diamonds then send me a message [on twitter](https://twitter.com/mudgen), or [email me](mailto:[email protected]). Or join the [EIP-2535 Diamonds Discord server](https://discord.gg/kQewPw2). | ||
|
||
## Useful Links | ||
1. [Introduction to the Diamond Standard, EIP-2535 Diamonds](https://eip2535diamonds.substack.com/p/introduction-to-the-diamond-standard) | ||
1. [EIP-2535 Diamonds](https://github.com/ethereum/EIPs/issues/2535) | ||
1. [Understanding Diamonds on Ethereum](https://dev.to/mudgen/understanding-diamonds-on-ethereum-1fb) | ||
1. [Solidity Storage Layout For Proxy Contracts and Diamonds](https://medium.com/1milliondevs/solidity-storage-layout-for-proxy-contracts-and-diamonds-c4f009b6903) | ||
1. [New Storage Layout For Proxy Contracts and Diamonds](https://medium.com/1milliondevs/new-storage-layout-for-proxy-contracts-and-diamonds-98d01d0eadb) | ||
1. [Upgradeable smart contracts using the Diamond Standard](https://hiddentao.com/archives/2020/05/28/upgradeable-smart-contracts-using-diamond-standard) | ||
1. [buidler-deploy supports diamonds](https://github.com/wighawag/buidler-deploy/) | ||
|
||
## Author | ||
|
||
This example implementation was written by Nick Mudge. | ||
|
||
Contact: | ||
|
||
- https://twitter.com/mudgen | ||
- [email protected] | ||
|
||
## License | ||
|
||
MIT license. See the license file. | ||
Anyone can use or modify this software for their purposes. | ||
|
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,63 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
/******************************************************************************\ | ||
* Author: Nick Mudge <[email protected]> (https://twitter.com/mudgen) | ||
* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 | ||
* | ||
* Implementation of a diamond. | ||
/******************************************************************************/ | ||
|
||
import { LibDiamond } from "./libraries/LibDiamond.sol"; | ||
import { IDiamondCut } from "./interfaces/IDiamondCut.sol"; | ||
|
||
contract Diamond { | ||
|
||
constructor(address _contractOwner, address _diamondCutFacet) payable { | ||
LibDiamond.setContractOwner(_contractOwner); | ||
|
||
// Add the diamondCut external function from the diamondCutFacet | ||
IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); | ||
bytes4[] memory functionSelectors = new bytes4[](1); | ||
functionSelectors[0] = IDiamondCut.diamondCut.selector; | ||
cut[0] = IDiamondCut.FacetCut({ | ||
facetAddress: _diamondCutFacet, | ||
action: IDiamondCut.FacetCutAction.Add, | ||
functionSelectors: functionSelectors | ||
}); | ||
LibDiamond.diamondCut(cut, address(0), ""); | ||
} | ||
|
||
// Find facet for function that is called and execute the | ||
// function if a facet is found and return any value. | ||
fallback() external payable { | ||
LibDiamond.DiamondStorage storage ds; | ||
bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; | ||
// get diamond storage | ||
assembly { | ||
ds.slot := position | ||
} | ||
// get facet from function selector | ||
address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; | ||
require(facet != address(0), "Diamond: Function does not exist"); | ||
// 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()) | ||
} | ||
} | ||
} | ||
|
||
receive() external payable {} | ||
} |
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,30 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
/******************************************************************************\ | ||
* Author: Nick Mudge <[email protected]> (https://twitter.com/mudgen) | ||
* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 | ||
/******************************************************************************/ | ||
|
||
import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; | ||
import { LibDiamond } from "../libraries/LibDiamond.sol"; | ||
|
||
// Remember to add the loupe functions from DiamondLoupeFacet to the diamond. | ||
// The loupe functions are required by the EIP2535 Diamonds standard | ||
|
||
contract DiamondCutFacet is IDiamondCut { | ||
/// @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 override { | ||
LibDiamond.enforceIsContractOwner(); | ||
LibDiamond.diamondCut(_diamondCut, _init, _calldata); | ||
} | ||
} |
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,67 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
/******************************************************************************\ | ||
* Author: Nick Mudge <[email protected]> (https://twitter.com/mudgen) | ||
* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 | ||
/******************************************************************************/ | ||
|
||
import { LibDiamond } from "../libraries/LibDiamond.sol"; | ||
import { IDiamondLoupe } from "../interfaces/IDiamondLoupe.sol"; | ||
import { IERC165 } from "../interfaces/IERC165.sol"; | ||
|
||
// The functions in DiamondLoupeFacet MUST be added to a diamond. | ||
// The EIP-2535 Diamond standard requires these functions. | ||
|
||
contract DiamondLoupeFacet is IDiamondLoupe, IERC165 { | ||
// Diamond Loupe Functions | ||
//////////////////////////////////////////////////////////////////// | ||
/// These functions are expected to be called frequently by tools. | ||
// | ||
// struct Facet { | ||
// address facetAddress; | ||
// bytes4[] functionSelectors; | ||
// } | ||
|
||
/// @notice Gets all facets and their selectors. | ||
/// @return facets_ Facet | ||
function facets() external override view returns (Facet[] memory facets_) { | ||
LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); | ||
uint256 numFacets = ds.facetAddresses.length; | ||
facets_ = new Facet[](numFacets); | ||
for (uint256 i; i < numFacets; i++) { | ||
address facetAddress_ = ds.facetAddresses[i]; | ||
facets_[i].facetAddress = facetAddress_; | ||
facets_[i].functionSelectors = ds.facetFunctionSelectors[facetAddress_].functionSelectors; | ||
} | ||
} | ||
|
||
/// @notice Gets all the function selectors provided by a facet. | ||
/// @param _facet The facet address. | ||
/// @return facetFunctionSelectors_ | ||
function facetFunctionSelectors(address _facet) external override view returns (bytes4[] memory facetFunctionSelectors_) { | ||
LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); | ||
facetFunctionSelectors_ = ds.facetFunctionSelectors[_facet].functionSelectors; | ||
} | ||
|
||
/// @notice Get all the facet addresses used by a diamond. | ||
/// @return facetAddresses_ | ||
function facetAddresses() external override view returns (address[] memory facetAddresses_) { | ||
LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); | ||
facetAddresses_ = ds.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 override view returns (address facetAddress_) { | ||
LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); | ||
facetAddress_ = ds.selectorToFacetAndPosition[_functionSelector].facetAddress; | ||
} | ||
|
||
// This implements ERC-165. | ||
function supportsInterface(bytes4 _interfaceId) external override view returns (bool) { | ||
LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); | ||
return ds.supportedInterfaces[_interfaceId]; | ||
} | ||
} |
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,16 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import { LibDiamond } from "../libraries/LibDiamond.sol"; | ||
import { IERC173 } from "../interfaces/IERC173.sol"; | ||
|
||
contract OwnershipFacet is IERC173 { | ||
function transferOwnership(address _newOwner) external override { | ||
LibDiamond.enforceIsContractOwner(); | ||
LibDiamond.setContractOwner(_newOwner); | ||
} | ||
|
||
function owner() external override view returns (address owner_) { | ||
owner_ = LibDiamond.contractOwner(); | ||
} | ||
} |
Oops, something went wrong.