├── .gitattributes ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE.txt ├── README.md ├── contracts ├── Diamond.sol ├── Migrations.sol ├── facets │ ├── DiamondCutFacet.sol │ ├── DiamondLoupeFacet.sol │ ├── OwnershipFacet.sol │ ├── Test1Facet.sol │ └── Test2Facet.sol ├── interfaces │ ├── IDiamondCut.sol │ ├── IDiamondLoupe.sol │ ├── IERC165.sol │ └── IERC173.sol └── libraries │ └── LibDiamond.sol ├── migrations ├── 1_initial_migration.js └── 2_diamond.js ├── package-lock.json ├── package.json ├── test ├── cacheBugTest.js └── diamondTest.js └── truffle-config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .etherlime-store 4 | coverage 5 | stuff.txt 6 | deployment/deploy.js 7 | .vscode 8 | bin -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.js -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.sol", 5 | "options": { 6 | "printWidth": 150, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "bracketSpacing": false, 11 | "explicitTypes": "always" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 Nick Mudge 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 9 | Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 12 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 13 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 14 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EIP-2535 Diamonds Implementation 2 | 3 | This is a simple example implementation for [EIP-2535 Diamonds](https://eips.ethereum.org/EIPS/eip-2535). To learn about other implementations go here: https://github.com/mudgen/diamond 4 | 5 | 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. 6 | 7 | The `contracts/Diamond.sol` file shows an example of implementing a diamond. 8 | 9 | The `contracts/facets/DiamondCutFacet.sol` file shows how to implement the `diamondCut` external function. 10 | 11 | The `contracts/facets/DiamondLoupeFacet.sol` file shows how to implement the four standard loupe functions. 12 | 13 | The `contracts/libraries/LibDiamond.sol` file shows how to implement Diamond Storage. 14 | 15 | The `test/diamondTest.js` file gives tests for the `diamondCut` function and the Diamond Loupe functions. 16 | 17 | ## How to Get Started Making Your Diamond 18 | 19 | 1. The most important thing is reading and understanding [EIP-2535 Diamonds](https://eips.ethereum.org/EIPS/eip-2535). If something is unclear let me know! 20 | 21 | 2. The second important thing is using an EIP-2535 Diamonds reference implementation. You are at the right place because this is the README for a reference implementation. 22 | 23 | This diamond implementation is boilerplate code that makes a diamond compliant with EIP-2535 Diamonds. 24 | 25 | Specifically you can copy and use the [DiamondCutFacet.sol](./contracts/facets/DiamondCutFacet.sol) and [DiamondLoupeFacet.sol](./contracts/facets/DiamondLoupeFacet.sol) contracts as is. They implement the `diamondCut` function and the loupe functions. 26 | 27 | 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. 28 | 29 | 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. 30 | 31 | The [LibDiamondCut.sol](./contracts/libraries/LibDiamondCut.sol) library contains an internal function version of `diamondCut` that can be used in the constructor of a diamond or other places. 32 | 33 | ## Calling Diamond Functions 34 | 35 | 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. 36 | 37 | Here is an example that uses web3.js: 38 | 39 | ```javascript 40 | let myUsefulFacet = new web3.eth.Contract(MyUsefulFacet.abi, diamondAddress); 41 | ``` 42 | 43 | In the code above we create a contract variable so we can call contract functions with it. 44 | 45 | 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. 46 | 47 | 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: 48 | 49 | ```solidity 50 | string result = MyUsefulFacet(diamondAddress).getResult() 51 | ``` 52 | 53 | ## Get Help and Join the Community 54 | 55 | 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:nick@perfectabstractions.com). Or join the [EIP-2535 Diamonds discord server](https://discord.gg/kQewPw2). 56 | 57 | ## Useful Links 58 | 59 | 1. [EIP-2535 Diamonds](https://eips.ethereum.org/EIPS/eip-2535) 60 | 1. [diamond-3-hardhat](https://github.com/mudgen/diamond-3-hardhat) 61 | 1. [Introduction to EIP-2535 Diamonds](https://eip2535diamonds.substack.com/p/introduction-to-the-diamond-standard) 62 | 1. [Solidity Storage Layout For Proxy Contracts and Diamonds](https://medium.com/1milliondevs/solidity-storage-layout-for-proxy-contracts-and-diamonds-c4f009b6903) 63 | 1. [New Storage Layout For Proxy Contracts and Diamonds](https://medium.com/1milliondevs/new-storage-layout-for-proxy-contracts-and-diamonds-98d01d0eadb) 64 | 1. [Diamond Setter](https://github.com/lampshade9909/DiamondSetter) 65 | 1. [Upgradeable smart contracts using the EIP-2535 Diamonds](https://hiddentao.com/archives/2020/05/28/upgradeable-smart-contracts-using-diamond-standard) 66 | 1. [buidler-deploy supports diamonds](https://github.com/wighawag/buidler-deploy/) 67 | 68 | ## Author 69 | 70 | This implementation was written by Nick Mudge. 71 | 72 | Contact: 73 | 74 | - https://twitter.com/mudgen 75 | - nick@perfectabstractions.com 76 | 77 | ## License 78 | 79 | MIT license. See the license file. 80 | Anyone can use or modify this software for their purposes. 81 | -------------------------------------------------------------------------------- /contracts/Diamond.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /******************************************************************************\ 6 | * Author: Nick Mudge (https://twitter.com/mudgen) 7 | * EIP-2535 Diamond Standard: https://eips.ethereum.org/EIPS/eip-2535 8 | * 9 | * Implementation of a diamond. 10 | /******************************************************************************/ 11 | 12 | import "./libraries/LibDiamond.sol"; 13 | import "./interfaces/IDiamondLoupe.sol"; 14 | import "./interfaces/IDiamondCut.sol"; 15 | import "./interfaces/IERC173.sol"; 16 | import "./interfaces/IERC165.sol"; 17 | 18 | contract Diamond { 19 | // more arguments are added to this struct 20 | // this avoids stack too deep errors 21 | struct DiamondArgs { 22 | address owner; 23 | } 24 | 25 | constructor(IDiamondCut.FacetCut[] memory _diamondCut, DiamondArgs memory _args) payable { 26 | LibDiamond.diamondCut(_diamondCut, address(0), new bytes(0)); 27 | LibDiamond.setContractOwner(_args.owner); 28 | 29 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 30 | 31 | // adding ERC165 data 32 | ds.supportedInterfaces[type(IERC165).interfaceId] = true; 33 | ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true; 34 | ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true; 35 | ds.supportedInterfaces[type(IERC173).interfaceId] = true; 36 | } 37 | 38 | // Find facet for function that is called and execute the 39 | // function if a facet is found and return any value. 40 | fallback() external payable { 41 | LibDiamond.DiamondStorage storage ds; 42 | bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; 43 | assembly { 44 | ds.slot := position 45 | } 46 | address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; 47 | require(facet != address(0), "Diamond: Function does not exist"); 48 | assembly { 49 | calldatacopy(0, 0, calldatasize()) 50 | let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) 51 | returndatacopy(0, 0, returndatasize()) 52 | switch result 53 | case 0 { 54 | revert(0, returndatasize()) 55 | } 56 | default { 57 | return(0, returndatasize()) 58 | } 59 | } 60 | } 61 | 62 | receive() external payable {} 63 | } 64 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | contract Migrations { 6 | address public owner; 7 | 8 | // A function with the signature `last_completed_migration()`, returning a uint, is required. 9 | uint256 public last_completed_migration; 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | constructor() { 16 | owner = msg.sender; 17 | } 18 | 19 | // A function with the signature `setCompleted(uint)` is required. 20 | function setCompleted(uint256 completed) external restricted { 21 | last_completed_migration = completed; 22 | } 23 | 24 | function upgrade(address new_address) external restricted { 25 | Migrations upgraded = Migrations(new_address); 26 | upgraded.setCompleted(last_completed_migration); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/facets/DiamondCutFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /******************************************************************************\ 6 | * Author: Nick Mudge (https://twitter.com/mudgen) 7 | * EIP-2535 Diamond Standard: https://eips.ethereum.org/EIPS/eip-2535 8 | /******************************************************************************/ 9 | 10 | import "../interfaces/IDiamondCut.sol"; 11 | import "../libraries/LibDiamond.sol"; 12 | 13 | contract DiamondCutFacet is IDiamondCut { 14 | /// @notice Add/replace/remove any number of functions and optionally execute 15 | /// a function with delegatecall 16 | /// @param _diamondCut Contains the facet addresses and function selectors 17 | /// @param _init The address of the contract or facet to execute _calldata 18 | /// @param _calldata A function call, including function selector and arguments 19 | /// _calldata is executed with delegatecall on _init 20 | function diamondCut( 21 | FacetCut[] calldata _diamondCut, 22 | address _init, 23 | bytes calldata _calldata 24 | ) external override { 25 | LibDiamond.enforceIsContractOwner(); 26 | LibDiamond.diamondCut(_diamondCut, _init, _calldata); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /contracts/facets/DiamondLoupeFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /******************************************************************************\ 6 | * Author: Nick Mudge (https://twitter.com/mudgen) 7 | * EIP-2535 Diamond Standard: https://eips.ethereum.org/EIPS/eip-2535 8 | /******************************************************************************/ 9 | 10 | import "../libraries/LibDiamond.sol"; 11 | import "../interfaces/IDiamondCut.sol"; 12 | import "../interfaces/IDiamondLoupe.sol"; 13 | import "../interfaces/IERC165.sol"; 14 | 15 | contract DiamondLoupeFacet is IDiamondLoupe, IERC165 { 16 | // Diamond Loupe Functions 17 | //////////////////////////////////////////////////////////////////// 18 | /// These functions are expected to be called frequently by tools. 19 | // 20 | // struct Facet { 21 | // address facetAddress; 22 | // bytes4[] functionSelectors; 23 | // } 24 | 25 | /// @notice Gets all facets and their selectors. 26 | /// @return facets_ Facet 27 | function facets() external override view returns (Facet[] memory facets_) { 28 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 29 | uint256 numFacets = ds.facetAddresses.length; 30 | facets_ = new Facet[](numFacets); 31 | for (uint256 i; i < numFacets; i++) { 32 | address facetAddress_ = ds.facetAddresses[i]; 33 | facets_[i].facetAddress = facetAddress_; 34 | facets_[i].functionSelectors = ds.facetFunctionSelectors[facetAddress_].functionSelectors; 35 | } 36 | } 37 | 38 | /// @notice Gets all the function selectors provided by a facet. 39 | /// @param _facet The facet address. 40 | /// @return facetFunctionSelectors_ 41 | function facetFunctionSelectors(address _facet) external override view returns (bytes4[] memory facetFunctionSelectors_) { 42 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 43 | facetFunctionSelectors_ = ds.facetFunctionSelectors[_facet].functionSelectors; 44 | } 45 | 46 | /// @notice Get all the facet addresses used by a diamond. 47 | /// @return facetAddresses_ 48 | function facetAddresses() external override view returns (address[] memory facetAddresses_) { 49 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 50 | facetAddresses_ = ds.facetAddresses; 51 | } 52 | 53 | /// @notice Gets the facet that supports the given selector. 54 | /// @dev If facet is not found return address(0). 55 | /// @param _functionSelector The function selector. 56 | /// @return facetAddress_ The facet address. 57 | function facetAddress(bytes4 _functionSelector) external override view returns (address facetAddress_) { 58 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 59 | facetAddress_ = ds.selectorToFacetAndPosition[_functionSelector].facetAddress; 60 | } 61 | 62 | // This implements ERC-165. 63 | function supportsInterface(bytes4 _interfaceId) external override view returns (bool) { 64 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 65 | return ds.supportedInterfaces[_interfaceId]; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /contracts/facets/OwnershipFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | 4 | import "../libraries/LibDiamond.sol"; 5 | import "../interfaces/IERC173.sol"; 6 | 7 | contract OwnershipFacet is IERC173 { 8 | function transferOwnership(address _newOwner) external override { 9 | LibDiamond.enforceIsContractOwner(); 10 | LibDiamond.setContractOwner(_newOwner); 11 | } 12 | 13 | function owner() external override view returns (address owner_) { 14 | owner_ = LibDiamond.contractOwner(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/facets/Test1Facet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | contract Test1Facet { 6 | event TestEvent(address something); 7 | 8 | function test1Func1() external {} 9 | 10 | function test1Func2() external {} 11 | 12 | function test1Func3() external {} 13 | 14 | function test1Func4() external {} 15 | 16 | function test1Func5() external {} 17 | 18 | function test1Func6() external {} 19 | 20 | function test1Func7() external {} 21 | 22 | function test1Func8() external {} 23 | 24 | function test1Func9() external {} 25 | 26 | function test1Func10() external {} 27 | 28 | function test1Func11() external {} 29 | 30 | function test1Func12() external {} 31 | 32 | function test1Func13() external {} 33 | 34 | function test1Func14() external {} 35 | 36 | function test1Func15() external {} 37 | 38 | function test1Func16() external {} 39 | 40 | function test1Func17() external {} 41 | 42 | function test1Func18() external {} 43 | 44 | function test1Func19() external {} 45 | 46 | function test1Func20() external {} 47 | 48 | function supportsInterface(bytes4 _interfaceID) external view returns (bool) {} 49 | } 50 | -------------------------------------------------------------------------------- /contracts/facets/Test2Facet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | contract Test2Facet { 6 | function test2Func1() external {} 7 | 8 | function test2Func2() external {} 9 | 10 | function test2Func3() external {} 11 | 12 | function test2Func4() external {} 13 | 14 | function test2Func5() external {} 15 | 16 | function test2Func6() external {} 17 | 18 | function test2Func7() external {} 19 | 20 | function test2Func8() external {} 21 | 22 | function test2Func9() external {} 23 | 24 | function test2Func10() external {} 25 | 26 | function test2Func11() external {} 27 | 28 | function test2Func12() external {} 29 | 30 | function test2Func13() external {} 31 | 32 | function test2Func14() external {} 33 | 34 | function test2Func15() external {} 35 | 36 | function test2Func16() external {} 37 | 38 | function test2Func17() external {} 39 | 40 | function test2Func18() external {} 41 | 42 | function test2Func19() external {} 43 | 44 | function test2Func20() external {} 45 | } 46 | -------------------------------------------------------------------------------- /contracts/interfaces/IDiamondCut.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /******************************************************************************\ 6 | * Author: Nick Mudge (https://twitter.com/mudgen) 7 | * EIP-2535 Diamond Standard: https://eips.ethereum.org/EIPS/eip-2535 8 | /******************************************************************************/ 9 | 10 | interface IDiamondCut { 11 | enum FacetCutAction {Add, Replace, Remove} 12 | // Add=0, Replace=1, Remove=2 13 | 14 | struct FacetCut { 15 | address facetAddress; 16 | FacetCutAction action; 17 | bytes4[] functionSelectors; 18 | } 19 | 20 | /// @notice Add/replace/remove any number of functions and optionally execute 21 | /// a function with delegatecall 22 | /// @param _diamondCut Contains the facet addresses and function selectors 23 | /// @param _init The address of the contract or facet to execute _calldata 24 | /// @param _calldata A function call, including function selector and arguments 25 | /// _calldata is executed with delegatecall on _init 26 | function diamondCut( 27 | FacetCut[] calldata _diamondCut, 28 | address _init, 29 | bytes calldata _calldata 30 | ) external; 31 | 32 | event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); 33 | } 34 | -------------------------------------------------------------------------------- /contracts/interfaces/IDiamondLoupe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /******************************************************************************\ 6 | * Author: Nick Mudge (https://twitter.com/mudgen) 7 | * EIP-2535 Diamond Standard: https://eips.ethereum.org/EIPS/eip-2535 8 | /******************************************************************************/ 9 | 10 | // A loupe is a small magnifying glass used to look at diamonds. 11 | // These functions look at diamonds 12 | interface IDiamondLoupe { 13 | /// These functions are expected to be called frequently 14 | /// by tools. 15 | 16 | struct Facet { 17 | address facetAddress; 18 | bytes4[] functionSelectors; 19 | } 20 | 21 | /// @notice Gets all facet addresses and their four byte function selectors. 22 | /// @return facets_ Facet 23 | function facets() external view returns (Facet[] memory facets_); 24 | 25 | /// @notice Gets all the function selectors supported by a specific facet. 26 | /// @param _facet The facet address. 27 | /// @return facetFunctionSelectors_ 28 | function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); 29 | 30 | /// @notice Get all the facet addresses used by a diamond. 31 | /// @return facetAddresses_ 32 | function facetAddresses() external view returns (address[] memory facetAddresses_); 33 | 34 | /// @notice Gets the facet that supports the given selector. 35 | /// @dev If facet is not found return address(0). 36 | /// @param _functionSelector The function selector. 37 | /// @return facetAddress_ The facet address. 38 | function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_); 39 | } 40 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC165.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | interface IERC165 { 6 | /// @notice Query if a contract implements an interface 7 | /// @param interfaceId The interface identifier, as specified in ERC-165 8 | /// @dev Interface identification is specified in ERC-165. This function 9 | /// uses less than 30,000 gas. 10 | /// @return `true` if the contract implements `interfaceID` and 11 | /// `interfaceID` is not 0xffffffff, `false` otherwise 12 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 13 | } 14 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC173.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | 4 | /// @title ERC-173 Contract Ownership Standard 5 | /// Note: the ERC-165 identifier for this interface is 0x7f5828d0 6 | /* is ERC165 */ 7 | interface IERC173 { 8 | /// @dev This emits when ownership of a contract changes. 9 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 10 | 11 | /// @notice Get the address of the owner 12 | /// @return owner_ The address of the owner. 13 | function owner() external view returns (address owner_); 14 | 15 | /// @notice Set the address of the new owner of the contract 16 | /// @dev Set _newOwner to address(0) to renounce any ownership. 17 | /// @param _newOwner The address of the new owner of the contract 18 | function transferOwnership(address _newOwner) external; 19 | } 20 | -------------------------------------------------------------------------------- /contracts/libraries/LibDiamond.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.6; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /******************************************************************************\ 6 | * Author: Nick Mudge (https://twitter.com/mudgen) 7 | * EIP-2535 Diamond Standard: https://eips.ethereum.org/EIPS/eip-2535 8 | /******************************************************************************/ 9 | 10 | import "../interfaces/IDiamondCut.sol"; 11 | 12 | library LibDiamond { 13 | bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); 14 | 15 | struct FacetAddressAndPosition { 16 | address facetAddress; 17 | uint16 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array 18 | } 19 | 20 | struct FacetFunctionSelectors { 21 | bytes4[] functionSelectors; 22 | uint16 facetAddressPosition; // position of facetAddress in facetAddresses array 23 | } 24 | 25 | struct DiamondStorage { 26 | // maps function selector to the facet address and 27 | // the position of the selector in the facetFunctionSelectors.selectors array 28 | mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition; 29 | // maps facet addresses to function selectors 30 | mapping(address => FacetFunctionSelectors) facetFunctionSelectors; 31 | // facet addresses 32 | address[] facetAddresses; 33 | // Used to query if a contract implements an interface. 34 | // Used to implement ERC-165. 35 | mapping(bytes4 => bool) supportedInterfaces; 36 | // owner of the contract 37 | address contractOwner; 38 | } 39 | 40 | function diamondStorage() internal pure returns (DiamondStorage storage ds) { 41 | bytes32 position = DIAMOND_STORAGE_POSITION; 42 | assembly { 43 | ds.slot := position 44 | } 45 | } 46 | 47 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 48 | 49 | function setContractOwner(address _newOwner) internal { 50 | DiamondStorage storage ds = diamondStorage(); 51 | address previousOwner = ds.contractOwner; 52 | ds.contractOwner = _newOwner; 53 | emit OwnershipTransferred(previousOwner, _newOwner); 54 | } 55 | 56 | function contractOwner() internal view returns (address contractOwner_) { 57 | contractOwner_ = diamondStorage().contractOwner; 58 | } 59 | 60 | function enforceIsContractOwner() internal view { 61 | require(msg.sender == diamondStorage().contractOwner, "LibDiamond: Must be contract owner"); 62 | } 63 | 64 | event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata); 65 | 66 | // Internal function version of diamondCut 67 | function diamondCut( 68 | IDiamondCut.FacetCut[] memory _diamondCut, 69 | address _init, 70 | bytes memory _calldata 71 | ) internal { 72 | for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) { 73 | IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action; 74 | if (action == IDiamondCut.FacetCutAction.Add) { 75 | addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); 76 | } else if (action == IDiamondCut.FacetCutAction.Replace) { 77 | replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); 78 | } else if (action == IDiamondCut.FacetCutAction.Remove) { 79 | removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); 80 | } else { 81 | revert("LibDiamondCut: Incorrect FacetCutAction"); 82 | } 83 | } 84 | emit DiamondCut(_diamondCut, _init, _calldata); 85 | initializeDiamondCut(_init, _calldata); 86 | } 87 | 88 | function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { 89 | require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); 90 | DiamondStorage storage ds = diamondStorage(); 91 | // uint16 selectorCount = uint16(diamondStorage().selectors.length); 92 | require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); 93 | uint16 selectorPosition = uint16(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length); 94 | // add new facet address if it does not exist 95 | if (selectorPosition == 0) { 96 | enforceHasContractCode(_facetAddress, "LibDiamondCut: New facet has no code"); 97 | ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = uint16(ds.facetAddresses.length); 98 | ds.facetAddresses.push(_facetAddress); 99 | } 100 | for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { 101 | bytes4 selector = _functionSelectors[selectorIndex]; 102 | address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; 103 | require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists"); 104 | ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(selector); 105 | ds.selectorToFacetAndPosition[selector].facetAddress = _facetAddress; 106 | ds.selectorToFacetAndPosition[selector].functionSelectorPosition = selectorPosition; 107 | selectorPosition++; 108 | } 109 | } 110 | 111 | function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { 112 | require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); 113 | DiamondStorage storage ds = diamondStorage(); 114 | require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); 115 | uint16 selectorPosition = uint16(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length); 116 | // add new facet address if it does not exist 117 | if (selectorPosition == 0) { 118 | enforceHasContractCode(_facetAddress, "LibDiamondCut: New facet has no code"); 119 | ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = uint16(ds.facetAddresses.length); 120 | ds.facetAddresses.push(_facetAddress); 121 | } 122 | for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { 123 | bytes4 selector = _functionSelectors[selectorIndex]; 124 | address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; 125 | require(oldFacetAddress != _facetAddress, "LibDiamondCut: Can't replace function with same function"); 126 | removeFunction(oldFacetAddress, selector); 127 | // add function 128 | ds.selectorToFacetAndPosition[selector].functionSelectorPosition = selectorPosition; 129 | ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(selector); 130 | ds.selectorToFacetAndPosition[selector].facetAddress = _facetAddress; 131 | selectorPosition++; 132 | } 133 | } 134 | 135 | function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { 136 | require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); 137 | DiamondStorage storage ds = diamondStorage(); 138 | // if function does not exist then do nothing and return 139 | require(_facetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)"); 140 | for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { 141 | bytes4 selector = _functionSelectors[selectorIndex]; 142 | address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; 143 | removeFunction(oldFacetAddress, selector); 144 | } 145 | } 146 | 147 | function removeFunction(address _facetAddress, bytes4 _selector) internal { 148 | DiamondStorage storage ds = diamondStorage(); 149 | require(_facetAddress != address(0), "LibDiamondCut: Can't remove function that doesn't exist"); 150 | // an immutable function is a function defined directly in a diamond 151 | require(_facetAddress != address(this), "LibDiamondCut: Can't remove immutable function"); 152 | // replace selector with last selector, then delete last selector 153 | uint256 selectorPosition = ds.selectorToFacetAndPosition[_selector].functionSelectorPosition; 154 | uint256 lastSelectorPosition = ds.facetFunctionSelectors[_facetAddress].functionSelectors.length - 1; 155 | // if not the same then replace _selector with lastSelector 156 | if (selectorPosition != lastSelectorPosition) { 157 | bytes4 lastSelector = ds.facetFunctionSelectors[_facetAddress].functionSelectors[lastSelectorPosition]; 158 | ds.facetFunctionSelectors[_facetAddress].functionSelectors[selectorPosition] = lastSelector; 159 | ds.selectorToFacetAndPosition[lastSelector].functionSelectorPosition = uint16(selectorPosition); 160 | } 161 | // delete the last selector 162 | ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop(); 163 | delete ds.selectorToFacetAndPosition[_selector]; 164 | 165 | // if no more selectors for facet address then delete the facet address 166 | if (lastSelectorPosition == 0) { 167 | // replace facet address with last facet address and delete last facet address 168 | uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1; 169 | uint256 facetAddressPosition = ds.facetFunctionSelectors[_facetAddress].facetAddressPosition; 170 | if (facetAddressPosition != lastFacetAddressPosition) { 171 | address lastFacetAddress = ds.facetAddresses[lastFacetAddressPosition]; 172 | ds.facetAddresses[facetAddressPosition] = lastFacetAddress; 173 | ds.facetFunctionSelectors[lastFacetAddress].facetAddressPosition = uint16(facetAddressPosition); 174 | } 175 | ds.facetAddresses.pop(); 176 | delete ds.facetFunctionSelectors[_facetAddress].facetAddressPosition; 177 | } 178 | } 179 | 180 | function initializeDiamondCut(address _init, bytes memory _calldata) internal { 181 | if (_init == address(0)) { 182 | require(_calldata.length == 0, "LibDiamondCut: _init is address(0) but_calldata is not empty"); 183 | } else { 184 | require(_calldata.length > 0, "LibDiamondCut: _calldata is empty but _init is not address(0)"); 185 | if (_init != address(this)) { 186 | enforceHasContractCode(_init, "LibDiamondCut: _init address has no code"); 187 | } 188 | (bool success, bytes memory error) = _init.delegatecall(_calldata); 189 | if (!success) { 190 | if (error.length > 0) { 191 | // bubble up the error 192 | revert(string(error)); 193 | } else { 194 | revert("LibDiamondCut: _init function reverted"); 195 | } 196 | } 197 | } 198 | } 199 | 200 | function enforceHasContractCode(address _contract, string memory _errorMessage) internal view { 201 | uint256 contractSize; 202 | assembly { 203 | contractSize := extcodesize(_contract) 204 | } 205 | require(contractSize > 0, _errorMessage); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-const */ 2 | /* global artifacts */ 3 | 4 | const Migrations = artifacts.require('Migrations') 5 | 6 | module.exports = function (deployer) { 7 | deployer.deploy(Migrations) 8 | } 9 | -------------------------------------------------------------------------------- /migrations/2_diamond.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-const */ 2 | /* global artifacts */ 3 | 4 | const Diamond = artifacts.require('Diamond') 5 | const DiamondCutFacet = artifacts.require('DiamondCutFacet') 6 | const DiamondLoupeFacet = artifacts.require('DiamondLoupeFacet') 7 | const OwnershipFacet = artifacts.require('OwnershipFacet') 8 | const Test1Facet = artifacts.require('Test1Facet') 9 | const Test2Facet = artifacts.require('Test2Facet') 10 | 11 | const FacetCutAction = { 12 | Add: 0, 13 | Replace: 1, 14 | Remove: 2 15 | } 16 | 17 | function getSelectors (contract) { 18 | const selectors = contract.abi.reduce((acc, val) => { 19 | if (val.type === 'function') { 20 | acc.push(val.signature) 21 | return acc 22 | } else { 23 | return acc 24 | } 25 | }, []) 26 | return selectors 27 | } 28 | 29 | module.exports = function (deployer, network, accounts) { 30 | deployer.deploy(Test1Facet) 31 | deployer.deploy(Test2Facet) 32 | 33 | deployer.deploy(DiamondCutFacet) 34 | deployer.deploy(DiamondLoupeFacet) 35 | deployer.deploy(OwnershipFacet).then(() => { 36 | const diamondCut = [ 37 | [DiamondCutFacet.address, FacetCutAction.Add, getSelectors(DiamondCutFacet)], 38 | [DiamondLoupeFacet.address, FacetCutAction.Add, getSelectors(DiamondLoupeFacet)], 39 | [OwnershipFacet.address, FacetCutAction.Add, getSelectors(OwnershipFacet)] 40 | ] 41 | return deployer.deploy(Diamond, diamondCut, [accounts[0]]) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diamond-3", 3 | "version": "1.4.0", 4 | "description": "This is the default package.json generated for your project", 5 | "main": "index.js", 6 | "scripts": { 7 | "prettier": "prettier --write contracts/**/*.sol", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "truffle": "^5.1.44" 14 | }, 15 | "devDependencies": { 16 | "@codechecks/client": "^0.1.10", 17 | "eth-gas-reporter": "^0.2.17", 18 | "prettier": "^2.1.1", 19 | "prettier-plugin-solidity": "^1.0.0-alpha.57", 20 | "standard": "^14.3.4", 21 | "typescript": "^4.0.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/cacheBugTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-const */ 2 | /* global contract artifacts web3 before it assert */ 3 | 4 | const Diamond = artifacts.require('Diamond') 5 | const DiamondCutFacet = artifacts.require('DiamondCutFacet') 6 | const DiamondLoupeFacet = artifacts.require('DiamondLoupeFacet') 7 | const Test1Facet = artifacts.require('Test1Facet') 8 | 9 | const FacetCutAction = { 10 | Add: 0, 11 | Replace: 1, 12 | Remove: 2 13 | } 14 | 15 | // The diamond example comes with 8 function selectors 16 | // [cut, loupe, loupe, loupe, loupe, erc165, transferOwnership, owner] 17 | // This bug manifests if you delete something from the final 18 | // selector slot array, so we'll fill up a new slot with 19 | // things, and have a fresh row to work with. 20 | contract('Cache bug test', async accounts => { 21 | let test1Facet 22 | let diamondCutFacet 23 | let diamondLoupeFacet 24 | let diamond 25 | const ownerSel = '0x8da5cb5b' 26 | const zeroAddress = '0x0000000000000000000000000000000000000000' 27 | // Selectors without 0x 28 | // web3.eth.abi.encodeFunctionSignature("test1Func2()").slice(2) etc 29 | const sel0 = '0x19e3b533' // fills up slot 1 30 | const sel1 = '0x0716c2ae' // fills up slot 1 31 | const sel2 = '0x11046047' // fills up slot 1 32 | const sel3 = '0xcf3bbe18' // fills up slot 1 33 | const sel4 = '0x24c1d5a7' // fills up slot 1 34 | const sel5 = '0xcbb835f6' // fills up slot 1 35 | const sel6 = '0xcbb835f7' // fills up slot 1 36 | const sel7 = '0xcbb835f8' // fills up slot 2 37 | const sel8 = '0xcbb835f9' // fills up slot 2 38 | const sel9 = '0xcbb835fa' // fills up slot 2 39 | const sel10 = '0xcbb835fb' // fills up slot 2 40 | let selectors = [ 41 | sel0, 42 | sel1, 43 | sel2, 44 | sel3, 45 | sel4, 46 | sel5, 47 | sel6, 48 | sel7, 49 | sel8, 50 | sel9, 51 | sel10 52 | ] 53 | 54 | before(async () => { 55 | diamond = await Diamond.deployed() 56 | test1Facet = await Test1Facet.deployed() 57 | diamondCutFacet = new web3.eth.Contract(DiamondCutFacet.abi, diamond.address) 58 | diamondLoupeFacet = new web3.eth.Contract(DiamondLoupeFacet.abi, diamond.address) 59 | web3.eth.defaultAccount = accounts[0] 60 | 61 | // Add functions 62 | await diamondCutFacet.methods.diamondCut([[test1Facet.address, FacetCutAction.Add, selectors]], zeroAddress, '0x').send({ from: web3.eth.defaultAccount, gas: 1000000 }) 63 | 64 | // Remove function selectors 65 | // Function selector for the owner function in slot 0 66 | selectors = [ 67 | ownerSel, // owner selector 68 | sel5, 69 | sel10 70 | ] 71 | await diamondCutFacet.methods.diamondCut([[zeroAddress, FacetCutAction.Remove, selectors]], zeroAddress, '0x').send({ from: web3.eth.defaultAccount, gas: 1000000 }) 72 | }) 73 | 74 | it('should not exhibit the cache bug', async () => { 75 | // Get the test1Facet's registered functions 76 | selectors = await diamondLoupeFacet.methods.facetFunctionSelectors(test1Facet.address).call() 77 | 78 | // Check individual correctness 79 | assert.isTrue(selectors.includes(sel0), 'Does not contain sel0') 80 | assert.isTrue(selectors.includes(sel1), 'Does not contain sel1') 81 | assert.isTrue(selectors.includes(sel2), 'Does not contain sel2') 82 | assert.isTrue(selectors.includes(sel3), 'Does not contain sel3') 83 | assert.isTrue(selectors.includes(sel4), 'Does not contain sel4') 84 | assert.isTrue(selectors.includes(sel6), 'Does not contain sel6') 85 | assert.isTrue(selectors.includes(sel7), 'Does not contain sel7') 86 | assert.isTrue(selectors.includes(sel8), 'Does not contain sel8') 87 | assert.isTrue(selectors.includes(sel9), 'Does not contain sel9') 88 | 89 | assert.isFalse(selectors.includes(ownerSel), 'Contains ownerSel') 90 | assert.isFalse(selectors.includes(sel10), 'Contains sel10') 91 | assert.isFalse(selectors.includes(sel5), 'Contains sel5') 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /test/diamondTest.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-const */ 2 | /* global contract artifacts web3 before it assert */ 3 | 4 | const Diamond = artifacts.require('Diamond') 5 | const DiamondCutFacet = artifacts.require('DiamondCutFacet') 6 | const DiamondLoupeFacet = artifacts.require('DiamondLoupeFacet') 7 | const OwnershipFacet = artifacts.require('OwnershipFacet') 8 | const Test1Facet = artifacts.require('Test1Facet') 9 | const Test2Facet = artifacts.require('Test2Facet') 10 | const FacetCutAction = { 11 | Add: 0, 12 | Replace: 1, 13 | Remove: 2 14 | } 15 | 16 | function getSelectors (contract) { 17 | const selectors = contract.abi.reduce((acc, val) => { 18 | if (val.type === 'function') { 19 | acc.push(val.signature) 20 | return acc 21 | } else { 22 | return acc 23 | } 24 | }, []) 25 | return selectors 26 | } 27 | 28 | function removeItem (array, item) { 29 | array.splice(array.indexOf(item), 1) 30 | return array 31 | } 32 | 33 | function findPositionInFacets (facetAddress, facets) { 34 | for (let i = 0; i < facets.length; i++) { 35 | if (facets[i].facetAddress === facetAddress) { 36 | return i 37 | } 38 | } 39 | } 40 | 41 | contract('DiamondTest', async (accounts) => { 42 | let diamondCutFacet 43 | let diamondLoupeFacet 44 | // eslint-disable-next-line no-unused-vars 45 | let ownershipFacet 46 | let diamond 47 | let test1Facet 48 | let test2Facet 49 | let result 50 | let addresses = [] 51 | 52 | const zeroAddress = '0x0000000000000000000000000000000000000000' 53 | 54 | before(async () => { 55 | test1Facet = await Test1Facet.deployed() 56 | test2Facet = await Test2Facet.deployed() 57 | diamond = await Diamond.deployed() 58 | diamondCutFacet = new web3.eth.Contract(DiamondCutFacet.abi, diamond.address) 59 | diamondLoupeFacet = new web3.eth.Contract(DiamondLoupeFacet.abi, diamond.address) 60 | // unfortunately this is done for the side affect of making selectors available in the ABI of 61 | // OwnershipFacet 62 | // eslint-disable-next-line no-unused-vars 63 | ownershipFacet = new web3.eth.Contract(OwnershipFacet.abi, diamond.address) 64 | web3.eth.defaultAccount = accounts[0] 65 | }) 66 | 67 | it('should have three facets -- call to facetAddresses function', async () => { 68 | for (const address of await diamondLoupeFacet.methods.facetAddresses().call()) { 69 | addresses.push(address) 70 | } 71 | 72 | // console.log(addresses) 73 | assert.equal(addresses.length, 3) 74 | }) 75 | 76 | it('facets should have the right function selectors -- call to facetFunctionSelectors function', async () => { 77 | let selectors = getSelectors(DiamondCutFacet) 78 | result = await diamondLoupeFacet.methods.facetFunctionSelectors(addresses[0]).call() 79 | assert.sameMembers(result, selectors) 80 | selectors = getSelectors(DiamondLoupeFacet) 81 | result = await diamondLoupeFacet.methods.facetFunctionSelectors(addresses[1]).call() 82 | assert.sameMembers(result, selectors) 83 | selectors = getSelectors(OwnershipFacet) 84 | result = await diamondLoupeFacet.methods.facetFunctionSelectors(addresses[2]).call() 85 | assert.sameMembers(result, selectors) 86 | }) 87 | 88 | it('selectors should be associated to facets correctly -- multiple calls to facetAddress function', async () => { 89 | assert.equal( 90 | addresses[0], 91 | await diamondLoupeFacet.methods.facetAddress('0x1f931c1c').call() 92 | ) 93 | assert.equal( 94 | addresses[1], 95 | await diamondLoupeFacet.methods.facetAddress('0xcdffacc6').call() 96 | ) 97 | assert.equal( 98 | addresses[1], 99 | await diamondLoupeFacet.methods.facetAddress('0x01ffc9a7').call() 100 | ) 101 | assert.equal( 102 | addresses[2], 103 | await diamondLoupeFacet.methods.facetAddress('0xf2fde38b').call() 104 | ) 105 | }) 106 | 107 | it('should get all the facets and function selectors of the diamond -- call to facets function', async () => { 108 | result = await diamondLoupeFacet.methods.facets().call() 109 | assert.equal(result[0].facetAddress, addresses[0]) 110 | let selectors = getSelectors(DiamondCutFacet) 111 | assert.sameMembers(result[0].functionSelectors, selectors) 112 | assert.equal(result[1].facetAddress, addresses[1]) 113 | selectors = getSelectors(DiamondLoupeFacet) 114 | assert.sameMembers(result[1].functionSelectors, selectors) 115 | assert.equal(result[2].facetAddress, addresses[2]) 116 | selectors = getSelectors(OwnershipFacet) 117 | assert.sameMembers(result[2].functionSelectors, selectors) 118 | assert.equal(result.length, 3) 119 | }) 120 | 121 | it('should add test1 functions', async () => { 122 | let selectors = getSelectors(test1Facet).slice(0, -1) 123 | addresses.push(test1Facet.address) 124 | await diamondCutFacet.methods 125 | .diamondCut([[test1Facet.address, FacetCutAction.Add, selectors]], zeroAddress, '0x') 126 | .send({ from: web3.eth.defaultAccount, gas: 1000000 }) 127 | result = await diamondLoupeFacet.methods.facetFunctionSelectors(addresses[3]).call() 128 | assert.sameMembers(result, selectors) 129 | }) 130 | 131 | it('should test function call', async () => { 132 | const test1FacetDiamond = new web3.eth.Contract(Test1Facet.abi, diamond.address) 133 | await test1FacetDiamond.methods.test1Func10().send({ from: web3.eth.defaultAccount, gas: 1000000 }) 134 | }) 135 | 136 | it('should replace test1 function', async () => { 137 | let selectors = getSelectors(test1Facet).slice(-1) 138 | await diamondCutFacet.methods 139 | .diamondCut([[test1Facet.address, FacetCutAction.Replace, selectors]], zeroAddress, '0x') 140 | .send({ from: web3.eth.defaultAccount, gas: 1000000 }) 141 | result = await diamondLoupeFacet.methods.facetFunctionSelectors(addresses[3]).call() 142 | assert.sameMembers(result, getSelectors(test1Facet)) 143 | }) 144 | 145 | it('should add test2 functions', async () => { 146 | const selectors = getSelectors(test2Facet) 147 | addresses.push(test2Facet.address) 148 | await diamondCutFacet.methods 149 | .diamondCut([[test2Facet.address, FacetCutAction.Add, selectors]], zeroAddress, '0x') 150 | .send({ from: web3.eth.defaultAccount, gas: 1000000 }) 151 | result = await diamondLoupeFacet.methods.facetFunctionSelectors(addresses[4]).call() 152 | assert.sameMembers(result, selectors) 153 | }) 154 | 155 | it('should remove some test2 functions', async () => { 156 | let selectors = getSelectors(test2Facet) 157 | let removeSelectors = [].concat(selectors.slice(0, 1), selectors.slice(4, 6), selectors.slice(-2)) 158 | result = await diamondCutFacet.methods 159 | .diamondCut([[zeroAddress, FacetCutAction.Remove, removeSelectors]], zeroAddress, '0x') 160 | .send({ from: web3.eth.defaultAccount, gas: 1000000 }) 161 | result = await diamondLoupeFacet.methods.facetFunctionSelectors(addresses[4]).call() 162 | selectors = 163 | [].concat( 164 | selectors.slice(-5, -4), 165 | selectors.slice(1, 4), 166 | selectors.slice(-4, -2), 167 | selectors.slice(6, -5) 168 | ) 169 | assert.sameMembers(result, selectors) 170 | }) 171 | 172 | it('should remove some test1 functions', async () => { 173 | let selectors = getSelectors(test1Facet) 174 | let removeSelectors = [].concat(selectors.slice(1, 2), selectors.slice(8, 10)) 175 | result = await diamondLoupeFacet.methods.facetFunctionSelectors(addresses[3]).call() 176 | result = await diamondCutFacet.methods 177 | .diamondCut([[zeroAddress, FacetCutAction.Remove, removeSelectors]], zeroAddress, '0x') 178 | .send({ from: web3.eth.defaultAccount, gas: 6000000 }) 179 | result = await diamondLoupeFacet.methods.facetFunctionSelectors(addresses[3]).call() 180 | selectors = [].concat(selectors.slice(0, 1), selectors.slice(2, 8), selectors.slice(10)) 181 | assert.sameMembers(result, selectors) 182 | }) 183 | 184 | it('remove all functions and facets accept diamondCut and facets', async () => { 185 | let removeSelectors = [] 186 | let facets = await diamondLoupeFacet.methods.facets().call() 187 | for (let i = 1; i < facets.length; i++) { 188 | removeSelectors.push(...facets[i].functionSelectors) 189 | } 190 | // remove the facets function 191 | removeItem(removeSelectors, '0x7a0ed627') 192 | 193 | result = await diamondCutFacet.methods 194 | .diamondCut([[zeroAddress, FacetCutAction.Remove, removeSelectors]], zeroAddress, '0x') 195 | .send({ from: web3.eth.defaultAccount, gas: 6000000 }) 196 | facets = await diamondLoupeFacet.methods.facets().call() 197 | assert.equal(facets.length, 2) 198 | assert.equal(facets[0][0], addresses[0]) 199 | assert.sameMembers(facets[0][1], ['0x1f931c1c']) 200 | assert.equal(facets[1][0], addresses[1]) 201 | assert.sameMembers(facets[1][1], ['0x7a0ed627']) 202 | }) 203 | 204 | it('add most functions and facets', async () => { 205 | const diamondCut = [] 206 | const selectors = getSelectors(DiamondLoupeFacet) 207 | removeItem(selectors, '0x7a0ed627') 208 | selectors.pop() // remove supportsInterface which will be added later 209 | diamondCut.push([addresses[1], FacetCutAction.Add, selectors]) 210 | diamondCut.push([addresses[2], FacetCutAction.Add, getSelectors(OwnershipFacet)]) 211 | diamondCut.push([addresses[3], FacetCutAction.Add, getSelectors(test1Facet)]) 212 | diamondCut.push([addresses[4], FacetCutAction.Add, getSelectors(test2Facet)]) 213 | result = await diamondCutFacet.methods 214 | .diamondCut(diamondCut, zeroAddress, '0x') 215 | .send({ from: web3.eth.defaultAccount, gas: 6000000 }) 216 | const facets = await diamondLoupeFacet.methods.facets().call() 217 | const facetAddresses = await diamondLoupeFacet.methods.facetAddresses().call() 218 | assert.equal(facetAddresses.length, 5) 219 | assert.equal(facets.length, 5) 220 | assert.sameMembers(facetAddresses, addresses) 221 | assert.equal(facets[0][0], facetAddresses[0], 'first facet') 222 | assert.equal(facets[1][0], facetAddresses[1], 'second facet') 223 | assert.equal(facets[2][0], facetAddresses[2], 'third facet') 224 | assert.equal(facets[3][0], facetAddresses[3], 'fourth facet') 225 | assert.equal(facets[4][0], facetAddresses[4], 'fifth facet') 226 | assert.sameMembers(facets[findPositionInFacets(addresses[0], facets)][1], getSelectors(DiamondCutFacet)) 227 | assert.sameMembers(facets[findPositionInFacets(addresses[1], facets)][1], removeItem(getSelectors(DiamondLoupeFacet), '0x01ffc9a7')) 228 | assert.sameMembers(facets[findPositionInFacets(addresses[2], facets)][1], getSelectors(OwnershipFacet)) 229 | assert.sameMembers(facets[findPositionInFacets(addresses[3], facets)][1], getSelectors(test1Facet)) 230 | assert.sameMembers(facets[findPositionInFacets(addresses[4], facets)][1], getSelectors(test2Facet)) 231 | }) 232 | }) 233 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Use this file to configure your truffle project. It's seeded with some 3 | * common settings for different networks and features like migrations, 4 | * compilation and testing. Uncomment the ones you need or modify 5 | * them to suit your project as necessary. 6 | * 7 | * More information about configuration can be found at: 8 | * 9 | * truffleframework.com/docs/advanced/configuration 10 | * 11 | * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) 12 | * to sign your transactions before they're sent to a remote public node. Infura accounts 13 | * are available for free at: infura.io/register. 14 | * 15 | * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate 16 | * public/private key pairs. If you're publishing your code to GitHub make sure you load this 17 | * phrase from a file you've .gitignored so it doesn't accidentally become public. 18 | * 19 | */ 20 | 21 | // const HDWalletProvider = require('@truffle/hdwallet-provider'); 22 | // const infuraKey = "fj4jll3k....."; 23 | // 24 | // const fs = require('fs'); 25 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 26 | 27 | module.exports = { 28 | /** 29 | * Networks define how you connect to your ethereum client and let you set the 30 | * defaults web3 uses to send transactions. If you don't specify one truffle 31 | * will spin up a development blockchain for you on port 9545 when you 32 | * run `develop` or `test`. You can ask a truffle command to use a specific 33 | * network from the command line, e.g 34 | * 35 | * $ truffle test --network 36 | */ 37 | 38 | networks: { 39 | // Useful for testing. The `development` name is special - truffle uses it by default 40 | // if it's defined here and no other network is specified at the command line. 41 | // You should run a client (like ganache-cli, geth or parity) in a separate terminal 42 | // tab if you use this network and you must also set the `host`, `port` and `network_id` 43 | // options below to some value. 44 | // 45 | development: { 46 | host: '127.0.0.1', // Localhost (default: none) 47 | port: 8545, // Standard Ethereum port (default: none) 48 | network_id: '*' // Any network (default: none) 49 | } 50 | 51 | // Another network with more advanced options... 52 | // advanced: { 53 | // port: 8777, // Custom port 54 | // network_id: 1342, // Custom network 55 | // gas: 8500000, // Gas sent with each transaction (default: ~6700000) 56 | // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) 57 | // from:
, // Account to send txs from (default: accounts[0]) 58 | // websockets: true // Enable EventEmitter interface for web3 (default: false) 59 | // }, 60 | 61 | // Useful for deploying to a public network. 62 | // NB: It's important to wrap the provider as a function. 63 | // ropsten: { 64 | // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), 65 | // network_id: 3, // Ropsten's id 66 | // gas: 5500000, // Ropsten has a lower block limit than mainnet 67 | // confirmations: 2, // # of confs to wait between deployments. (default: 0) 68 | // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) 69 | // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) 70 | // }, 71 | 72 | // Useful for private networks 73 | // private: { 74 | // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), 75 | // network_id: 2111, // This network is yours, in the cloud. 76 | // production: true // Treats this network as if it was a public net. (default: false) 77 | // } 78 | }, 79 | 80 | // Set default mocha options here, use special reporters etc. 81 | mocha: { 82 | // reporter: 'eth-gas-reporter' 83 | // timeout: 100000 84 | }, 85 | 86 | // Configure your compilers 87 | compilers: { 88 | solc: { 89 | version: '0.7.6', // Fetch exact version from solc-bin (default: truffle's version) 90 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 91 | settings: { // See the solidity docs for advice about optimization and evmVersion 92 | optimizer: { 93 | enabled: true, 94 | runs: 9000000 95 | } 96 | // evmVersion: "byzantium" 97 | } 98 | } 99 | } 100 | } 101 | --------------------------------------------------------------------------------