├── .gitignore ├── LICENSE ├── README.md ├── ancient-proxy ├── .gitattributes ├── .gitignore ├── contracts │ ├── BusinessLogicV1.sol │ ├── BusinessLogicV2.sol │ ├── Dispatcher.sol │ └── Upgradeable.sol └── tests │ ├── conftest.py │ └── test_bl_v1.py ├── diamond-1-brownie ├── .gitattributes ├── .gitignore ├── contracts │ ├── Diamond.sol │ ├── facets │ │ ├── DiamondCutFacet.sol │ │ ├── DiamondLoupeFacet.sol │ │ ├── OwnershipFacet.sol │ │ ├── Test1Facet.sol │ │ └── Test2Facet.sol │ ├── interfaces │ │ ├── IDiamondCut.sol │ │ ├── IDiamondLoupe.sol │ │ ├── IERC165.sol │ │ └── IERC173.sol │ ├── libraries │ │ └── LibDiamond.sol │ └── upgradeInitializers │ │ └── DiamondInit.sol ├── scripts │ ├── deploy.py │ └── helpers.py └── tests │ ├── conftest.py │ ├── diamond_basics_test.py │ ├── diamond_cut_test.py │ └── diamond_cut_test1_test2_test.py ├── diamond-2-brownie ├── .gitattributes ├── .gitignore ├── contracts │ ├── Diamond.sol │ ├── facets │ │ ├── DiamondCutFacet.sol │ │ ├── DiamondLoupeFacet.sol │ │ ├── OwnershipFacet.sol │ │ ├── Test1Facet.sol │ │ └── Test2Facet.sol │ ├── interfaces │ │ ├── IDiamondCut.sol │ │ ├── IDiamondLoupe.sol │ │ ├── IERC165.sol │ │ └── IERC173.sol │ ├── libraries │ │ └── LibDiamond.sol │ └── upgradeInitializers │ │ └── DiamondInit.sol ├── scripts │ ├── deploy.py │ └── helpers.py └── tests │ ├── conftest.py │ ├── diamond_basics_test.py │ ├── diamond_cut_test.py │ └── diamond_cut_test1_test2_test.py ├── diamond-3-brownie ├── .gitattributes ├── .gitignore ├── contracts │ ├── Diamond.sol │ ├── facets │ │ ├── DiamondCutFacet.sol │ │ ├── DiamondLoupeFacet.sol │ │ ├── OwnershipFacet.sol │ │ ├── Test1Facet.sol │ │ └── Test2Facet.sol │ ├── interfaces │ │ ├── IDiamondCut.sol │ │ ├── IDiamondLoupe.sol │ │ ├── IERC165.sol │ │ └── IERC173.sol │ ├── libraries │ │ └── LibDiamond.sol │ └── upgradeInitializers │ │ └── DiamondInit.sol ├── scripts │ ├── deploy.py │ └── helpers.py └── tests │ ├── conftest.py │ ├── diamond_basics_test.py │ ├── diamond_cut_test.py │ └── diamond_cut_test1_test2_test.py └── eternal-storage ├── .gitattributes ├── .gitignore ├── contracts ├── EternalStorage.sol ├── SocialLib.sol ├── SocialLibV2.sol ├── SocialSite.sol └── SocialSiteV2.sol └── tests ├── conftest.py ├── test_social_site.py └── test_social_site_v2.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Agent FUD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # evm-smart-contract-upgrade-techniques 2 | Simple eth-brownie projects to demonstrate different smart contract upgrade techniques 3 | 4 | To see how they work first install [eth-brownie](https://github.com/eth-brownie/brownie), then clone the repository. After that enter directories and run ***brownie test -v*** By examining the tests you can get an idea how to work with these upgrade techniques. 5 | 6 | ## [Ancient Proxy](ancient-proxy) 7 | This is a modernized implementation of [Nick Johnson](https://gist.github.com/Arachnid)'s [upgradeable.sol](https://gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f), the grandfather of all the upgradeability/proxy patterns. 8 | 9 | ## [Eternal Storage](eternal-storage) 10 | Based on a [blog post](https://medium.com/colony/writing-upgradeable-contracts-in-solidity-6743f0eecc88) of [Elena Gesheva](https://medium.com/@elena_gesheva) 11 | 12 | ## Diamond Pattern eip-2535 13 | 14 | These are examples of [Nick Mudge](https://github.com/mudgen) [eip-2535](https://eips.ethereum.org/EIPS/eip-2535) diamond reference implementation with [eth-brownie](https://github.com/eth-brownie/brownie) for python lovers. In the three diamond directory the tests are the same, smart contracts are different. 15 | 16 | [diamond-1-brownie](diamond-1-brownie) 17 | 18 | [diamond-2-brownie](diamond-2-brownie) 19 | 20 | [diamond-3-brownie](diamond-3-brownie) 21 | 22 | To see gas consumption, run ***brownie test --gas*** in each diamond directory. -------------------------------------------------------------------------------- /ancient-proxy/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.vy linguist-language=Python 3 | -------------------------------------------------------------------------------- /ancient-proxy/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .env 3 | .history 4 | .hypothesis/ 5 | build/ 6 | reports/ 7 | -------------------------------------------------------------------------------- /ancient-proxy/contracts/BusinessLogicV1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "./Upgradeable.sol"; 6 | 7 | contract BusinessLogicV1 is Upgradeable { 8 | 9 | uint _value; 10 | 11 | function initialize() override public { 12 | _sizes[bytes4(keccak256("getUint()"))] = 32; 13 | } 14 | 15 | function getUint() public view returns (uint) { 16 | return _value; 17 | } 18 | 19 | function setUint(uint value) public { 20 | _value = value; 21 | } 22 | } -------------------------------------------------------------------------------- /ancient-proxy/contracts/BusinessLogicV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "./Upgradeable.sol"; 6 | 7 | contract BusinessLogicV2 is Upgradeable { 8 | 9 | uint _value; 10 | 11 | function initialize() override public { 12 | _sizes[bytes4(keccak256("getUint()"))] = 32; 13 | } 14 | 15 | function getUint() public view returns (uint) { 16 | return _value * 2; 17 | } 18 | 19 | function setUint(uint value) public { 20 | _value = value; 21 | } 22 | } -------------------------------------------------------------------------------- /ancient-proxy/contracts/Dispatcher.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "./Upgradeable.sol"; 6 | 7 | contract Dispatcher is Upgradeable { 8 | 9 | constructor(address target) { 10 | replace(target); 11 | } 12 | 13 | function initialize() override public pure { 14 | // Should only be called by on target contracts, not on the dispatcher 15 | assert(false); 16 | } 17 | 18 | fallback() external { 19 | bytes4 sig; 20 | assembly { sig := calldataload(0) } 21 | uint len = _sizes[sig]; 22 | address target = destination; 23 | 24 | assembly { 25 | calldatacopy(0x0, 0x0, calldatasize()) 26 | let result := delegatecall(sub(gas(), 10000), target, 0x0, calldatasize(), 0, len) 27 | return(0, len) //we throw away any return data 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ancient-proxy/contracts/Upgradeable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | abstract contract Upgradeable { 6 | 7 | mapping(bytes4 => uint32) _sizes; 8 | 9 | address destination; 10 | 11 | function initialize() virtual public ; 12 | 13 | function replace(address _destination) public { 14 | destination = _destination; 15 | _destination.delegatecall(abi.encodeWithSelector(bytes4(keccak256("initialize()")))); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ancient-proxy/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from brownie import Contract 4 | from brownie import Dispatcher, BusinessLogicV1 5 | 6 | 7 | @pytest.fixture(scope="module") 8 | def bl_v1(accounts): 9 | owner = accounts[0] 10 | blv1 = BusinessLogicV1.deploy({"from": owner}) 11 | disp = Dispatcher.deploy(blv1.address, {"from": owner}) 12 | 13 | # blv1 runs on disp address 14 | return Contract.from_abi("BusinessLogicV1", disp.address, blv1.abi) 15 | -------------------------------------------------------------------------------- /ancient-proxy/tests/test_bl_v1.py: -------------------------------------------------------------------------------- 1 | def test_business_logic_v1_works(accounts, bl_v1): 2 | owner = accounts[0] 3 | assert bl_v1.getUint() == 0 4 | bl_v1.setUint(131, {"from": owner}) 5 | assert bl_v1.getUint() == 131 6 | 7 | 8 | def test_replace_business_logic_works(accounts, bl_v1): 9 | pass 10 | -------------------------------------------------------------------------------- /diamond-1-brownie/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.vy linguist-language=Python 3 | -------------------------------------------------------------------------------- /diamond-1-brownie/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .env 3 | .history 4 | .hypothesis/ 5 | build/ 6 | reports/ 7 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/Diamond.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | * 8 | * Implementation of a diamond. 9 | /******************************************************************************/ 10 | 11 | import { LibDiamond } from "./libraries/LibDiamond.sol"; 12 | import { IDiamondCut } from "./interfaces/IDiamondCut.sol"; 13 | 14 | contract Diamond { 15 | 16 | constructor(address _contractOwner, address _diamondCutFacet) payable { 17 | LibDiamond.setContractOwner(_contractOwner); 18 | 19 | // Add the diamondCut external function from the diamondCutFacet 20 | IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); 21 | bytes4[] memory functionSelectors = new bytes4[](1); 22 | functionSelectors[0] = IDiamondCut.diamondCut.selector; 23 | cut[0] = IDiamondCut.FacetCut({ 24 | facetAddress: _diamondCutFacet, 25 | action: IDiamondCut.FacetCutAction.Add, 26 | functionSelectors: functionSelectors 27 | }); 28 | LibDiamond.diamondCut(cut, address(0), ""); 29 | } 30 | 31 | // Find facet for function that is called and execute the 32 | // function if a facet is found and return any value. 33 | fallback() external payable { 34 | LibDiamond.DiamondStorage storage ds; 35 | bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; 36 | // get diamond storage 37 | assembly { 38 | ds.slot := position 39 | } 40 | // get facet from function selector 41 | address facet = ds.facetAddressAndSelectorPosition[msg.sig].facetAddress; 42 | require(facet != address(0), "Diamond: Function does not exist"); 43 | // Execute external function from facet using delegatecall and return any value. 44 | assembly { 45 | // copy function selector and any arguments 46 | calldatacopy(0, 0, calldatasize()) 47 | // execute function call using the facet 48 | let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) 49 | // get any return value 50 | returndatacopy(0, 0, returndatasize()) 51 | // return any return value or error back to the caller 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 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/facets/DiamondCutFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; 10 | import { LibDiamond } from "../libraries/LibDiamond.sol"; 11 | 12 | contract DiamondCutFacet is IDiamondCut { 13 | /// @notice Add/replace/remove any number of functions and optionally execute 14 | /// a function with delegatecall 15 | /// @param _diamondCut Contains the facet addresses and function selectors 16 | /// @param _init The address of the contract or facet to execute _calldata 17 | /// @param _calldata A function call, including function selector and arguments 18 | /// _calldata is executed with delegatecall on _init 19 | function diamondCut( 20 | FacetCut[] calldata _diamondCut, 21 | address _init, 22 | bytes calldata _calldata 23 | ) external override { 24 | LibDiamond.enforceIsContractOwner(); 25 | LibDiamond.diamondCut(_diamondCut, _init, _calldata); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/facets/DiamondLoupeFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | /******************************************************************************\ 4 | * Author: Nick Mudge (https://twitter.com/mudgen) 5 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 6 | /******************************************************************************/ 7 | 8 | import { LibDiamond } from "../libraries/LibDiamond.sol"; 9 | import { IDiamondLoupe } from "../interfaces/IDiamondLoupe.sol"; 10 | import { IERC165 } from "../interfaces/IERC165.sol"; 11 | 12 | contract DiamondLoupeFacet is IDiamondLoupe, IERC165 { 13 | // Diamond Loupe Functions 14 | //////////////////////////////////////////////////////////////////// 15 | /// These functions are expected to be called frequently by tools. 16 | // 17 | // struct Facet { 18 | // address facetAddress; 19 | // bytes4[] functionSelectors; 20 | // } 21 | /// @notice Gets all facets and their selectors. 22 | /// @return facets_ Facet 23 | function facets() external override view returns (Facet[] memory facets_) { 24 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 25 | uint256 selectorCount = ds.selectors.length; 26 | // create an array set to the maximum size possible 27 | facets_ = new Facet[](selectorCount); 28 | // create an array for counting the number of selectors for each facet 29 | uint8[] memory numFacetSelectors = new uint8[](selectorCount); 30 | // total number of facets 31 | uint256 numFacets; 32 | // loop through function selectors 33 | for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) { 34 | bytes4 selector = ds.selectors[selectorIndex]; 35 | address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress; 36 | bool continueLoop = false; 37 | // find the functionSelectors array for selector and add selector to it 38 | for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { 39 | if (facets_[facetIndex].facetAddress == facetAddress_) { 40 | facets_[facetIndex].functionSelectors[numFacetSelectors[facetIndex]] = selector; 41 | // probably will never have more than 256 functions from one facet contract 42 | require(numFacetSelectors[facetIndex] < 255); 43 | numFacetSelectors[facetIndex]++; 44 | continueLoop = true; 45 | break; 46 | } 47 | } 48 | // if functionSelectors array exists for selector then continue loop 49 | if (continueLoop) { 50 | continueLoop = false; 51 | continue; 52 | } 53 | // create a new functionSelectors array for selector 54 | facets_[numFacets].facetAddress = facetAddress_; 55 | facets_[numFacets].functionSelectors = new bytes4[](selectorCount); 56 | facets_[numFacets].functionSelectors[0] = selector; 57 | numFacetSelectors[numFacets] = 1; 58 | numFacets++; 59 | } 60 | for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { 61 | uint256 numSelectors = numFacetSelectors[facetIndex]; 62 | bytes4[] memory selectors = facets_[facetIndex].functionSelectors; 63 | // setting the number of selectors 64 | assembly { 65 | mstore(selectors, numSelectors) 66 | } 67 | } 68 | // setting the number of facets 69 | assembly { 70 | mstore(facets_, numFacets) 71 | } 72 | } 73 | 74 | /// @notice Gets all the function selectors supported by a specific facet. 75 | /// @param _facet The facet address. 76 | /// @return _facetFunctionSelectors The selectors associated with a facet address. 77 | function facetFunctionSelectors(address _facet) external override view returns (bytes4[] memory _facetFunctionSelectors) { 78 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 79 | uint256 selectorCount = ds.selectors.length; 80 | uint256 numSelectors; 81 | _facetFunctionSelectors = new bytes4[](selectorCount); 82 | // loop through function selectors 83 | for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) { 84 | bytes4 selector = ds.selectors[selectorIndex]; 85 | address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress; 86 | if (_facet == facetAddress_) { 87 | _facetFunctionSelectors[numSelectors] = selector; 88 | numSelectors++; 89 | } 90 | } 91 | // Set the number of selectors in the array 92 | assembly { 93 | mstore(_facetFunctionSelectors, numSelectors) 94 | } 95 | } 96 | 97 | /// @notice Get all the facet addresses used by a diamond. 98 | /// @return facetAddresses_ 99 | function facetAddresses() external override view returns (address[] memory facetAddresses_) { 100 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 101 | uint256 selectorCount = ds.selectors.length; 102 | // create an array set to the maximum size possible 103 | facetAddresses_ = new address[](selectorCount); 104 | uint256 numFacets; 105 | // loop through function selectors 106 | for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) { 107 | bytes4 selector = ds.selectors[selectorIndex]; 108 | address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress; 109 | bool continueLoop = false; 110 | // see if we have collected the address already and break out of loop if we have 111 | for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { 112 | if (facetAddress_ == facetAddresses_[facetIndex]) { 113 | continueLoop = true; 114 | break; 115 | } 116 | } 117 | // continue loop if we already have the address 118 | if (continueLoop) { 119 | continueLoop = false; 120 | continue; 121 | } 122 | // include address 123 | facetAddresses_[numFacets] = facetAddress_; 124 | numFacets++; 125 | } 126 | // Set the number of facet addresses in the array 127 | assembly { 128 | mstore(facetAddresses_, numFacets) 129 | } 130 | } 131 | 132 | /// @notice Gets the facet address that supports the given selector. 133 | /// @dev If facet is not found return address(0). 134 | /// @param _functionSelector The function selector. 135 | /// @return facetAddress_ The facet address. 136 | function facetAddress(bytes4 _functionSelector) external override view returns (address facetAddress_) { 137 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 138 | facetAddress_ = ds.facetAddressAndSelectorPosition[_functionSelector].facetAddress; 139 | } 140 | 141 | // This implements ERC-165. 142 | function supportsInterface(bytes4 _interfaceId) external override view returns (bool) { 143 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 144 | return ds.supportedInterfaces[_interfaceId]; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/facets/OwnershipFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { LibDiamond } from "../libraries/LibDiamond.sol"; 5 | import { IERC173 } from "../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 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/facets/Test1Facet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Test1Facet { 5 | event TestEvent(address something); 6 | 7 | function test1Func1() external {} 8 | 9 | function test1Func2() external {} 10 | 11 | function test1Func3() external {} 12 | 13 | function test1Func4() external {} 14 | 15 | function test1Func5() external {} 16 | 17 | function test1Func6() external {} 18 | 19 | function test1Func7() external {} 20 | 21 | function test1Func8() external {} 22 | 23 | function test1Func9() external {} 24 | 25 | function test1Func10() external {} 26 | 27 | function test1Func11() external {} 28 | 29 | function test1Func12() external {} 30 | 31 | function test1Func13() external {} 32 | 33 | function test1Func14() external {} 34 | 35 | function test1Func15() external {} 36 | 37 | function test1Func16() external {} 38 | 39 | function test1Func17() external {} 40 | 41 | function test1Func18() external {} 42 | 43 | function test1Func19() external {} 44 | 45 | function test1Func20() external {} 46 | 47 | function supportsInterface(bytes4 _interfaceID) external view returns (bool) {} 48 | } 49 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/facets/Test2Facet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Test2Facet { 5 | function test2Func1() external {} 6 | 7 | function test2Func2() external {} 8 | 9 | function test2Func3() external {} 10 | 11 | function test2Func4() external {} 12 | 13 | function test2Func5() external {} 14 | 15 | function test2Func6() external {} 16 | 17 | function test2Func7() external {} 18 | 19 | function test2Func8() external {} 20 | 21 | function test2Func9() external {} 22 | 23 | function test2Func10() external {} 24 | 25 | function test2Func11() external {} 26 | 27 | function test2Func12() external {} 28 | 29 | function test2Func13() external {} 30 | 31 | function test2Func14() external {} 32 | 33 | function test2Func15() external {} 34 | 35 | function test2Func16() external {} 36 | 37 | function test2Func17() external {} 38 | 39 | function test2Func18() external {} 40 | 41 | function test2Func19() external {} 42 | 43 | function test2Func20() external {} 44 | } 45 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/interfaces/IDiamondCut.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | interface IDiamondCut { 10 | enum FacetCutAction {Add, Replace, Remove} 11 | // Add=0, Replace=1, Remove=2 12 | 13 | struct FacetCut { 14 | address facetAddress; 15 | FacetCutAction action; 16 | bytes4[] functionSelectors; 17 | } 18 | 19 | /// @notice Add/replace/remove any number of functions and optionally execute 20 | /// a function with delegatecall 21 | /// @param _diamondCut Contains the facet addresses and function selectors 22 | /// @param _init The address of the contract or facet to execute _calldata 23 | /// @param _calldata A function call, including function selector and arguments 24 | /// _calldata is executed with delegatecall on _init 25 | function diamondCut( 26 | FacetCut[] calldata _diamondCut, 27 | address _init, 28 | bytes calldata _calldata 29 | ) external; 30 | 31 | event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); 32 | } 33 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/interfaces/IDiamondLoupe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | // A loupe is a small magnifying glass used to look at diamonds. 10 | // These functions look at diamonds 11 | interface IDiamondLoupe { 12 | /// These functions are expected to be called frequently 13 | /// by tools. 14 | 15 | struct Facet { 16 | address facetAddress; 17 | bytes4[] functionSelectors; 18 | } 19 | 20 | /// @notice Gets all facet addresses and their four byte function selectors. 21 | /// @return facets_ Facet 22 | function facets() external view returns (Facet[] memory facets_); 23 | 24 | /// @notice Gets all the function selectors supported by a specific facet. 25 | /// @param _facet The facet address. 26 | /// @return facetFunctionSelectors_ 27 | function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); 28 | 29 | /// @notice Get all the facet addresses used by a diamond. 30 | /// @return facetAddresses_ 31 | function facetAddresses() external view returns (address[] memory facetAddresses_); 32 | 33 | /// @notice Gets the facet that supports the given selector. 34 | /// @dev If facet is not found return address(0). 35 | /// @param _functionSelector The function selector. 36 | /// @return facetAddress_ The facet address. 37 | function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_); 38 | } 39 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/interfaces/IERC165.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IERC165 { 5 | /// @notice Query if a contract implements an interface 6 | /// @param interfaceId The interface identifier, as specified in ERC-165 7 | /// @dev Interface identification is specified in ERC-165. This function 8 | /// uses less than 30,000 gas. 9 | /// @return `true` if the contract implements `interfaceID` and 10 | /// `interfaceID` is not 0xffffffff, `false` otherwise 11 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/interfaces/IERC173.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 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 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/libraries/LibDiamond.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; 9 | 10 | library LibDiamond { 11 | bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); 12 | 13 | struct FacetAddressAndSelectorPosition { 14 | address facetAddress; 15 | uint16 selectorPosition; 16 | } 17 | 18 | struct DiamondStorage { 19 | // function selector => facet address and selector position in selectors array 20 | mapping(bytes4 => FacetAddressAndSelectorPosition) facetAddressAndSelectorPosition; 21 | bytes4[] selectors; 22 | mapping(bytes4 => bool) supportedInterfaces; 23 | // owner of the contract 24 | address contractOwner; 25 | } 26 | 27 | function diamondStorage() internal pure returns (DiamondStorage storage ds) { 28 | bytes32 position = DIAMOND_STORAGE_POSITION; 29 | assembly { 30 | ds.slot := position 31 | } 32 | } 33 | 34 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 35 | 36 | function setContractOwner(address _newOwner) internal { 37 | DiamondStorage storage ds = diamondStorage(); 38 | address previousOwner = ds.contractOwner; 39 | ds.contractOwner = _newOwner; 40 | emit OwnershipTransferred(previousOwner, _newOwner); 41 | } 42 | 43 | function contractOwner() internal view returns (address contractOwner_) { 44 | contractOwner_ = diamondStorage().contractOwner; 45 | } 46 | 47 | function enforceIsContractOwner() internal view { 48 | require(msg.sender == diamondStorage().contractOwner, "LibDiamond: Must be contract owner"); 49 | } 50 | 51 | event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata); 52 | 53 | // Internal function version of diamondCut 54 | function diamondCut( 55 | IDiamondCut.FacetCut[] memory _diamondCut, 56 | address _init, 57 | bytes memory _calldata 58 | ) internal { 59 | for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) { 60 | IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action; 61 | if (action == IDiamondCut.FacetCutAction.Add) { 62 | addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); 63 | } else if (action == IDiamondCut.FacetCutAction.Replace) { 64 | replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); 65 | } else if (action == IDiamondCut.FacetCutAction.Remove) { 66 | removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); 67 | } else { 68 | revert("LibDiamondCut: Incorrect FacetCutAction"); 69 | } 70 | } 71 | emit DiamondCut(_diamondCut, _init, _calldata); 72 | initializeDiamondCut(_init, _calldata); 73 | } 74 | 75 | function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { 76 | require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); 77 | DiamondStorage storage ds = diamondStorage(); 78 | uint16 selectorCount = uint16(ds.selectors.length); 79 | require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); 80 | enforceHasContractCode(_facetAddress, "LibDiamondCut: Add facet has no code"); 81 | for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { 82 | bytes4 selector = _functionSelectors[selectorIndex]; 83 | address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector].facetAddress; 84 | require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists"); 85 | ds.facetAddressAndSelectorPosition[selector] = FacetAddressAndSelectorPosition(_facetAddress, selectorCount); 86 | ds.selectors.push(selector); 87 | selectorCount++; 88 | } 89 | } 90 | 91 | function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { 92 | require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); 93 | DiamondStorage storage ds = diamondStorage(); 94 | require(_facetAddress != address(0), "LibDiamondCut: Replace facet can't be address(0)"); 95 | enforceHasContractCode(_facetAddress, "LibDiamondCut: Replace facet has no code"); 96 | for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { 97 | bytes4 selector = _functionSelectors[selectorIndex]; 98 | address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector].facetAddress; 99 | // can't replace immutable functions -- functions defined directly in the diamond 100 | require(oldFacetAddress != address(this), "LibDiamondCut: Can't replace immutable function"); 101 | require(oldFacetAddress != _facetAddress, "LibDiamondCut: Can't replace function with same function"); 102 | require(oldFacetAddress != address(0), "LibDiamondCut: Can't replace function that doesn't exist"); 103 | // replace old facet address 104 | ds.facetAddressAndSelectorPosition[selector].facetAddress = _facetAddress; 105 | } 106 | } 107 | 108 | function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { 109 | require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); 110 | DiamondStorage storage ds = diamondStorage(); 111 | uint256 selectorCount = ds.selectors.length; 112 | require(_facetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)"); 113 | for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { 114 | bytes4 selector = _functionSelectors[selectorIndex]; 115 | FacetAddressAndSelectorPosition memory oldFacetAddressAndSelectorPosition = ds.facetAddressAndSelectorPosition[selector]; 116 | require(oldFacetAddressAndSelectorPosition.facetAddress != address(0), "LibDiamondCut: Can't remove function that doesn't exist"); 117 | // can't remove immutable functions -- functions defined directly in the diamond 118 | require(oldFacetAddressAndSelectorPosition.facetAddress != address(this), "LibDiamondCut: Can't remove immutable function."); 119 | // replace selector with last selector 120 | selectorCount--; 121 | if (oldFacetAddressAndSelectorPosition.selectorPosition != selectorCount) { 122 | bytes4 lastSelector = ds.selectors[selectorCount]; 123 | ds.selectors[oldFacetAddressAndSelectorPosition.selectorPosition] = lastSelector; 124 | ds.facetAddressAndSelectorPosition[lastSelector].selectorPosition = oldFacetAddressAndSelectorPosition.selectorPosition; 125 | } 126 | // delete last selector 127 | ds.selectors.pop(); 128 | delete ds.facetAddressAndSelectorPosition[selector]; 129 | } 130 | } 131 | 132 | function initializeDiamondCut(address _init, bytes memory _calldata) internal { 133 | if (_init == address(0)) { 134 | require(_calldata.length == 0, "LibDiamondCut: _init is address(0) but_calldata is not empty"); 135 | } else { 136 | require(_calldata.length > 0, "LibDiamondCut: _calldata is empty but _init is not address(0)"); 137 | if (_init != address(this)) { 138 | enforceHasContractCode(_init, "LibDiamondCut: _init address has no code"); 139 | } 140 | (bool success, bytes memory error) = _init.delegatecall(_calldata); 141 | if (!success) { 142 | if (error.length > 0) { 143 | // bubble up the error 144 | revert(string(error)); 145 | } else { 146 | revert("LibDiamondCut: _init function reverted"); 147 | } 148 | } 149 | } 150 | } 151 | 152 | function enforceHasContractCode(address _contract, string memory _errorMessage) internal view { 153 | uint256 contractSize; 154 | assembly { 155 | contractSize := extcodesize(_contract) 156 | } 157 | require(contractSize > 0, _errorMessage); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /diamond-1-brownie/contracts/upgradeInitializers/DiamondInit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | * 8 | * Implementation of a diamond. 9 | /******************************************************************************/ 10 | 11 | import {LibDiamond} from "../libraries/LibDiamond.sol"; 12 | import { IDiamondLoupe } from "../interfaces/IDiamondLoupe.sol"; 13 | import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; 14 | import { IERC173 } from "../interfaces/IERC173.sol"; 15 | import { IERC165 } from "../interfaces/IERC165.sol"; 16 | 17 | // It is exapected that this contract is customized if you want to deploy your diamond 18 | // with data from a deployment script. Use the init function to initialize state variables 19 | // of your diamond. Add parameters to the init funciton if you need to. 20 | 21 | contract DiamondInit { 22 | 23 | // You can add parameters to this function in order to pass in 24 | // data to set your own state variables 25 | function init() external { 26 | // adding ERC165 data 27 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 28 | ds.supportedInterfaces[type(IERC165).interfaceId] = true; 29 | ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true; 30 | ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true; 31 | ds.supportedInterfaces[type(IERC173).interfaceId] = true; 32 | 33 | // add your own state variables 34 | // EIP-2535 specifies that the `diamondCut` function takes two optional 35 | // arguments: address _init and bytes calldata _calldata 36 | // These arguments are used to execute an arbitrary function using delegatecall 37 | // in order to set state variables in the diamond during deployment or an upgrade 38 | // More info here: https://eips.ethereum.org/EIPS/eip-2535#diamond-interface 39 | } 40 | } -------------------------------------------------------------------------------- /diamond-1-brownie/scripts/deploy.py: -------------------------------------------------------------------------------- 1 | from brownie import Contract, accounts 2 | from brownie import ( 3 | Diamond, 4 | DiamondCutFacet, 5 | DiamondLoupeFacet, 6 | OwnershipFacet, 7 | DiamondInit, 8 | ) 9 | 10 | # from brownie import Test1Facet, Test2Facet 11 | from scripts.helpers import facetCutAction, getSelectors 12 | 13 | 14 | def main(): 15 | owner = accounts[0] 16 | 17 | diamondCutFacet = DiamondCutFacet.deploy({"from": owner}) 18 | diamondLoupeFacet = DiamondLoupeFacet.deploy({"from": owner}) 19 | ownershipFacet = OwnershipFacet.deploy({"from": owner}) 20 | diamondInit = DiamondInit.deploy({"from": owner}) 21 | diamond = Diamond.deploy(owner, diamondCutFacet.address, {"from": owner}) 22 | 23 | cut = [ 24 | [ 25 | diamondLoupeFacet.address, 26 | facetCutAction["Add"], 27 | getSelectors(DiamondLoupeFacet), 28 | ], 29 | [ownershipFacet.address, facetCutAction["Add"], getSelectors(OwnershipFacet)], 30 | ] 31 | 32 | # DiamondCutFacet at diamond.address 33 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 34 | initSelector = getSelectors(DiamondInit) 35 | 36 | diamondCut.diamondCut(cut, diamondInit.address, initSelector[0], {"from": owner}) 37 | -------------------------------------------------------------------------------- /diamond-1-brownie/scripts/helpers.py: -------------------------------------------------------------------------------- 1 | import web3 2 | 3 | facetCutAction = {"Add": 0, "Replace": 1, "Remove": 2} 4 | 5 | zeroAddress = "0x0000000000000000000000000000000000000000" 6 | 7 | 8 | def getSelectors(contract): 9 | return list(contract.signatures.values()) 10 | 11 | 12 | def hashFunctionSignature(function_signature_text): 13 | return web3.Web3.keccak(text=function_signature_text).hex()[0:10] 14 | -------------------------------------------------------------------------------- /diamond-1-brownie/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract 3 | from scripts.helpers import facetCutAction, getSelectors 4 | 5 | 6 | @pytest.fixture(scope="module") 7 | def deployed_contracts( 8 | accounts, 9 | Diamond, 10 | DiamondInit, 11 | DiamondCutFacet, 12 | DiamondLoupeFacet, 13 | OwnershipFacet, 14 | Test1Facet, 15 | Test2Facet, 16 | ): 17 | owner = accounts[0] 18 | 19 | diamondCutFacet = DiamondCutFacet.deploy({"from": owner}) 20 | diamondLoupeFacet = DiamondLoupeFacet.deploy({"from": owner}) 21 | ownershipFacet = OwnershipFacet.deploy({"from": owner}) 22 | test1Facet = Test1Facet.deploy({"from": owner}) 23 | test2Facet = Test2Facet.deploy({"from": owner}) 24 | 25 | diamondInit = DiamondInit.deploy({"from": owner}) 26 | diamond = Diamond.deploy(owner, diamondCutFacet.address, {"from": owner}) 27 | 28 | cut = [ 29 | [ 30 | diamondLoupeFacet.address, 31 | facetCutAction["Add"], 32 | getSelectors(DiamondLoupeFacet), 33 | ], 34 | [ownershipFacet.address, facetCutAction["Add"], getSelectors(OwnershipFacet)], 35 | ] 36 | 37 | # DiamondCutFacet at diamond.address 38 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 39 | 40 | # DiamondInit has only one function, init() 41 | initSelector = getSelectors(DiamondInit)[0] 42 | 43 | # Cutting the Diamond 44 | diamondCut.diamondCut(cut, diamondInit.address, initSelector, {"from": owner}) 45 | 46 | return ( 47 | diamond, 48 | diamondCutFacet, 49 | diamondLoupeFacet, 50 | ownershipFacet, 51 | test1Facet, 52 | test2Facet, 53 | ) 54 | -------------------------------------------------------------------------------- /diamond-1-brownie/tests/diamond_basics_test.py: -------------------------------------------------------------------------------- 1 | from scripts.helpers import getSelectors 2 | from brownie import Contract 3 | 4 | 5 | def test_contracts_deployed(deployed_contracts): 6 | for contract in deployed_contracts: 7 | assert len(contract.address) == 42 8 | 9 | 10 | def test_diamond_has_three_facets(deployed_contracts): 11 | diamond = deployed_contracts[0] 12 | diamondLoupeFacet = deployed_contracts[2] 13 | diamondLoupe = Contract.from_abi( 14 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 15 | ) 16 | assert len(diamondLoupe.facetAddresses()) == 3 17 | 18 | 19 | def test_facets_have_the_right_function_selectors(deployed_contracts): 20 | diamond = deployed_contracts[0] 21 | diamondLoupeFacet = deployed_contracts[2] 22 | diamondCutFacet = deployed_contracts[1] 23 | ownership = deployed_contracts[3] 24 | 25 | diamondLoupe = Contract.from_abi( 26 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 27 | ) 28 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 29 | 30 | facetAddresses = diamondLoupe.facetAddresses() 31 | 32 | selectors = getSelectors(diamondCut) 33 | result = diamondLoupe.facetFunctionSelectors(facetAddresses[0]) 34 | assert selectors == list(result) 35 | 36 | selectors = getSelectors(diamondLoupe) 37 | result = diamondLoupe.facetFunctionSelectors(facetAddresses[1]) 38 | assert selectors == list(result) 39 | 40 | selectors = getSelectors(ownership) 41 | result = diamondLoupe.facetFunctionSelectors(facetAddresses[2]) 42 | assert selectors == list(result) 43 | 44 | 45 | def test_selectors_associated_to_facets_correctly(deployed_contracts): 46 | diamond = deployed_contracts[0] 47 | diamondLoupeFacet = deployed_contracts[2] 48 | diamondLoupe = Contract.from_abi( 49 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 50 | ) 51 | facetAddresses = diamondLoupe.facetAddresses() 52 | assert diamondLoupe.facetAddress("0x1f931c1c") == facetAddresses[0] 53 | assert diamondLoupe.facetAddress("0xcdffacc6") == facetAddresses[1] 54 | assert diamondLoupe.facetAddress("0x01ffc9a7") == facetAddresses[1] 55 | assert diamondLoupe.facetAddress("0xf2fde38b") == facetAddresses[2] 56 | -------------------------------------------------------------------------------- /diamond-1-brownie/tests/diamond_cut_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from scripts.helpers import ( 3 | getSelectors, 4 | facetCutAction, 5 | zeroAddress, 6 | hashFunctionSignature, 7 | ) 8 | from brownie import Contract 9 | 10 | 11 | def test_add_test1facet_functions(accounts, deployed_contracts, Test1Facet): 12 | owner = accounts[0] 13 | diamond = deployed_contracts[0] 14 | diamondLoupeFacet = deployed_contracts[2] 15 | diamondCutFacet = deployed_contracts[1] 16 | test1Facet = deployed_contracts[4] 17 | 18 | diamondLoupe = Contract.from_abi( 19 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 20 | ) 21 | 22 | # removing supportsInterface(bytes4) from selector list 23 | supportsInterFaceFnSignature = "supportsInterface(bytes4)" 24 | selector = hashFunctionSignature(supportsInterFaceFnSignature) 25 | selectors = getSelectors(Test1Facet) 26 | selectors.remove(selector) 27 | 28 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 29 | 30 | cut = [[test1Facet.address, facetCutAction["Add"], selectors]] 31 | 32 | diamondCut.diamondCut( 33 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 34 | ) 35 | 36 | assert len(diamondLoupe.facetAddresses()) == 4 37 | 38 | 39 | def test_call_test1facet_functions(accounts, deployed_contracts, Test1Facet): 40 | owner = accounts[0] 41 | diamond = deployed_contracts[0] 42 | diamondLoupeFacet = deployed_contracts[2] 43 | test1 = deployed_contracts[4] 44 | test1AtDiamond = Contract.from_abi("Test1Facet", diamond.address, Test1Facet.abi) 45 | 46 | test1AtDiamond.test1Func3({"from": owner}) 47 | test1AtDiamond.test1Func19({"from": owner}) 48 | 49 | diamondLoupe = Contract.from_abi( 50 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 51 | ) 52 | assert len(diamondLoupe.facetFunctionSelectors(test1.address)) == 20 53 | 54 | 55 | def test_replace_supportsInterface_function(accounts, deployed_contracts): 56 | owner = accounts[0] 57 | diamond = deployed_contracts[0] 58 | diamondCutFacet = deployed_contracts[1] 59 | diamondLoupeFacet = deployed_contracts[2] 60 | test1 = deployed_contracts[4] 61 | 62 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 63 | diamondLoupe = Contract.from_abi( 64 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 65 | ) 66 | 67 | assert len(diamondLoupe.facetFunctionSelectors(test1.address)) == 20 68 | 69 | function_signature = str = "supportsInterface(bytes4)" 70 | selector = hashFunctionSignature(function_signature_text=function_signature) 71 | cut = [[test1.address, facetCutAction["Replace"], [selector]]] 72 | 73 | diamondCut.diamondCut( 74 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 75 | ) 76 | 77 | assert len(diamondLoupe.facetFunctionSelectors(test1.address)) == 21 78 | 79 | 80 | def test_add_test2facet_functions(accounts, deployed_contracts, Test2Facet): 81 | owner = accounts[0] 82 | diamond = deployed_contracts[0] 83 | diamondCutFacet = deployed_contracts[1] 84 | diamondLoupeFacet = deployed_contracts[2] 85 | test2 = deployed_contracts[5] 86 | 87 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 88 | diamondLoupe = Contract.from_abi( 89 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 90 | ) 91 | 92 | cut = [[test2.address, facetCutAction["Add"], getSelectors(Test2Facet)]] 93 | 94 | # Cut the Diamond 95 | diamondCut.diamondCut( 96 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 97 | ) 98 | 99 | selectors = getSelectors(test2) 100 | z = diamondLoupe.facetFunctionSelectors(test2.address) 101 | assert len(z) == 20 102 | assert selectors == list(z) 103 | 104 | 105 | def test_test2facet_call(accounts, deployed_contracts, Test2Facet): 106 | owner = accounts[0] 107 | diamond = deployed_contracts[0] 108 | test2AtDiamond = Contract.from_abi("Test2Facet", diamond.address, Test2Facet.abi) 109 | test2AtDiamond.test2Func9({"from": owner}) 110 | 111 | 112 | def test_remove_some_test2facet_functions(accounts, deployed_contracts): 113 | owner = accounts[0] 114 | diamond = deployed_contracts[0] 115 | diamondCutFacet = deployed_contracts[1] 116 | diamondLoupeFacet = deployed_contracts[2] 117 | test2 = deployed_contracts[5] 118 | 119 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 120 | diamondLoupe = Contract.from_abi( 121 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 122 | ) 123 | 124 | functions_to_remove = [ 125 | "test2Func1()", 126 | "test2Func5()", 127 | "test2Func6()", 128 | "test2Func19()", 129 | "test2Func20()", 130 | ] 131 | selectors_to_remove = [ 132 | hashFunctionSignature(fname) for fname in functions_to_remove 133 | ] 134 | 135 | cut = [[zeroAddress, facetCutAction["Remove"], selectors_to_remove]] 136 | 137 | # Cut the Diamond 138 | diamondCut.diamondCut( 139 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 140 | ) 141 | function_selectors = diamondLoupe.facetFunctionSelectors(test2.address) 142 | assert len(function_selectors) == 15 143 | for fsr in selectors_to_remove: 144 | if fsr in function_selectors: 145 | assert False 146 | 147 | 148 | def test_remove_some_test1facet_functions(accounts, deployed_contracts): 149 | owner = accounts[0] 150 | diamond = deployed_contracts[0] 151 | diamondCutFacet = deployed_contracts[1] 152 | diamondLoupeFacet = deployed_contracts[2] 153 | test1 = deployed_contracts[4] 154 | 155 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 156 | diamondLoupe = Contract.from_abi( 157 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 158 | ) 159 | 160 | functions_to_remove = [ 161 | "test1Func1()", 162 | "test1Func5()", 163 | "test1Func6()", 164 | "test1Func11()", 165 | "test1Func19()", 166 | "test1Func20()", 167 | ] 168 | selectors_to_remove = [ 169 | hashFunctionSignature(fname) for fname in functions_to_remove 170 | ] 171 | 172 | cut = [[zeroAddress, facetCutAction["Remove"], selectors_to_remove]] 173 | 174 | # Cut the Diamond 175 | diamondCut.diamondCut( 176 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 177 | ) 178 | function_selectors = diamondLoupe.facetFunctionSelectors(test1.address) 179 | assert len(function_selectors) == 15 180 | for fsr in selectors_to_remove: 181 | if fsr in function_selectors: 182 | assert False 183 | 184 | 185 | def test_remove_all_facets_and_functions_except_diamondcut_and_facets( 186 | accounts, deployed_contracts 187 | ): 188 | owner = accounts[0] 189 | diamond = deployed_contracts[0] 190 | diamondCutFacet = deployed_contracts[1] 191 | diamondLoupeFacet = deployed_contracts[2] 192 | 193 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 194 | diamondLoupe = Contract.from_abi( 195 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 196 | ) 197 | 198 | # get all the selectors 199 | facets_with_selectors = diamondLoupe.facets() 200 | all_selectors = [] 201 | for x in facets_with_selectors: 202 | all_selectors.extend(list(x[1])) 203 | 204 | # remove ['facets()', 'diamondCut((address,uint8,bytes4[])[],address,bytes)']) from selectors 205 | selectors_to_keep = [ 206 | hashFunctionSignature("facets()"), 207 | hashFunctionSignature("diamondCut((address,uint8,bytes4[])[],address,bytes)"), 208 | ] 209 | 210 | for s in selectors_to_keep: 211 | all_selectors.remove(s) 212 | 213 | # Cut the Diamond 214 | cut = [[zeroAddress, facetCutAction["Remove"], all_selectors]] 215 | 216 | # Cut the Diamond 217 | diamondCut.diamondCut( 218 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 219 | ) 220 | 221 | facets = diamondLoupe.facets() 222 | 223 | assert len(facets) == 2 224 | -------------------------------------------------------------------------------- /diamond-1-brownie/tests/diamond_cut_test1_test2_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract 3 | from scripts.helpers import ( 4 | hashFunctionSignature, 5 | getSelectors, 6 | zeroAddress, 7 | facetCutAction, 8 | ) 9 | 10 | 11 | def test_add_test1_and_test2_facet_functions(accounts, deployed_contracts): 12 | owner = accounts[0] 13 | 14 | diamond = deployed_contracts[0] 15 | diamondCutFacet = deployed_contracts[1] 16 | diamondLoupeFacet = deployed_contracts[2] 17 | test1Facet = deployed_contracts[4] 18 | test2Facet = deployed_contracts[5] 19 | 20 | test1FacetSelectors = getSelectors(test1Facet) 21 | test1FacetSelectors.remove(hashFunctionSignature("supportsInterface(bytes4)")) 22 | 23 | cut = [ 24 | [test1Facet.address, facetCutAction["Add"], test1FacetSelectors], 25 | [test2Facet.address, facetCutAction["Add"], getSelectors(test2Facet)], 26 | ] 27 | 28 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 29 | 30 | diamondCut.diamondCut( 31 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 32 | ) 33 | 34 | diamondLoupe = Contract.from_abi( 35 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 36 | ) 37 | 38 | facets = diamondLoupe.facets() 39 | 40 | assert len(facets) == 5 41 | -------------------------------------------------------------------------------- /diamond-2-brownie/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.vy linguist-language=Python 3 | -------------------------------------------------------------------------------- /diamond-2-brownie/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .env 3 | .history 4 | .hypothesis/ 5 | build/ 6 | reports/ 7 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/Diamond.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | * 8 | * Implementation of a diamond. 9 | /******************************************************************************/ 10 | 11 | import { LibDiamond } from "./libraries/LibDiamond.sol"; 12 | import { IDiamondCut } from "./interfaces/IDiamondCut.sol"; 13 | 14 | contract Diamond { 15 | 16 | constructor(address _contractOwner, address _diamondCutFacet) payable { 17 | LibDiamond.setContractOwner(_contractOwner); 18 | 19 | // Add the diamondCut external function from the diamondCutFacet 20 | IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); 21 | bytes4[] memory functionSelectors = new bytes4[](1); 22 | functionSelectors[0] = IDiamondCut.diamondCut.selector; 23 | cut[0] = IDiamondCut.FacetCut({ 24 | facetAddress: _diamondCutFacet, 25 | action: IDiamondCut.FacetCutAction.Add, 26 | functionSelectors: functionSelectors 27 | }); 28 | LibDiamond.diamondCut(cut, address(0), ""); 29 | } 30 | 31 | // Find facet for function that is called and execute the 32 | // function if a facet is found and return any value. 33 | fallback() external payable { 34 | LibDiamond.DiamondStorage storage ds; 35 | bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; 36 | // get diamond storage 37 | assembly { 38 | ds.slot := position 39 | } 40 | // get facet from function selector 41 | address facet = address(bytes20(ds.facets[msg.sig])); 42 | require(facet != address(0), "Diamond: Function does not exist"); 43 | // Execute external function from facet using delegatecall and return any value. 44 | assembly { 45 | // copy function selector and any arguments 46 | calldatacopy(0, 0, calldatasize()) 47 | // execute function call using the facet 48 | let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) 49 | // get any return value 50 | returndatacopy(0, 0, returndatasize()) 51 | // return any return value or error back to the caller 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 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/facets/DiamondCutFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; 10 | import { LibDiamond } from "../libraries/LibDiamond.sol"; 11 | 12 | contract DiamondCutFacet is IDiamondCut { 13 | /// @notice Add/replace/remove any number of functions and optionally execute 14 | /// a function with delegatecall 15 | /// @param _diamondCut Contains the facet addresses and function selectors 16 | /// @param _init The address of the contract or facet to execute _calldata 17 | /// @param _calldata A function call, including function selector and arguments 18 | /// _calldata is executed with delegatecall on _init 19 | function diamondCut( 20 | FacetCut[] calldata _diamondCut, 21 | address _init, 22 | bytes calldata _calldata 23 | ) external override { 24 | LibDiamond.enforceIsContractOwner(); 25 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 26 | uint256 originalSelectorCount = ds.selectorCount; 27 | uint256 selectorCount = originalSelectorCount; 28 | bytes32 selectorSlot; 29 | // Check if last selector slot is not full 30 | // "selectorCount & 7" is a gas efficient modulo by eight "selectorCount % 8" 31 | if (selectorCount & 7 > 0) { 32 | // get last selectorSlot 33 | // "selectorCount >> 3" is a gas efficient division by 8 "selectorCount / 8" 34 | selectorSlot = ds.selectorSlots[selectorCount >> 3]; 35 | } 36 | // loop through diamond cut 37 | for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) { 38 | (selectorCount, selectorSlot) = LibDiamond.addReplaceRemoveFacetSelectors( 39 | selectorCount, 40 | selectorSlot, 41 | _diamondCut[facetIndex].facetAddress, 42 | _diamondCut[facetIndex].action, 43 | _diamondCut[facetIndex].functionSelectors 44 | ); 45 | } 46 | if (selectorCount != originalSelectorCount) { 47 | ds.selectorCount = uint16(selectorCount); 48 | } 49 | // If last selector slot is not full 50 | // "selectorCount & 7" is a gas efficient modulo by eight "selectorCount % 8" 51 | if (selectorCount & 7 > 0) { 52 | // "selectorCount >> 3" is a gas efficient division by 8 "selectorCount / 8" 53 | ds.selectorSlots[selectorCount >> 3] = selectorSlot; 54 | } 55 | emit DiamondCut(_diamondCut, _init, _calldata); 56 | LibDiamond.initializeDiamondCut(_init, _calldata); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/facets/DiamondLoupeFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | /******************************************************************************\ 4 | * Author: Nick Mudge (https://twitter.com/mudgen) 5 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 6 | /******************************************************************************/ 7 | 8 | import { LibDiamond } from "../libraries/LibDiamond.sol"; 9 | import { IDiamondLoupe } from "../interfaces/IDiamondLoupe.sol"; 10 | import { IERC165 } from "../interfaces/IERC165.sol"; 11 | 12 | contract DiamondLoupeFacet is IDiamondLoupe, IERC165 { 13 | // Diamond Loupe Functions 14 | //////////////////////////////////////////////////////////////////// 15 | /// These functions are expected to be called frequently by tools. 16 | // 17 | // struct Facet { 18 | // address facetAddress; 19 | // bytes4[] functionSelectors; 20 | // } 21 | /// @notice Gets all facets and their selectors. 22 | /// @return facets_ Facet 23 | function facets() external override view returns (Facet[] memory facets_) { 24 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 25 | facets_ = new Facet[](ds.selectorCount); 26 | uint8[] memory numFacetSelectors = new uint8[](ds.selectorCount); 27 | uint256 numFacets; 28 | uint256 selectorIndex; 29 | // loop through function selectors 30 | for (uint256 slotIndex; selectorIndex < ds.selectorCount; slotIndex++) { 31 | bytes32 slot = ds.selectorSlots[slotIndex]; 32 | for (uint256 selectorSlotIndex; selectorSlotIndex < 8; selectorSlotIndex++) { 33 | selectorIndex++; 34 | if (selectorIndex > ds.selectorCount) { 35 | break; 36 | } 37 | bytes4 selector = bytes4(slot << (selectorSlotIndex << 5)); 38 | address facetAddress_ = address(bytes20(ds.facets[selector])); 39 | bool continueLoop = false; 40 | for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { 41 | if (facets_[facetIndex].facetAddress == facetAddress_) { 42 | facets_[facetIndex].functionSelectors[numFacetSelectors[facetIndex]] = selector; 43 | // probably will never have more than 256 functions from one facet contract 44 | require(numFacetSelectors[facetIndex] < 255); 45 | numFacetSelectors[facetIndex]++; 46 | continueLoop = true; 47 | break; 48 | } 49 | } 50 | if (continueLoop) { 51 | continueLoop = false; 52 | continue; 53 | } 54 | facets_[numFacets].facetAddress = facetAddress_; 55 | facets_[numFacets].functionSelectors = new bytes4[](ds.selectorCount); 56 | facets_[numFacets].functionSelectors[0] = selector; 57 | numFacetSelectors[numFacets] = 1; 58 | numFacets++; 59 | } 60 | } 61 | for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { 62 | uint256 numSelectors = numFacetSelectors[facetIndex]; 63 | bytes4[] memory selectors = facets_[facetIndex].functionSelectors; 64 | // setting the number of selectors 65 | assembly { 66 | mstore(selectors, numSelectors) 67 | } 68 | } 69 | // setting the number of facets 70 | assembly { 71 | mstore(facets_, numFacets) 72 | } 73 | } 74 | 75 | /// @notice Gets all the function selectors supported by a specific facet. 76 | /// @param _facet The facet address. 77 | /// @return _facetFunctionSelectors The selectors associated with a facet address. 78 | function facetFunctionSelectors(address _facet) external override view returns (bytes4[] memory _facetFunctionSelectors) { 79 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 80 | uint256 numSelectors; 81 | _facetFunctionSelectors = new bytes4[](ds.selectorCount); 82 | uint256 selectorIndex; 83 | // loop through function selectors 84 | for (uint256 slotIndex; selectorIndex < ds.selectorCount; slotIndex++) { 85 | bytes32 slot = ds.selectorSlots[slotIndex]; 86 | for (uint256 selectorSlotIndex; selectorSlotIndex < 8; selectorSlotIndex++) { 87 | selectorIndex++; 88 | if (selectorIndex > ds.selectorCount) { 89 | break; 90 | } 91 | bytes4 selector = bytes4(slot << (selectorSlotIndex << 5)); 92 | address facet = address(bytes20(ds.facets[selector])); 93 | if (_facet == facet) { 94 | _facetFunctionSelectors[numSelectors] = selector; 95 | numSelectors++; 96 | } 97 | } 98 | } 99 | // Set the number of selectors in the array 100 | assembly { 101 | mstore(_facetFunctionSelectors, numSelectors) 102 | } 103 | } 104 | 105 | /// @notice Get all the facet addresses used by a diamond. 106 | /// @return facetAddresses_ 107 | function facetAddresses() external override view returns (address[] memory facetAddresses_) { 108 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 109 | facetAddresses_ = new address[](ds.selectorCount); 110 | uint256 numFacets; 111 | uint256 selectorIndex; 112 | // loop through function selectors 113 | for (uint256 slotIndex; selectorIndex < ds.selectorCount; slotIndex++) { 114 | bytes32 slot = ds.selectorSlots[slotIndex]; 115 | for (uint256 selectorSlotIndex; selectorSlotIndex < 8; selectorSlotIndex++) { 116 | selectorIndex++; 117 | if (selectorIndex > ds.selectorCount) { 118 | break; 119 | } 120 | bytes4 selector = bytes4(slot << (selectorSlotIndex << 5)); 121 | address facetAddress_ = address(bytes20(ds.facets[selector])); 122 | bool continueLoop = false; 123 | for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { 124 | if (facetAddress_ == facetAddresses_[facetIndex]) { 125 | continueLoop = true; 126 | break; 127 | } 128 | } 129 | if (continueLoop) { 130 | continueLoop = false; 131 | continue; 132 | } 133 | facetAddresses_[numFacets] = facetAddress_; 134 | numFacets++; 135 | } 136 | } 137 | // Set the number of facet addresses in the array 138 | assembly { 139 | mstore(facetAddresses_, numFacets) 140 | } 141 | } 142 | 143 | /// @notice Gets the facet that supports the given selector. 144 | /// @dev If facet is not found return address(0). 145 | /// @param _functionSelector The function selector. 146 | /// @return facetAddress_ The facet address. 147 | function facetAddress(bytes4 _functionSelector) external override view returns (address facetAddress_) { 148 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 149 | facetAddress_ = address(bytes20(ds.facets[_functionSelector])); 150 | } 151 | 152 | // This implements ERC-165. 153 | function supportsInterface(bytes4 _interfaceId) external override view returns (bool) { 154 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 155 | return ds.supportedInterfaces[_interfaceId]; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/facets/OwnershipFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | import { LibDiamond } from "../libraries/LibDiamond.sol"; 5 | import { IERC173 } from "../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 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/facets/Test1Facet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | contract Test1Facet { 5 | event TestEvent(address something); 6 | 7 | function test1Func1() external {} 8 | 9 | function test1Func2() external {} 10 | 11 | function test1Func3() external {} 12 | 13 | function test1Func4() external {} 14 | 15 | function test1Func5() external {} 16 | 17 | function test1Func6() external {} 18 | 19 | function test1Func7() external {} 20 | 21 | function test1Func8() external {} 22 | 23 | function test1Func9() external {} 24 | 25 | function test1Func10() external {} 26 | 27 | function test1Func11() external {} 28 | 29 | function test1Func12() external {} 30 | 31 | function test1Func13() external {} 32 | 33 | function test1Func14() external {} 34 | 35 | function test1Func15() external {} 36 | 37 | function test1Func16() external {} 38 | 39 | function test1Func17() external {} 40 | 41 | function test1Func18() external {} 42 | 43 | function test1Func19() external {} 44 | 45 | function test1Func20() external {} 46 | 47 | function supportsInterface(bytes4 _interfaceID) external view returns (bool) {} 48 | } 49 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/facets/Test2Facet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | contract Test2Facet { 5 | function test2Func1() external {} 6 | 7 | function test2Func2() external {} 8 | 9 | function test2Func3() external {} 10 | 11 | function test2Func4() external {} 12 | 13 | function test2Func5() external {} 14 | 15 | function test2Func6() external {} 16 | 17 | function test2Func7() external {} 18 | 19 | function test2Func8() external {} 20 | 21 | function test2Func9() external {} 22 | 23 | function test2Func10() external {} 24 | 25 | function test2Func11() external {} 26 | 27 | function test2Func12() external {} 28 | 29 | function test2Func13() external {} 30 | 31 | function test2Func14() external {} 32 | 33 | function test2Func15() external {} 34 | 35 | function test2Func16() external {} 36 | 37 | function test2Func17() external {} 38 | 39 | function test2Func18() external {} 40 | 41 | function test2Func19() external {} 42 | 43 | function test2Func20() external {} 44 | } 45 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/interfaces/IDiamondCut.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | interface IDiamondCut { 10 | enum FacetCutAction {Add, Replace, Remove} 11 | // Add=0, Replace=1, Remove=2 12 | 13 | struct FacetCut { 14 | address facetAddress; 15 | FacetCutAction action; 16 | bytes4[] functionSelectors; 17 | } 18 | 19 | /// @notice Add/replace/remove any number of functions and optionally execute 20 | /// a function with delegatecall 21 | /// @param _diamondCut Contains the facet addresses and function selectors 22 | /// @param _init The address of the contract or facet to execute _calldata 23 | /// @param _calldata A function call, including function selector and arguments 24 | /// _calldata is executed with delegatecall on _init 25 | function diamondCut( 26 | FacetCut[] calldata _diamondCut, 27 | address _init, 28 | bytes calldata _calldata 29 | ) external; 30 | 31 | event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); 32 | } 33 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/interfaces/IDiamondLoupe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | // A loupe is a small magnifying glass used to look at diamonds. 10 | // These functions look at diamonds 11 | interface IDiamondLoupe { 12 | /// These functions are expected to be called frequently 13 | /// by tools. 14 | 15 | struct Facet { 16 | address facetAddress; 17 | bytes4[] functionSelectors; 18 | } 19 | 20 | /// @notice Gets all facet addresses and their four byte function selectors. 21 | /// @return facets_ Facet 22 | function facets() external view returns (Facet[] memory facets_); 23 | 24 | /// @notice Gets all the function selectors supported by a specific facet. 25 | /// @param _facet The facet address. 26 | /// @return facetFunctionSelectors_ 27 | function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); 28 | 29 | /// @notice Get all the facet addresses used by a diamond. 30 | /// @return facetAddresses_ 31 | function facetAddresses() external view returns (address[] memory facetAddresses_); 32 | 33 | /// @notice Gets the facet that supports the given selector. 34 | /// @dev If facet is not found return address(0). 35 | /// @param _functionSelector The function selector. 36 | /// @return facetAddress_ The facet address. 37 | function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_); 38 | } 39 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/interfaces/IERC165.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | interface IERC165 { 5 | /// @notice Query if a contract implements an interface 6 | /// @param interfaceId The interface identifier, as specified in ERC-165 7 | /// @dev Interface identification is specified in ERC-165. This function 8 | /// uses less than 30,000 gas. 9 | /// @return `true` if the contract implements `interfaceID` and 10 | /// `interfaceID` is not 0xffffffff, `false` otherwise 11 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/interfaces/IERC173.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 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 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/libraries/LibDiamond.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; 9 | 10 | library LibDiamond { 11 | bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); 12 | 13 | struct DiamondStorage { 14 | // maps function selectors to the facets that execute the functions. 15 | // and maps the selectors to their position in the selectorSlots array. 16 | // func selector => address facet, selector position 17 | mapping(bytes4 => bytes32) facets; 18 | // array of slots of function selectors. 19 | // each slot holds 8 function selectors. 20 | mapping(uint256 => bytes32) selectorSlots; 21 | // The number of function selectors in selectorSlots 22 | uint16 selectorCount; 23 | // Used to query if a contract implements an interface. 24 | // Used to implement ERC-165. 25 | mapping(bytes4 => bool) supportedInterfaces; 26 | // owner of the contract 27 | address contractOwner; 28 | } 29 | 30 | function diamondStorage() internal pure returns (DiamondStorage storage ds) { 31 | bytes32 position = DIAMOND_STORAGE_POSITION; 32 | assembly { 33 | ds.slot := position 34 | } 35 | } 36 | 37 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 38 | 39 | function setContractOwner(address _newOwner) internal { 40 | DiamondStorage storage ds = diamondStorage(); 41 | address previousOwner = ds.contractOwner; 42 | ds.contractOwner = _newOwner; 43 | emit OwnershipTransferred(previousOwner, _newOwner); 44 | } 45 | 46 | function contractOwner() internal view returns (address contractOwner_) { 47 | contractOwner_ = diamondStorage().contractOwner; 48 | } 49 | 50 | function enforceIsContractOwner() internal view { 51 | require(msg.sender == diamondStorage().contractOwner, "LibDiamond: Must be contract owner"); 52 | } 53 | 54 | event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata); 55 | 56 | bytes32 constant CLEAR_ADDRESS_MASK = bytes32(uint256(0xffffffffffffffffffffffff)); 57 | bytes32 constant CLEAR_SELECTOR_MASK = bytes32(uint256(0xffffffff << 224)); 58 | 59 | // Internal function version of diamondCut 60 | // This code is almost the same as the external diamondCut, 61 | // except it is using 'Facet[] memory _diamondCut' instead of 62 | // 'Facet[] calldata _diamondCut'. 63 | // The code is duplicated to prevent copying calldata to memory which 64 | // causes an error for a two dimensional array. 65 | function diamondCut( 66 | IDiamondCut.FacetCut[] memory _diamondCut, 67 | address _init, 68 | bytes memory _calldata 69 | ) internal { 70 | DiamondStorage storage ds = diamondStorage(); 71 | uint256 originalSelectorCount = ds.selectorCount; 72 | uint256 selectorCount = originalSelectorCount; 73 | bytes32 selectorSlot; 74 | // Check if last selector slot is not full 75 | // "selectorCount & 7" is a gas efficient modulo by eight "selectorCount % 8" 76 | if (selectorCount & 7 > 0) { 77 | // get last selectorSlot 78 | // "selectorSlot >> 3" is a gas efficient division by 8 "selectorSlot / 8" 79 | selectorSlot = ds.selectorSlots[selectorCount >> 3]; 80 | } 81 | // loop through diamond cut 82 | for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) { 83 | (selectorCount, selectorSlot) = addReplaceRemoveFacetSelectors( 84 | selectorCount, 85 | selectorSlot, 86 | _diamondCut[facetIndex].facetAddress, 87 | _diamondCut[facetIndex].action, 88 | _diamondCut[facetIndex].functionSelectors 89 | ); 90 | } 91 | if (selectorCount != originalSelectorCount) { 92 | ds.selectorCount = uint16(selectorCount); 93 | } 94 | // If last selector slot is not full 95 | // "selectorCount & 7" is a gas efficient modulo by eight "selectorCount % 8" 96 | if (selectorCount & 7 > 0) { 97 | // "selectorSlot >> 3" is a gas efficient division by 8 "selectorSlot / 8" 98 | ds.selectorSlots[selectorCount >> 3] = selectorSlot; 99 | } 100 | emit DiamondCut(_diamondCut, _init, _calldata); 101 | initializeDiamondCut(_init, _calldata); 102 | } 103 | 104 | function addReplaceRemoveFacetSelectors( 105 | uint256 _selectorCount, 106 | bytes32 _selectorSlot, 107 | address _newFacetAddress, 108 | IDiamondCut.FacetCutAction _action, 109 | bytes4[] memory _selectors 110 | ) internal returns (uint256, bytes32) { 111 | DiamondStorage storage ds = diamondStorage(); 112 | require(_selectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); 113 | if (_action == IDiamondCut.FacetCutAction.Add) { 114 | enforceHasContractCode(_newFacetAddress, "LibDiamondCut: Add facet has no code"); 115 | for (uint256 selectorIndex; selectorIndex < _selectors.length; selectorIndex++) { 116 | bytes4 selector = _selectors[selectorIndex]; 117 | bytes32 oldFacet = ds.facets[selector]; 118 | require(address(bytes20(oldFacet)) == address(0), "LibDiamondCut: Can't add function that already exists"); 119 | // add facet for selector 120 | ds.facets[selector] = bytes20(_newFacetAddress) | bytes32(_selectorCount); 121 | // "_selectorCount & 7" is a gas efficient modulo by eight "_selectorCount % 8" 122 | uint256 selectorInSlotPosition = (_selectorCount & 7) << 5; 123 | // clear selector position in slot and add selector 124 | _selectorSlot = (_selectorSlot & ~(CLEAR_SELECTOR_MASK >> selectorInSlotPosition)) | (bytes32(selector) >> selectorInSlotPosition); 125 | // if slot is full then write it to storage 126 | if (selectorInSlotPosition == 224) { 127 | // "_selectorSlot >> 3" is a gas efficient division by 8 "_selectorSlot / 8" 128 | ds.selectorSlots[_selectorCount >> 3] = _selectorSlot; 129 | _selectorSlot = 0; 130 | } 131 | _selectorCount++; 132 | } 133 | } else if (_action == IDiamondCut.FacetCutAction.Replace) { 134 | enforceHasContractCode(_newFacetAddress, "LibDiamondCut: Replace facet has no code"); 135 | for (uint256 selectorIndex; selectorIndex < _selectors.length; selectorIndex++) { 136 | bytes4 selector = _selectors[selectorIndex]; 137 | bytes32 oldFacet = ds.facets[selector]; 138 | address oldFacetAddress = address(bytes20(oldFacet)); 139 | // only useful if immutable functions exist 140 | require(oldFacetAddress != address(this), "LibDiamondCut: Can't replace immutable function"); 141 | require(oldFacetAddress != _newFacetAddress, "LibDiamondCut: Can't replace function with same function"); 142 | require(oldFacetAddress != address(0), "LibDiamondCut: Can't replace function that doesn't exist"); 143 | // replace old facet address 144 | ds.facets[selector] = (oldFacet & CLEAR_ADDRESS_MASK) | bytes20(_newFacetAddress); 145 | } 146 | } else if (_action == IDiamondCut.FacetCutAction.Remove) { 147 | require(_newFacetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)"); 148 | // "_selectorCount >> 3" is a gas efficient division by 8 "_selectorCount / 8" 149 | uint256 selectorSlotCount = _selectorCount >> 3; 150 | // "_selectorCount & 7" is a gas efficient modulo by eight "_selectorCount % 8" 151 | uint256 selectorInSlotIndex = _selectorCount & 7; 152 | for (uint256 selectorIndex; selectorIndex < _selectors.length; selectorIndex++) { 153 | if (_selectorSlot == 0) { 154 | // get last selectorSlot 155 | selectorSlotCount--; 156 | _selectorSlot = ds.selectorSlots[selectorSlotCount]; 157 | selectorInSlotIndex = 7; 158 | } else { 159 | selectorInSlotIndex--; 160 | } 161 | bytes4 lastSelector; 162 | uint256 oldSelectorsSlotCount; 163 | uint256 oldSelectorInSlotPosition; 164 | // adding a block here prevents stack too deep error 165 | { 166 | bytes4 selector = _selectors[selectorIndex]; 167 | bytes32 oldFacet = ds.facets[selector]; 168 | require(address(bytes20(oldFacet)) != address(0), "LibDiamondCut: Can't remove function that doesn't exist"); 169 | // only useful if immutable functions exist 170 | require(address(bytes20(oldFacet)) != address(this), "LibDiamondCut: Can't remove immutable function"); 171 | // replace selector with last selector in ds.facets 172 | // gets the last selector 173 | lastSelector = bytes4(_selectorSlot << (selectorInSlotIndex << 5)); 174 | if (lastSelector != selector) { 175 | // update last selector slot position info 176 | ds.facets[lastSelector] = (oldFacet & CLEAR_ADDRESS_MASK) | bytes20(ds.facets[lastSelector]); 177 | } 178 | delete ds.facets[selector]; 179 | uint256 oldSelectorCount = uint16(uint256(oldFacet)); 180 | // "oldSelectorCount >> 3" is a gas efficient division by 8 "oldSelectorCount / 8" 181 | oldSelectorsSlotCount = oldSelectorCount >> 3; 182 | // "oldSelectorCount & 7" is a gas efficient modulo by eight "oldSelectorCount % 8" 183 | oldSelectorInSlotPosition = (oldSelectorCount & 7) << 5; 184 | } 185 | if (oldSelectorsSlotCount != selectorSlotCount) { 186 | bytes32 oldSelectorSlot = ds.selectorSlots[oldSelectorsSlotCount]; 187 | // clears the selector we are deleting and puts the last selector in its place. 188 | oldSelectorSlot = 189 | (oldSelectorSlot & ~(CLEAR_SELECTOR_MASK >> oldSelectorInSlotPosition)) | 190 | (bytes32(lastSelector) >> oldSelectorInSlotPosition); 191 | // update storage with the modified slot 192 | ds.selectorSlots[oldSelectorsSlotCount] = oldSelectorSlot; 193 | } else { 194 | // clears the selector we are deleting and puts the last selector in its place. 195 | _selectorSlot = 196 | (_selectorSlot & ~(CLEAR_SELECTOR_MASK >> oldSelectorInSlotPosition)) | 197 | (bytes32(lastSelector) >> oldSelectorInSlotPosition); 198 | } 199 | if (selectorInSlotIndex == 0) { 200 | delete ds.selectorSlots[selectorSlotCount]; 201 | _selectorSlot = 0; 202 | } 203 | } 204 | _selectorCount = selectorSlotCount * 8 + selectorInSlotIndex; 205 | } else { 206 | revert("LibDiamondCut: Incorrect FacetCutAction"); 207 | } 208 | return (_selectorCount, _selectorSlot); 209 | } 210 | 211 | function initializeDiamondCut(address _init, bytes memory _calldata) internal { 212 | if (_init == address(0)) { 213 | require(_calldata.length == 0, "LibDiamondCut: _init is address(0) but_calldata is not empty"); 214 | } else { 215 | require(_calldata.length > 0, "LibDiamondCut: _calldata is empty but _init is not address(0)"); 216 | if (_init != address(this)) { 217 | enforceHasContractCode(_init, "LibDiamondCut: _init address has no code"); 218 | } 219 | (bool success, bytes memory error) = _init.delegatecall(_calldata); 220 | if (!success) { 221 | if (error.length > 0) { 222 | // bubble up the error 223 | revert(string(error)); 224 | } else { 225 | revert("LibDiamondCut: _init function reverted"); 226 | } 227 | } 228 | } 229 | } 230 | 231 | function enforceHasContractCode(address _contract, string memory _errorMessage) internal view { 232 | uint256 contractSize; 233 | assembly { 234 | contractSize := extcodesize(_contract) 235 | } 236 | require(contractSize > 0, _errorMessage); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /diamond-2-brownie/contracts/upgradeInitializers/DiamondInit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.9; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | * 8 | * Implementation of a diamond. 9 | /******************************************************************************/ 10 | 11 | import {LibDiamond} from "../libraries/LibDiamond.sol"; 12 | import { IDiamondLoupe } from "../interfaces/IDiamondLoupe.sol"; 13 | import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; 14 | import { IERC173 } from "../interfaces/IERC173.sol"; 15 | import { IERC165 } from "../interfaces/IERC165.sol"; 16 | 17 | // It is exapected that this contract is customized if you want to deploy your diamond 18 | // with data from a deployment script. Use the init function to initialize state variables 19 | // of your diamond. Add parameters to the init funciton if you need to. 20 | 21 | contract DiamondInit { 22 | 23 | // You can add parameters to this function in order to pass in 24 | // data to set your own state variables 25 | function init() external { 26 | // adding ERC165 data 27 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 28 | ds.supportedInterfaces[type(IERC165).interfaceId] = true; 29 | ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true; 30 | ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true; 31 | ds.supportedInterfaces[type(IERC173).interfaceId] = true; 32 | 33 | // add your own state variables 34 | // EIP-2535 specifies that the `diamondCut` function takes two optional 35 | // arguments: address _init and bytes calldata _calldata 36 | // These arguments are used to execute an arbitrary function using delegatecall 37 | // in order to set state variables in the diamond during deployment or an upgrade 38 | // More info here: https://eips.ethereum.org/EIPS/eip-2535#diamond-interface 39 | } 40 | 41 | 42 | } -------------------------------------------------------------------------------- /diamond-2-brownie/scripts/deploy.py: -------------------------------------------------------------------------------- 1 | from brownie import Contract, accounts 2 | from brownie import ( 3 | Diamond, 4 | DiamondCutFacet, 5 | DiamondLoupeFacet, 6 | OwnershipFacet, 7 | DiamondInit, 8 | ) 9 | 10 | # from brownie import Test1Facet, Test2Facet 11 | from scripts.helpers import facetCutAction, getSelectors 12 | 13 | 14 | def main(): 15 | owner = accounts[0] 16 | 17 | diamondCutFacet = DiamondCutFacet.deploy({"from": owner}) 18 | diamondLoupeFacet = DiamondLoupeFacet.deploy({"from": owner}) 19 | ownershipFacet = OwnershipFacet.deploy({"from": owner}) 20 | diamondInit = DiamondInit.deploy({"from": owner}) 21 | diamond = Diamond.deploy(owner, diamondCutFacet.address, {"from": owner}) 22 | 23 | cut = [ 24 | [ 25 | diamondLoupeFacet.address, 26 | facetCutAction["Add"], 27 | getSelectors(DiamondLoupeFacet), 28 | ], 29 | [ownershipFacet.address, facetCutAction["Add"], getSelectors(OwnershipFacet)], 30 | ] 31 | 32 | # DiamondCutFacet at diamond.address 33 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 34 | initSelector = getSelectors(DiamondInit) 35 | 36 | diamondCut.diamondCut(cut, diamondInit.address, initSelector[0], {"from": owner}) 37 | -------------------------------------------------------------------------------- /diamond-2-brownie/scripts/helpers.py: -------------------------------------------------------------------------------- 1 | import web3 2 | 3 | facetCutAction = {"Add": 0, "Replace": 1, "Remove": 2} 4 | 5 | zeroAddress = "0x0000000000000000000000000000000000000000" 6 | 7 | 8 | def getSelectors(contract): 9 | return list(contract.signatures.values()) 10 | 11 | 12 | def hashFunctionSignature(function_signature_text): 13 | return web3.Web3.keccak(text=function_signature_text).hex()[0:10] 14 | -------------------------------------------------------------------------------- /diamond-2-brownie/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract 3 | from scripts.helpers import facetCutAction, getSelectors 4 | 5 | 6 | @pytest.fixture(scope="module") 7 | def deployed_contracts( 8 | accounts, 9 | Diamond, 10 | DiamondInit, 11 | DiamondCutFacet, 12 | DiamondLoupeFacet, 13 | OwnershipFacet, 14 | Test1Facet, 15 | Test2Facet, 16 | ): 17 | owner = accounts[0] 18 | 19 | diamondCutFacet = DiamondCutFacet.deploy({"from": owner}) 20 | diamondLoupeFacet = DiamondLoupeFacet.deploy({"from": owner}) 21 | ownershipFacet = OwnershipFacet.deploy({"from": owner}) 22 | test1Facet = Test1Facet.deploy({"from": owner}) 23 | test2Facet = Test2Facet.deploy({"from": owner}) 24 | 25 | diamondInit = DiamondInit.deploy({"from": owner}) 26 | diamond = Diamond.deploy(owner, diamondCutFacet.address, {"from": owner}) 27 | 28 | cut = [ 29 | [ 30 | diamondLoupeFacet.address, 31 | facetCutAction["Add"], 32 | getSelectors(DiamondLoupeFacet), 33 | ], 34 | [ownershipFacet.address, facetCutAction["Add"], getSelectors(OwnershipFacet)], 35 | ] 36 | 37 | # DiamondCutFacet at diamond.address 38 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 39 | 40 | # DiamondInit has only one function, init() 41 | initSelector = getSelectors(DiamondInit)[0] 42 | 43 | # Cutting the Diamond 44 | diamondCut.diamondCut(cut, diamondInit.address, initSelector, {"from": owner}) 45 | 46 | return ( 47 | diamond, 48 | diamondCutFacet, 49 | diamondLoupeFacet, 50 | ownershipFacet, 51 | test1Facet, 52 | test2Facet, 53 | ) 54 | -------------------------------------------------------------------------------- /diamond-2-brownie/tests/diamond_basics_test.py: -------------------------------------------------------------------------------- 1 | from scripts.helpers import getSelectors 2 | from brownie import Contract 3 | 4 | 5 | def test_contracts_deployed(deployed_contracts): 6 | for contract in deployed_contracts: 7 | assert len(contract.address) == 42 8 | 9 | 10 | def test_diamond_has_three_facets(deployed_contracts): 11 | diamond = deployed_contracts[0] 12 | diamondLoupeFacet = deployed_contracts[2] 13 | diamondLoupe = Contract.from_abi( 14 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 15 | ) 16 | assert len(diamondLoupe.facetAddresses()) == 3 17 | 18 | 19 | def test_facets_have_the_right_function_selectors(deployed_contracts): 20 | diamond = deployed_contracts[0] 21 | diamondLoupeFacet = deployed_contracts[2] 22 | diamondCutFacet = deployed_contracts[1] 23 | ownership = deployed_contracts[3] 24 | 25 | diamondLoupe = Contract.from_abi( 26 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 27 | ) 28 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 29 | 30 | facetAddresses = diamondLoupe.facetAddresses() 31 | 32 | selectors = getSelectors(diamondCut) 33 | result = diamondLoupe.facetFunctionSelectors(facetAddresses[0]) 34 | assert selectors == list(result) 35 | 36 | selectors = getSelectors(diamondLoupe) 37 | result = diamondLoupe.facetFunctionSelectors(facetAddresses[1]) 38 | assert selectors == list(result) 39 | 40 | selectors = getSelectors(ownership) 41 | result = diamondLoupe.facetFunctionSelectors(facetAddresses[2]) 42 | assert selectors == list(result) 43 | 44 | 45 | def test_selectors_associated_to_facets_correctly(deployed_contracts): 46 | diamond = deployed_contracts[0] 47 | diamondLoupeFacet = deployed_contracts[2] 48 | diamondLoupe = Contract.from_abi( 49 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 50 | ) 51 | facetAddresses = diamondLoupe.facetAddresses() 52 | assert diamondLoupe.facetAddress("0x1f931c1c") == facetAddresses[0] 53 | assert diamondLoupe.facetAddress("0xcdffacc6") == facetAddresses[1] 54 | assert diamondLoupe.facetAddress("0x01ffc9a7") == facetAddresses[1] 55 | assert diamondLoupe.facetAddress("0xf2fde38b") == facetAddresses[2] 56 | -------------------------------------------------------------------------------- /diamond-2-brownie/tests/diamond_cut_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from scripts.helpers import ( 3 | getSelectors, 4 | facetCutAction, 5 | zeroAddress, 6 | hashFunctionSignature, 7 | ) 8 | from brownie import Contract 9 | 10 | 11 | def test_add_test1facet_functions(accounts, deployed_contracts, Test1Facet): 12 | owner = accounts[0] 13 | diamond = deployed_contracts[0] 14 | diamondLoupeFacet = deployed_contracts[2] 15 | diamondCutFacet = deployed_contracts[1] 16 | test1Facet = deployed_contracts[4] 17 | 18 | diamondLoupe = Contract.from_abi( 19 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 20 | ) 21 | 22 | # removing supportsInterface(bytes4) from selector list 23 | supportsInterFaceFnSignature = "supportsInterface(bytes4)" 24 | selector = hashFunctionSignature(supportsInterFaceFnSignature) 25 | selectors = getSelectors(Test1Facet) 26 | selectors.remove(selector) 27 | 28 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 29 | 30 | cut = [[test1Facet.address, facetCutAction["Add"], selectors]] 31 | 32 | diamondCut.diamondCut( 33 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 34 | ) 35 | 36 | assert len(diamondLoupe.facetAddresses()) == 4 37 | 38 | 39 | def test_call_test1facet_functions(accounts, deployed_contracts, Test1Facet): 40 | owner = accounts[0] 41 | diamond = deployed_contracts[0] 42 | diamondLoupeFacet = deployed_contracts[2] 43 | test1 = deployed_contracts[4] 44 | test1AtDiamond = Contract.from_abi("Test1Facet", diamond.address, Test1Facet.abi) 45 | 46 | test1AtDiamond.test1Func3({"from": owner}) 47 | test1AtDiamond.test1Func19({"from": owner}) 48 | 49 | diamondLoupe = Contract.from_abi( 50 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 51 | ) 52 | assert len(diamondLoupe.facetFunctionSelectors(test1.address)) == 20 53 | 54 | 55 | def test_replace_supportsInterface_function(accounts, deployed_contracts): 56 | owner = accounts[0] 57 | diamond = deployed_contracts[0] 58 | diamondCutFacet = deployed_contracts[1] 59 | diamondLoupeFacet = deployed_contracts[2] 60 | test1 = deployed_contracts[4] 61 | 62 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 63 | diamondLoupe = Contract.from_abi( 64 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 65 | ) 66 | 67 | assert len(diamondLoupe.facetFunctionSelectors(test1.address)) == 20 68 | 69 | function_signature = str = "supportsInterface(bytes4)" 70 | selector = hashFunctionSignature(function_signature_text=function_signature) 71 | cut = [[test1.address, facetCutAction["Replace"], [selector]]] 72 | 73 | diamondCut.diamondCut( 74 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 75 | ) 76 | 77 | assert len(diamondLoupe.facetFunctionSelectors(test1.address)) == 21 78 | 79 | 80 | def test_add_test2facet_functions(accounts, deployed_contracts, Test2Facet): 81 | owner = accounts[0] 82 | diamond = deployed_contracts[0] 83 | diamondCutFacet = deployed_contracts[1] 84 | diamondLoupeFacet = deployed_contracts[2] 85 | test2 = deployed_contracts[5] 86 | 87 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 88 | diamondLoupe = Contract.from_abi( 89 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 90 | ) 91 | 92 | cut = [[test2.address, facetCutAction["Add"], getSelectors(Test2Facet)]] 93 | 94 | # Cut the Diamond 95 | diamondCut.diamondCut( 96 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 97 | ) 98 | 99 | selectors = getSelectors(test2) 100 | z = diamondLoupe.facetFunctionSelectors(test2.address) 101 | assert len(z) == 20 102 | assert selectors == list(z) 103 | 104 | 105 | def test_test2facet_call(accounts, deployed_contracts, Test2Facet): 106 | owner = accounts[0] 107 | diamond = deployed_contracts[0] 108 | test2AtDiamond = Contract.from_abi("Test2Facet", diamond.address, Test2Facet.abi) 109 | test2AtDiamond.test2Func9({"from": owner}) 110 | 111 | 112 | def test_remove_some_test2facet_functions(accounts, deployed_contracts): 113 | owner = accounts[0] 114 | diamond = deployed_contracts[0] 115 | diamondCutFacet = deployed_contracts[1] 116 | diamondLoupeFacet = deployed_contracts[2] 117 | test2 = deployed_contracts[5] 118 | 119 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 120 | diamondLoupe = Contract.from_abi( 121 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 122 | ) 123 | 124 | functions_to_remove = [ 125 | "test2Func1()", 126 | "test2Func5()", 127 | "test2Func6()", 128 | "test2Func19()", 129 | "test2Func20()", 130 | ] 131 | selectors_to_remove = [ 132 | hashFunctionSignature(fname) for fname in functions_to_remove 133 | ] 134 | 135 | cut = [[zeroAddress, facetCutAction["Remove"], selectors_to_remove]] 136 | 137 | # Cut the Diamond 138 | diamondCut.diamondCut( 139 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 140 | ) 141 | function_selectors = diamondLoupe.facetFunctionSelectors(test2.address) 142 | assert len(function_selectors) == 15 143 | for fsr in selectors_to_remove: 144 | if fsr in function_selectors: 145 | assert False 146 | 147 | 148 | def test_remove_some_test1facet_functions(accounts, deployed_contracts): 149 | owner = accounts[0] 150 | diamond = deployed_contracts[0] 151 | diamondCutFacet = deployed_contracts[1] 152 | diamondLoupeFacet = deployed_contracts[2] 153 | test1 = deployed_contracts[4] 154 | 155 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 156 | diamondLoupe = Contract.from_abi( 157 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 158 | ) 159 | 160 | functions_to_remove = [ 161 | "test1Func1()", 162 | "test1Func5()", 163 | "test1Func6()", 164 | "test1Func11()", 165 | "test1Func19()", 166 | "test1Func20()", 167 | ] 168 | selectors_to_remove = [ 169 | hashFunctionSignature(fname) for fname in functions_to_remove 170 | ] 171 | 172 | cut = [[zeroAddress, facetCutAction["Remove"], selectors_to_remove]] 173 | 174 | # Cut the Diamond 175 | diamondCut.diamondCut( 176 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 177 | ) 178 | function_selectors = diamondLoupe.facetFunctionSelectors(test1.address) 179 | assert len(function_selectors) == 15 180 | for fsr in selectors_to_remove: 181 | if fsr in function_selectors: 182 | assert False 183 | 184 | 185 | def test_remove_all_facets_and_functions_except_diamondcut_and_facets( 186 | accounts, deployed_contracts 187 | ): 188 | owner = accounts[0] 189 | diamond = deployed_contracts[0] 190 | diamondCutFacet = deployed_contracts[1] 191 | diamondLoupeFacet = deployed_contracts[2] 192 | 193 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 194 | diamondLoupe = Contract.from_abi( 195 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 196 | ) 197 | 198 | # get all the selectors 199 | facets_with_selectors = diamondLoupe.facets() 200 | all_selectors = [] 201 | for x in facets_with_selectors: 202 | all_selectors.extend(list(x[1])) 203 | 204 | # remove ['facets()', 'diamondCut((address,uint8,bytes4[])[],address,bytes)']) from selectors 205 | selectors_to_keep = [ 206 | hashFunctionSignature("facets()"), 207 | hashFunctionSignature("diamondCut((address,uint8,bytes4[])[],address,bytes)"), 208 | ] 209 | 210 | for s in selectors_to_keep: 211 | all_selectors.remove(s) 212 | 213 | # Cut the Diamond 214 | cut = [[zeroAddress, facetCutAction["Remove"], all_selectors]] 215 | 216 | # Cut the Diamond 217 | diamondCut.diamondCut( 218 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 219 | ) 220 | 221 | facets = diamondLoupe.facets() 222 | 223 | assert len(facets) == 2 224 | -------------------------------------------------------------------------------- /diamond-2-brownie/tests/diamond_cut_test1_test2_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract 3 | from scripts.helpers import ( 4 | hashFunctionSignature, 5 | getSelectors, 6 | zeroAddress, 7 | facetCutAction, 8 | ) 9 | 10 | 11 | def test_add_test1_and_test2_facet_functions(accounts, deployed_contracts): 12 | owner = accounts[0] 13 | 14 | diamond = deployed_contracts[0] 15 | diamondCutFacet = deployed_contracts[1] 16 | diamondLoupeFacet = deployed_contracts[2] 17 | test1Facet = deployed_contracts[4] 18 | test2Facet = deployed_contracts[5] 19 | 20 | test1FacetSelectors = getSelectors(test1Facet) 21 | test1FacetSelectors.remove(hashFunctionSignature("supportsInterface(bytes4)")) 22 | 23 | cut = [ 24 | [test1Facet.address, facetCutAction["Add"], test1FacetSelectors], 25 | [test2Facet.address, facetCutAction["Add"], getSelectors(test2Facet)], 26 | ] 27 | 28 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 29 | 30 | diamondCut.diamondCut( 31 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 32 | ) 33 | 34 | diamondLoupe = Contract.from_abi( 35 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 36 | ) 37 | 38 | facets = diamondLoupe.facets() 39 | 40 | assert len(facets) == 5 41 | -------------------------------------------------------------------------------- /diamond-3-brownie/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.vy linguist-language=Python 3 | -------------------------------------------------------------------------------- /diamond-3-brownie/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .env 3 | .history 4 | .hypothesis/ 5 | build/ 6 | reports/ 7 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/Diamond.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | * 8 | * Implementation of a diamond. 9 | /******************************************************************************/ 10 | 11 | import { LibDiamond } from "./libraries/LibDiamond.sol"; 12 | import { IDiamondCut } from "./interfaces/IDiamondCut.sol"; 13 | 14 | contract Diamond { 15 | 16 | constructor(address _contractOwner, address _diamondCutFacet) payable { 17 | LibDiamond.setContractOwner(_contractOwner); 18 | 19 | // Add the diamondCut external function from the diamondCutFacet 20 | IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1); 21 | bytes4[] memory functionSelectors = new bytes4[](1); 22 | functionSelectors[0] = IDiamondCut.diamondCut.selector; 23 | cut[0] = IDiamondCut.FacetCut({ 24 | facetAddress: _diamondCutFacet, 25 | action: IDiamondCut.FacetCutAction.Add, 26 | functionSelectors: functionSelectors 27 | }); 28 | LibDiamond.diamondCut(cut, address(0), ""); 29 | } 30 | 31 | // Find facet for function that is called and execute the 32 | // function if a facet is found and return any value. 33 | fallback() external payable { 34 | LibDiamond.DiamondStorage storage ds; 35 | bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; 36 | // get diamond storage 37 | assembly { 38 | ds.slot := position 39 | } 40 | // get facet from function selector 41 | address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; 42 | require(facet != address(0), "Diamond: Function does not exist"); 43 | // Execute external function from facet using delegatecall and return any value. 44 | assembly { 45 | // copy function selector and any arguments 46 | calldatacopy(0, 0, calldatasize()) 47 | // execute function call using the facet 48 | let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) 49 | // get any return value 50 | returndatacopy(0, 0, returndatasize()) 51 | // return any return value or error back to the caller 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 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/facets/DiamondCutFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; 10 | import { LibDiamond } from "../libraries/LibDiamond.sol"; 11 | 12 | contract DiamondCutFacet is IDiamondCut { 13 | /// @notice Add/replace/remove any number of functions and optionally execute 14 | /// a function with delegatecall 15 | /// @param _diamondCut Contains the facet addresses and function selectors 16 | /// @param _init The address of the contract or facet to execute _calldata 17 | /// @param _calldata A function call, including function selector and arguments 18 | /// _calldata is executed with delegatecall on _init 19 | function diamondCut( 20 | FacetCut[] calldata _diamondCut, 21 | address _init, 22 | bytes calldata _calldata 23 | ) external override { 24 | LibDiamond.enforceIsContractOwner(); 25 | LibDiamond.diamondCut(_diamondCut, _init, _calldata); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/facets/DiamondLoupeFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | /******************************************************************************\ 4 | * Author: Nick Mudge (https://twitter.com/mudgen) 5 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 6 | /******************************************************************************/ 7 | 8 | import { LibDiamond } from "../libraries/LibDiamond.sol"; 9 | import { IDiamondLoupe } from "../interfaces/IDiamondLoupe.sol"; 10 | import { IERC165 } from "../interfaces/IERC165.sol"; 11 | 12 | contract DiamondLoupeFacet is IDiamondLoupe, IERC165 { 13 | // Diamond Loupe Functions 14 | //////////////////////////////////////////////////////////////////// 15 | /// These functions are expected to be called frequently by tools. 16 | // 17 | // struct Facet { 18 | // address facetAddress; 19 | // bytes4[] functionSelectors; 20 | // } 21 | 22 | /// @notice Gets all facets and their selectors. 23 | /// @return facets_ Facet 24 | function facets() external override view returns (Facet[] memory facets_) { 25 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 26 | uint256 numFacets = ds.facetAddresses.length; 27 | facets_ = new Facet[](numFacets); 28 | for (uint256 i; i < numFacets; i++) { 29 | address facetAddress_ = ds.facetAddresses[i]; 30 | facets_[i].facetAddress = facetAddress_; 31 | facets_[i].functionSelectors = ds.facetFunctionSelectors[facetAddress_].functionSelectors; 32 | } 33 | } 34 | 35 | /// @notice Gets all the function selectors provided by a facet. 36 | /// @param _facet The facet address. 37 | /// @return facetFunctionSelectors_ 38 | function facetFunctionSelectors(address _facet) external override view returns (bytes4[] memory facetFunctionSelectors_) { 39 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 40 | facetFunctionSelectors_ = ds.facetFunctionSelectors[_facet].functionSelectors; 41 | } 42 | 43 | /// @notice Get all the facet addresses used by a diamond. 44 | /// @return facetAddresses_ 45 | function facetAddresses() external override view returns (address[] memory facetAddresses_) { 46 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 47 | facetAddresses_ = ds.facetAddresses; 48 | } 49 | 50 | /// @notice Gets the facet that supports the given selector. 51 | /// @dev If facet is not found return address(0). 52 | /// @param _functionSelector The function selector. 53 | /// @return facetAddress_ The facet address. 54 | function facetAddress(bytes4 _functionSelector) external override view returns (address facetAddress_) { 55 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 56 | facetAddress_ = ds.selectorToFacetAndPosition[_functionSelector].facetAddress; 57 | } 58 | 59 | // This implements ERC-165. 60 | function supportsInterface(bytes4 _interfaceId) external override view returns (bool) { 61 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 62 | return ds.supportedInterfaces[_interfaceId]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/facets/OwnershipFacet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import { LibDiamond } from "../libraries/LibDiamond.sol"; 5 | import { IERC173 } from "../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 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/facets/Test1Facet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Test1Facet { 5 | event TestEvent(address something); 6 | 7 | function test1Func1() external {} 8 | 9 | function test1Func2() external {} 10 | 11 | function test1Func3() external {} 12 | 13 | function test1Func4() external {} 14 | 15 | function test1Func5() external {} 16 | 17 | function test1Func6() external {} 18 | 19 | function test1Func7() external {} 20 | 21 | function test1Func8() external {} 22 | 23 | function test1Func9() external {} 24 | 25 | function test1Func10() external {} 26 | 27 | function test1Func11() external {} 28 | 29 | function test1Func12() external {} 30 | 31 | function test1Func13() external {} 32 | 33 | function test1Func14() external {} 34 | 35 | function test1Func15() external {} 36 | 37 | function test1Func16() external {} 38 | 39 | function test1Func17() external {} 40 | 41 | function test1Func18() external {} 42 | 43 | function test1Func19() external {} 44 | 45 | function test1Func20() external {} 46 | 47 | function supportsInterface(bytes4 _interfaceID) external view returns (bool) {} 48 | } 49 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/facets/Test2Facet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Test2Facet { 5 | function test2Func1() external {} 6 | 7 | function test2Func2() external {} 8 | 9 | function test2Func3() external {} 10 | 11 | function test2Func4() external {} 12 | 13 | function test2Func5() external {} 14 | 15 | function test2Func6() external {} 16 | 17 | function test2Func7() external {} 18 | 19 | function test2Func8() external {} 20 | 21 | function test2Func9() external {} 22 | 23 | function test2Func10() external {} 24 | 25 | function test2Func11() external {} 26 | 27 | function test2Func12() external {} 28 | 29 | function test2Func13() external {} 30 | 31 | function test2Func14() external {} 32 | 33 | function test2Func15() external {} 34 | 35 | function test2Func16() external {} 36 | 37 | function test2Func17() external {} 38 | 39 | function test2Func18() external {} 40 | 41 | function test2Func19() external {} 42 | 43 | function test2Func20() external {} 44 | } 45 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/interfaces/IDiamondCut.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | interface IDiamondCut { 10 | enum FacetCutAction {Add, Replace, Remove} 11 | // Add=0, Replace=1, Remove=2 12 | 13 | struct FacetCut { 14 | address facetAddress; 15 | FacetCutAction action; 16 | bytes4[] functionSelectors; 17 | } 18 | 19 | /// @notice Add/replace/remove any number of functions and optionally execute 20 | /// a function with delegatecall 21 | /// @param _diamondCut Contains the facet addresses and function selectors 22 | /// @param _init The address of the contract or facet to execute _calldata 23 | /// @param _calldata A function call, including function selector and arguments 24 | /// _calldata is executed with delegatecall on _init 25 | function diamondCut( 26 | FacetCut[] calldata _diamondCut, 27 | address _init, 28 | bytes calldata _calldata 29 | ) external; 30 | 31 | event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); 32 | } 33 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/interfaces/IDiamondLoupe.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | 9 | // A loupe is a small magnifying glass used to look at diamonds. 10 | // These functions look at diamonds 11 | interface IDiamondLoupe { 12 | /// These functions are expected to be called frequently 13 | /// by tools. 14 | 15 | struct Facet { 16 | address facetAddress; 17 | bytes4[] functionSelectors; 18 | } 19 | 20 | /// @notice Gets all facet addresses and their four byte function selectors. 21 | /// @return facets_ Facet 22 | function facets() external view returns (Facet[] memory facets_); 23 | 24 | /// @notice Gets all the function selectors supported by a specific facet. 25 | /// @param _facet The facet address. 26 | /// @return facetFunctionSelectors_ 27 | function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); 28 | 29 | /// @notice Get all the facet addresses used by a diamond. 30 | /// @return facetAddresses_ 31 | function facetAddresses() external view returns (address[] memory facetAddresses_); 32 | 33 | /// @notice Gets the facet that supports the given selector. 34 | /// @dev If facet is not found return address(0). 35 | /// @param _functionSelector The function selector. 36 | /// @return facetAddress_ The facet address. 37 | function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_); 38 | } 39 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/interfaces/IERC165.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | interface IERC165 { 5 | /// @notice Query if a contract implements an interface 6 | /// @param interfaceId The interface identifier, as specified in ERC-165 7 | /// @dev Interface identification is specified in ERC-165. This function 8 | /// uses less than 30,000 gas. 9 | /// @return `true` if the contract implements `interfaceID` and 10 | /// `interfaceID` is not 0xffffffff, `false` otherwise 11 | function supportsInterface(bytes4 interfaceId) external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/interfaces/IERC173.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 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 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/libraries/LibDiamond.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | /******************************************************************************/ 8 | import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; 9 | 10 | library LibDiamond { 11 | bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); 12 | 13 | struct FacetAddressAndPosition { 14 | address facetAddress; 15 | uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array 16 | } 17 | 18 | struct FacetFunctionSelectors { 19 | bytes4[] functionSelectors; 20 | uint256 facetAddressPosition; // position of facetAddress in facetAddresses array 21 | } 22 | 23 | struct DiamondStorage { 24 | // maps function selector to the facet address and 25 | // the position of the selector in the facetFunctionSelectors.selectors array 26 | mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition; 27 | // maps facet addresses to function selectors 28 | mapping(address => FacetFunctionSelectors) facetFunctionSelectors; 29 | // facet addresses 30 | address[] facetAddresses; 31 | // Used to query if a contract implements an interface. 32 | // Used to implement ERC-165. 33 | mapping(bytes4 => bool) supportedInterfaces; 34 | // owner of the contract 35 | address contractOwner; 36 | } 37 | 38 | function diamondStorage() internal pure returns (DiamondStorage storage ds) { 39 | bytes32 position = DIAMOND_STORAGE_POSITION; 40 | assembly { 41 | ds.slot := position 42 | } 43 | } 44 | 45 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 46 | 47 | function setContractOwner(address _newOwner) internal { 48 | DiamondStorage storage ds = diamondStorage(); 49 | address previousOwner = ds.contractOwner; 50 | ds.contractOwner = _newOwner; 51 | emit OwnershipTransferred(previousOwner, _newOwner); 52 | } 53 | 54 | function contractOwner() internal view returns (address contractOwner_) { 55 | contractOwner_ = diamondStorage().contractOwner; 56 | } 57 | 58 | function enforceIsContractOwner() internal view { 59 | require(msg.sender == diamondStorage().contractOwner, "LibDiamond: Must be contract owner"); 60 | } 61 | 62 | event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata); 63 | 64 | // Internal function version of diamondCut 65 | function diamondCut( 66 | IDiamondCut.FacetCut[] memory _diamondCut, 67 | address _init, 68 | bytes memory _calldata 69 | ) internal { 70 | for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) { 71 | IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action; 72 | if (action == IDiamondCut.FacetCutAction.Add) { 73 | addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); 74 | } else if (action == IDiamondCut.FacetCutAction.Replace) { 75 | replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); 76 | } else if (action == IDiamondCut.FacetCutAction.Remove) { 77 | removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); 78 | } else { 79 | revert("LibDiamondCut: Incorrect FacetCutAction"); 80 | } 81 | } 82 | emit DiamondCut(_diamondCut, _init, _calldata); 83 | initializeDiamondCut(_init, _calldata); 84 | } 85 | 86 | function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { 87 | require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); 88 | DiamondStorage storage ds = diamondStorage(); 89 | require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); 90 | uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length); 91 | // add new facet address if it does not exist 92 | if (selectorPosition == 0) { 93 | addFacet(ds, _facetAddress); 94 | } 95 | for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { 96 | bytes4 selector = _functionSelectors[selectorIndex]; 97 | address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; 98 | require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists"); 99 | addFunction(ds, selector, selectorPosition, _facetAddress); 100 | selectorPosition++; 101 | } 102 | } 103 | 104 | function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { 105 | require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); 106 | DiamondStorage storage ds = diamondStorage(); 107 | require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); 108 | uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length); 109 | // add new facet address if it does not exist 110 | if (selectorPosition == 0) { 111 | addFacet(ds, _facetAddress); 112 | } 113 | for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { 114 | bytes4 selector = _functionSelectors[selectorIndex]; 115 | address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; 116 | require(oldFacetAddress != _facetAddress, "LibDiamondCut: Can't replace function with same function"); 117 | removeFunction(ds, oldFacetAddress, selector); 118 | addFunction(ds, selector, selectorPosition, _facetAddress); 119 | selectorPosition++; 120 | } 121 | } 122 | 123 | function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { 124 | require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut"); 125 | DiamondStorage storage ds = diamondStorage(); 126 | // if function does not exist then do nothing and return 127 | require(_facetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)"); 128 | for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { 129 | bytes4 selector = _functionSelectors[selectorIndex]; 130 | address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; 131 | removeFunction(ds, oldFacetAddress, selector); 132 | } 133 | } 134 | 135 | function addFacet(DiamondStorage storage ds, address _facetAddress) internal { 136 | enforceHasContractCode(_facetAddress, "LibDiamondCut: New facet has no code"); 137 | ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds.facetAddresses.length; 138 | ds.facetAddresses.push(_facetAddress); 139 | } 140 | 141 | 142 | function addFunction(DiamondStorage storage ds, bytes4 _selector, uint96 _selectorPosition, address _facetAddress) internal { 143 | ds.selectorToFacetAndPosition[_selector].functionSelectorPosition = _selectorPosition; 144 | ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(_selector); 145 | ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress; 146 | } 147 | 148 | function removeFunction(DiamondStorage storage ds, address _facetAddress, bytes4 _selector) internal { 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 = uint96(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 = 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 | -------------------------------------------------------------------------------- /diamond-3-brownie/contracts/upgradeInitializers/DiamondInit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | /******************************************************************************\ 5 | * Author: Nick Mudge (https://twitter.com/mudgen) 6 | * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 7 | * 8 | * Implementation of a diamond. 9 | /******************************************************************************/ 10 | 11 | import {LibDiamond} from "../libraries/LibDiamond.sol"; 12 | import { IDiamondLoupe } from "../interfaces/IDiamondLoupe.sol"; 13 | import { IDiamondCut } from "../interfaces/IDiamondCut.sol"; 14 | import { IERC173 } from "../interfaces/IERC173.sol"; 15 | import { IERC165 } from "../interfaces/IERC165.sol"; 16 | 17 | // It is exapected that this contract is customized if you want to deploy your diamond 18 | // with data from a deployment script. Use the init function to initialize state variables 19 | // of your diamond. Add parameters to the init funciton if you need to. 20 | 21 | contract DiamondInit { 22 | 23 | // You can add parameters to this function in order to pass in 24 | // data to set your own state variables 25 | function init() external { 26 | // adding ERC165 data 27 | LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); 28 | ds.supportedInterfaces[type(IERC165).interfaceId] = true; 29 | ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true; 30 | ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true; 31 | ds.supportedInterfaces[type(IERC173).interfaceId] = true; 32 | 33 | // add your own state variables 34 | // EIP-2535 specifies that the `diamondCut` function takes two optional 35 | // arguments: address _init and bytes calldata _calldata 36 | // These arguments are used to execute an arbitrary function using delegatecall 37 | // in order to set state variables in the diamond during deployment or an upgrade 38 | // More info here: https://eips.ethereum.org/EIPS/eip-2535#diamond-interface 39 | } 40 | 41 | 42 | } -------------------------------------------------------------------------------- /diamond-3-brownie/scripts/deploy.py: -------------------------------------------------------------------------------- 1 | from brownie import Contract, accounts 2 | from brownie import ( 3 | Diamond, 4 | DiamondCutFacet, 5 | DiamondLoupeFacet, 6 | OwnershipFacet, 7 | DiamondInit, 8 | ) 9 | 10 | # from brownie import Test1Facet, Test2Facet 11 | from scripts.helpers import facetCutAction, getSelectors 12 | 13 | 14 | def main(): 15 | owner = accounts[0] 16 | 17 | diamondCutFacet = DiamondCutFacet.deploy({"from": owner}) 18 | diamondLoupeFacet = DiamondLoupeFacet.deploy({"from": owner}) 19 | ownershipFacet = OwnershipFacet.deploy({"from": owner}) 20 | diamondInit = DiamondInit.deploy({"from": owner}) 21 | diamond = Diamond.deploy(owner, diamondCutFacet.address, {"from": owner}) 22 | 23 | cut = [ 24 | [ 25 | diamondLoupeFacet.address, 26 | facetCutAction["Add"], 27 | getSelectors(DiamondLoupeFacet), 28 | ], 29 | [ownershipFacet.address, facetCutAction["Add"], getSelectors(OwnershipFacet)], 30 | ] 31 | 32 | # DiamondCutFacet at diamond.address 33 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 34 | initSelector = getSelectors(DiamondInit) 35 | 36 | diamondCut.diamondCut(cut, diamondInit.address, initSelector[0], {"from": owner}) 37 | -------------------------------------------------------------------------------- /diamond-3-brownie/scripts/helpers.py: -------------------------------------------------------------------------------- 1 | import web3 2 | 3 | facetCutAction = {"Add": 0, "Replace": 1, "Remove": 2} 4 | 5 | zeroAddress = "0x0000000000000000000000000000000000000000" 6 | 7 | 8 | def getSelectors(contract): 9 | return list(contract.signatures.values()) 10 | 11 | 12 | def hashFunctionSignature(function_signature_text): 13 | return web3.Web3.keccak(text=function_signature_text).hex()[0:10] 14 | -------------------------------------------------------------------------------- /diamond-3-brownie/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract 3 | from scripts.helpers import facetCutAction, getSelectors 4 | 5 | 6 | @pytest.fixture(scope="module") 7 | def deployed_contracts( 8 | accounts, 9 | Diamond, 10 | DiamondInit, 11 | DiamondCutFacet, 12 | DiamondLoupeFacet, 13 | OwnershipFacet, 14 | Test1Facet, 15 | Test2Facet, 16 | ): 17 | owner = accounts[0] 18 | 19 | diamondCutFacet = DiamondCutFacet.deploy({"from": owner}) 20 | diamondLoupeFacet = DiamondLoupeFacet.deploy({"from": owner}) 21 | ownershipFacet = OwnershipFacet.deploy({"from": owner}) 22 | test1Facet = Test1Facet.deploy({"from": owner}) 23 | test2Facet = Test2Facet.deploy({"from": owner}) 24 | 25 | diamondInit = DiamondInit.deploy({"from": owner}) 26 | diamond = Diamond.deploy(owner, diamondCutFacet.address, {"from": owner}) 27 | 28 | cut = [ 29 | [ 30 | diamondLoupeFacet.address, 31 | facetCutAction["Add"], 32 | getSelectors(DiamondLoupeFacet), 33 | ], 34 | [ownershipFacet.address, facetCutAction["Add"], getSelectors(OwnershipFacet)], 35 | ] 36 | 37 | # DiamondCutFacet at diamond.address 38 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 39 | 40 | # DiamondInit has only one function, init() 41 | initSelector = getSelectors(DiamondInit)[0] 42 | 43 | # Cutting the Diamond 44 | diamondCut.diamondCut(cut, diamondInit.address, initSelector, {"from": owner}) 45 | 46 | return ( 47 | diamond, 48 | diamondCutFacet, 49 | diamondLoupeFacet, 50 | ownershipFacet, 51 | test1Facet, 52 | test2Facet, 53 | ) 54 | -------------------------------------------------------------------------------- /diamond-3-brownie/tests/diamond_basics_test.py: -------------------------------------------------------------------------------- 1 | from scripts.helpers import getSelectors 2 | from brownie import Contract 3 | 4 | 5 | def test_contracts_deployed(deployed_contracts): 6 | for contract in deployed_contracts: 7 | assert len(contract.address) == 42 8 | 9 | 10 | def test_diamond_has_three_facets(deployed_contracts): 11 | diamond = deployed_contracts[0] 12 | diamondLoupeFacet = deployed_contracts[2] 13 | diamondLoupe = Contract.from_abi( 14 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 15 | ) 16 | assert len(diamondLoupe.facetAddresses()) == 3 17 | 18 | 19 | def test_facets_have_the_right_function_selectors(deployed_contracts): 20 | diamond = deployed_contracts[0] 21 | diamondLoupeFacet = deployed_contracts[2] 22 | diamondCutFacet = deployed_contracts[1] 23 | ownership = deployed_contracts[3] 24 | 25 | diamondLoupe = Contract.from_abi( 26 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 27 | ) 28 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 29 | 30 | facetAddresses = diamondLoupe.facetAddresses() 31 | 32 | selectors = getSelectors(diamondCut) 33 | result = diamondLoupe.facetFunctionSelectors(facetAddresses[0]) 34 | assert selectors == list(result) 35 | 36 | selectors = getSelectors(diamondLoupe) 37 | result = diamondLoupe.facetFunctionSelectors(facetAddresses[1]) 38 | assert selectors == list(result) 39 | 40 | selectors = getSelectors(ownership) 41 | result = diamondLoupe.facetFunctionSelectors(facetAddresses[2]) 42 | assert selectors == list(result) 43 | 44 | 45 | def test_selectors_associated_to_facets_correctly(deployed_contracts): 46 | diamond = deployed_contracts[0] 47 | diamondLoupeFacet = deployed_contracts[2] 48 | diamondLoupe = Contract.from_abi( 49 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 50 | ) 51 | facetAddresses = diamondLoupe.facetAddresses() 52 | assert diamondLoupe.facetAddress("0x1f931c1c") == facetAddresses[0] 53 | assert diamondLoupe.facetAddress("0xcdffacc6") == facetAddresses[1] 54 | assert diamondLoupe.facetAddress("0x01ffc9a7") == facetAddresses[1] 55 | assert diamondLoupe.facetAddress("0xf2fde38b") == facetAddresses[2] 56 | -------------------------------------------------------------------------------- /diamond-3-brownie/tests/diamond_cut_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from scripts.helpers import ( 3 | getSelectors, 4 | facetCutAction, 5 | zeroAddress, 6 | hashFunctionSignature, 7 | ) 8 | from brownie import Contract 9 | 10 | 11 | def test_add_test1facet_functions(accounts, deployed_contracts, Test1Facet): 12 | owner = accounts[0] 13 | diamond = deployed_contracts[0] 14 | diamondLoupeFacet = deployed_contracts[2] 15 | diamondCutFacet = deployed_contracts[1] 16 | test1Facet = deployed_contracts[4] 17 | 18 | diamondLoupe = Contract.from_abi( 19 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 20 | ) 21 | 22 | # removing supportsInterface(bytes4) from selector list 23 | supportsInterFaceFnSignature = "supportsInterface(bytes4)" 24 | selector = hashFunctionSignature(supportsInterFaceFnSignature) 25 | selectors = getSelectors(Test1Facet) 26 | selectors.remove(selector) 27 | 28 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 29 | 30 | cut = [[test1Facet.address, facetCutAction["Add"], selectors]] 31 | 32 | diamondCut.diamondCut( 33 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 34 | ) 35 | 36 | assert len(diamondLoupe.facetAddresses()) == 4 37 | 38 | 39 | def test_call_test1facet_functions(accounts, deployed_contracts, Test1Facet): 40 | owner = accounts[0] 41 | diamond = deployed_contracts[0] 42 | diamondLoupeFacet = deployed_contracts[2] 43 | test1 = deployed_contracts[4] 44 | test1AtDiamond = Contract.from_abi("Test1Facet", diamond.address, Test1Facet.abi) 45 | 46 | test1AtDiamond.test1Func3({"from": owner}) 47 | test1AtDiamond.test1Func19({"from": owner}) 48 | 49 | diamondLoupe = Contract.from_abi( 50 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 51 | ) 52 | assert len(diamondLoupe.facetFunctionSelectors(test1.address)) == 20 53 | 54 | 55 | def test_replace_supportsInterface_function(accounts, deployed_contracts): 56 | owner = accounts[0] 57 | diamond = deployed_contracts[0] 58 | diamondCutFacet = deployed_contracts[1] 59 | diamondLoupeFacet = deployed_contracts[2] 60 | test1 = deployed_contracts[4] 61 | 62 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 63 | diamondLoupe = Contract.from_abi( 64 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 65 | ) 66 | 67 | assert len(diamondLoupe.facetFunctionSelectors(test1.address)) == 20 68 | 69 | function_signature = str = "supportsInterface(bytes4)" 70 | selector = hashFunctionSignature(function_signature_text=function_signature) 71 | cut = [[test1.address, facetCutAction["Replace"], [selector]]] 72 | 73 | diamondCut.diamondCut( 74 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 75 | ) 76 | 77 | assert len(diamondLoupe.facetFunctionSelectors(test1.address)) == 21 78 | 79 | 80 | def test_add_test2facet_functions(accounts, deployed_contracts, Test2Facet): 81 | owner = accounts[0] 82 | diamond = deployed_contracts[0] 83 | diamondCutFacet = deployed_contracts[1] 84 | diamondLoupeFacet = deployed_contracts[2] 85 | test2 = deployed_contracts[5] 86 | 87 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 88 | diamondLoupe = Contract.from_abi( 89 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 90 | ) 91 | 92 | cut = [[test2.address, facetCutAction["Add"], getSelectors(Test2Facet)]] 93 | 94 | # Cut the Diamond 95 | diamondCut.diamondCut( 96 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 97 | ) 98 | 99 | selectors = getSelectors(test2) 100 | z = diamondLoupe.facetFunctionSelectors(test2.address) 101 | assert len(z) == 20 102 | assert selectors == list(z) 103 | 104 | 105 | def test_test2facet_call(accounts, deployed_contracts, Test2Facet): 106 | owner = accounts[0] 107 | diamond = deployed_contracts[0] 108 | test2AtDiamond = Contract.from_abi("Test2Facet", diamond.address, Test2Facet.abi) 109 | test2AtDiamond.test2Func9({"from": owner}) 110 | 111 | 112 | def test_remove_some_test2facet_functions(accounts, deployed_contracts): 113 | owner = accounts[0] 114 | diamond = deployed_contracts[0] 115 | diamondCutFacet = deployed_contracts[1] 116 | diamondLoupeFacet = deployed_contracts[2] 117 | test2 = deployed_contracts[5] 118 | 119 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 120 | diamondLoupe = Contract.from_abi( 121 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 122 | ) 123 | 124 | functions_to_remove = [ 125 | "test2Func1()", 126 | "test2Func5()", 127 | "test2Func6()", 128 | "test2Func19()", 129 | "test2Func20()", 130 | ] 131 | selectors_to_remove = [ 132 | hashFunctionSignature(fname) for fname in functions_to_remove 133 | ] 134 | 135 | cut = [[zeroAddress, facetCutAction["Remove"], selectors_to_remove]] 136 | 137 | # Cut the Diamond 138 | diamondCut.diamondCut( 139 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 140 | ) 141 | function_selectors = diamondLoupe.facetFunctionSelectors(test2.address) 142 | assert len(function_selectors) == 15 143 | for fsr in selectors_to_remove: 144 | if fsr in function_selectors: 145 | assert False 146 | 147 | 148 | def test_remove_some_test1facet_functions(accounts, deployed_contracts): 149 | owner = accounts[0] 150 | diamond = deployed_contracts[0] 151 | diamondCutFacet = deployed_contracts[1] 152 | diamondLoupeFacet = deployed_contracts[2] 153 | test1 = deployed_contracts[4] 154 | 155 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 156 | diamondLoupe = Contract.from_abi( 157 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 158 | ) 159 | 160 | functions_to_remove = [ 161 | "test1Func1()", 162 | "test1Func5()", 163 | "test1Func6()", 164 | "test1Func11()", 165 | "test1Func19()", 166 | "test1Func20()", 167 | ] 168 | selectors_to_remove = [ 169 | hashFunctionSignature(fname) for fname in functions_to_remove 170 | ] 171 | 172 | cut = [[zeroAddress, facetCutAction["Remove"], selectors_to_remove]] 173 | 174 | # Cut the Diamond 175 | diamondCut.diamondCut( 176 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 177 | ) 178 | function_selectors = diamondLoupe.facetFunctionSelectors(test1.address) 179 | assert len(function_selectors) == 15 180 | for fsr in selectors_to_remove: 181 | if fsr in function_selectors: 182 | assert False 183 | 184 | 185 | def test_remove_all_facets_and_functions_except_diamondcut_and_facets( 186 | accounts, deployed_contracts 187 | ): 188 | owner = accounts[0] 189 | diamond = deployed_contracts[0] 190 | diamondCutFacet = deployed_contracts[1] 191 | diamondLoupeFacet = deployed_contracts[2] 192 | 193 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 194 | diamondLoupe = Contract.from_abi( 195 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 196 | ) 197 | 198 | # get all the selectors 199 | facets_with_selectors = diamondLoupe.facets() 200 | all_selectors = [] 201 | for x in facets_with_selectors: 202 | all_selectors.extend(list(x[1])) 203 | 204 | # remove ['facets()', 'diamondCut((address,uint8,bytes4[])[],address,bytes)']) from selectors 205 | selectors_to_keep = [ 206 | hashFunctionSignature("facets()"), 207 | hashFunctionSignature("diamondCut((address,uint8,bytes4[])[],address,bytes)"), 208 | ] 209 | 210 | for s in selectors_to_keep: 211 | all_selectors.remove(s) 212 | 213 | # Cut the Diamond 214 | cut = [[zeroAddress, facetCutAction["Remove"], all_selectors]] 215 | 216 | # Cut the Diamond 217 | diamondCut.diamondCut( 218 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 219 | ) 220 | 221 | facets = diamondLoupe.facets() 222 | 223 | assert len(facets) == 2 224 | -------------------------------------------------------------------------------- /diamond-3-brownie/tests/diamond_cut_test1_test2_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import Contract 3 | from scripts.helpers import ( 4 | hashFunctionSignature, 5 | getSelectors, 6 | zeroAddress, 7 | facetCutAction, 8 | ) 9 | 10 | 11 | def test_add_test1_and_test2_facet_functions(accounts, deployed_contracts): 12 | owner = accounts[0] 13 | 14 | diamond = deployed_contracts[0] 15 | diamondCutFacet = deployed_contracts[1] 16 | diamondLoupeFacet = deployed_contracts[2] 17 | test1Facet = deployed_contracts[4] 18 | test2Facet = deployed_contracts[5] 19 | 20 | test1FacetSelectors = getSelectors(test1Facet) 21 | test1FacetSelectors.remove(hashFunctionSignature("supportsInterface(bytes4)")) 22 | 23 | cut = [ 24 | [test1Facet.address, facetCutAction["Add"], test1FacetSelectors], 25 | [test2Facet.address, facetCutAction["Add"], getSelectors(test2Facet)], 26 | ] 27 | 28 | diamondCut = Contract.from_abi("DiamondCut", diamond.address, diamondCutFacet.abi) 29 | 30 | diamondCut.diamondCut( 31 | cut, zeroAddress, bytes(0), {"from": owner, "gasLimit": 800000} 32 | ) 33 | 34 | diamondLoupe = Contract.from_abi( 35 | "DiamondLoupe", diamond.address, diamondLoupeFacet.abi 36 | ) 37 | 38 | facets = diamondLoupe.facets() 39 | 40 | assert len(facets) == 5 41 | -------------------------------------------------------------------------------- /eternal-storage/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | *.vy linguist-language=Python 3 | -------------------------------------------------------------------------------- /eternal-storage/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .env 3 | .history 4 | .hypothesis/ 5 | build/ 6 | reports/ 7 | -------------------------------------------------------------------------------- /eternal-storage/contracts/EternalStorage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | contract EternalStorage { 6 | 7 | mapping(bytes32 => uint256) internal uintStorage; 8 | mapping(bytes32 => bool) internal boolStorage; 9 | 10 | function getUIntValue(bytes32 record) public view returns (uint) { 11 | return uintStorage[record]; 12 | } 13 | 14 | function setUintValue(bytes32 record, uint256 value) public { 15 | uintStorage[record] = value; 16 | } 17 | 18 | function getBoolValue(bytes32 record) public view returns (bool) { 19 | return boolStorage[record]; 20 | } 21 | 22 | function setBoolValue(bytes32 record, bool value) public { 23 | boolStorage[record] = value; 24 | } 25 | } -------------------------------------------------------------------------------- /eternal-storage/contracts/SocialLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "./EternalStorage.sol"; 6 | 7 | library SocialLib { 8 | 9 | function likeURL(address _eternalStorage, string memory _url) public { 10 | uint256 currentLikes = getLikesOfURL(_eternalStorage, _url); 11 | bytes32 slot = keccak256(abi.encodePacked(_url, 'likes')); 12 | EternalStorage(_eternalStorage).setUintValue(slot, currentLikes + 1); 13 | } 14 | 15 | function getLikesOfURL(address _eternalStorage, string memory _url) public view returns(uint256) { 16 | bytes32 slot = keccak256(abi.encodePacked(_url, 'likes')); 17 | return EternalStorage(_eternalStorage).getUIntValue(slot); 18 | } 19 | 20 | function disLikeURL(address _eternalStorage, string memory _url) public { 21 | uint256 currentDisLikes = getDisLikesOfURL(_eternalStorage, _url); 22 | bytes32 slot = keccak256(abi.encodePacked(_url, 'dislikes')); 23 | EternalStorage(_eternalStorage).setUintValue(slot, currentDisLikes + 1); 24 | } 25 | 26 | function getDisLikesOfURL(address _eternalStorage, string memory _url) public view returns(uint256) { 27 | bytes32 slot = keccak256(abi.encodePacked(_url, 'dislikes')); 28 | return EternalStorage(_eternalStorage).getUIntValue(slot); 29 | } 30 | } -------------------------------------------------------------------------------- /eternal-storage/contracts/SocialLibV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "./EternalStorage.sol"; 6 | 7 | library SocialLibV2 { 8 | 9 | // likes 10 | function likeURL(address _eternalStorage, string memory _url) public { 11 | uint256 currentLikes = getLikesOfURL(_eternalStorage, _url); 12 | bytes32 slot = keccak256(abi.encodePacked(_url, 'likes')); 13 | EternalStorage(_eternalStorage).setUintValue(slot, currentLikes + 1); 14 | } 15 | 16 | function getLikesOfURL(address _eternalStorage, string memory _url) public view returns(uint256) { 17 | bytes32 slot = keccak256(abi.encodePacked(_url, 'likes')); 18 | return EternalStorage(_eternalStorage).getUIntValue(slot); 19 | } 20 | 21 | function setUserHasLikedURL(address _eternalStorage, string memory _url) public { 22 | bytes32 slot = keccak256(abi.encodePacked(_url, 'likes', msg.sender)); 23 | EternalStorage(_eternalStorage).setBoolValue(slot, true); 24 | } 25 | 26 | function getUserHasLikedURL(address _eternalStorage, string memory _url) public view returns(bool) { 27 | bytes32 slot = keccak256(abi.encodePacked(_url, 'likes', msg.sender)); 28 | return EternalStorage(_eternalStorage).getBoolValue(slot); 29 | } 30 | 31 | // dislikes 32 | function disLikeURL(address _eternalStorage, string memory _url) public { 33 | uint256 currentDisLikes = getDisLikesOfURL(_eternalStorage, _url); 34 | bytes32 slot = keccak256(abi.encodePacked(_url, 'dislikes')); 35 | EternalStorage(_eternalStorage).setUintValue(slot, currentDisLikes + 1); 36 | } 37 | 38 | function getDisLikesOfURL(address _eternalStorage, string memory _url) public view returns(uint256) { 39 | bytes32 slot = keccak256(abi.encodePacked(_url, 'dislikes')); 40 | return EternalStorage(_eternalStorage).getUIntValue(slot); 41 | } 42 | 43 | function setUserHasDisLikedURL(address _eternalStorage, string memory _url) public { 44 | bytes32 slot = keccak256(abi.encodePacked(_url, 'dislikes', msg.sender)); 45 | EternalStorage(_eternalStorage).setBoolValue(slot, true); 46 | } 47 | 48 | function getUserHasDisLikedURL(address _eternalStorage, string memory _url) public view returns(bool) { 49 | bytes32 slot = keccak256(abi.encodePacked(_url, 'dislikes', msg.sender)); 50 | return EternalStorage(_eternalStorage).getBoolValue(slot); 51 | } 52 | } -------------------------------------------------------------------------------- /eternal-storage/contracts/SocialSite.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "./SocialLib.sol"; 6 | 7 | contract SocialSite { 8 | 9 | using SocialLib for address; 10 | address eternalStorage; 11 | 12 | constructor(address _eternalStorage) { 13 | eternalStorage = _eternalStorage; 14 | } 15 | 16 | function like(string memory _url) public { 17 | eternalStorage.likeURL(_url); 18 | } 19 | 20 | function disLike(string memory _url) public { 21 | eternalStorage.disLikeURL(_url); 22 | } 23 | 24 | function getLikes(string memory _url) public view returns(uint256) { 25 | return eternalStorage.getLikesOfURL(_url); 26 | } 27 | 28 | function getDisLikes(string memory _url) public view returns(uint256) { 29 | return eternalStorage.getDisLikesOfURL(_url); 30 | } 31 | } -------------------------------------------------------------------------------- /eternal-storage/contracts/SocialSiteV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.9; 4 | 5 | import "./SocialLibV2.sol"; 6 | 7 | contract SocialSiteV2 { 8 | 9 | using SocialLibV2 for address; 10 | address eternalStorage; 11 | 12 | constructor(address _eternalStorage) { 13 | eternalStorage = _eternalStorage; 14 | } 15 | 16 | function like(string memory _url) public { 17 | require(eternalStorage.getUserHasLikedURL(_url) == false, "ERROR: User has already liked"); 18 | eternalStorage.setUserHasLikedURL(_url); 19 | eternalStorage.likeURL(_url); 20 | } 21 | 22 | function disLike(string memory _url) public { 23 | require(eternalStorage.getUserHasDisLikedURL(_url) == false, "ERROR: User has already disliked"); 24 | eternalStorage.setUserHasDisLikedURL(_url); 25 | eternalStorage.disLikeURL(_url); 26 | } 27 | 28 | function getLikes(string memory _url) public view returns(uint256) { 29 | return eternalStorage.getLikesOfURL(_url); 30 | } 31 | 32 | function getDisLikes(string memory _url) public view returns(uint256) { 33 | return eternalStorage.getDisLikesOfURL(_url); 34 | } 35 | } -------------------------------------------------------------------------------- /eternal-storage/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from brownie import EternalStorage, SocialLib, SocialSite, SocialLibV2, SocialSiteV2 3 | 4 | 5 | @pytest.fixture(scope="module") 6 | def social_site_contract(accounts): 7 | owner = accounts[0] 8 | es = EternalStorage.deploy({"from": owner}) 9 | SocialLib.deploy({"from": owner}) 10 | ss = SocialSite.deploy(es.address, {"from": owner}) 11 | return ss 12 | 13 | 14 | @pytest.fixture(scope="module") 15 | def social_site_contract_v2(accounts): 16 | owner = accounts[0] 17 | es = EternalStorage.deploy({"from": owner}) 18 | 19 | SocialLib.deploy({"from": owner}) 20 | ssv1 = SocialSite.deploy(es.address, {"from": owner}) 21 | 22 | SocialLibV2.deploy({"from": owner}) 23 | ssv2 = SocialSiteV2.deploy(es.address, {"from": owner}) 24 | return ssv1, ssv2 25 | -------------------------------------------------------------------------------- /eternal-storage/tests/test_social_site.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_we_can_like_an_url(social_site_contract): 5 | social_site_contract.like("https://orno.io") 6 | social_site_contract.like("https://orno.io") 7 | social_site_contract.like("https://orno.io") 8 | social_site_contract.like("https://orno.io") 9 | 10 | num_likes = social_site_contract.getLikes("https://orno.io") 11 | 12 | assert num_likes == 4 13 | 14 | assert social_site_contract.getLikes("https://youtube.com") == 0 15 | 16 | 17 | def test_we_can_dislike_an_url(social_site_contract): 18 | social_site_contract.disLike("https://youtube.com") 19 | social_site_contract.disLike("https://youtube.com") 20 | social_site_contract.disLike("https://youtube.com") 21 | 22 | assert social_site_contract.getDisLikes("https://youtube.com") == 3 23 | 24 | 25 | def test_we_can_mix_likes_and_dislikes(social_site_contract): 26 | likes = social_site_contract.getLikes("https://orno.io") 27 | disLikes = social_site_contract.getDisLikes("https://orno.io") 28 | 29 | social_site_contract.like("https://orno.io") 30 | social_site_contract.like("https://orno.io") 31 | social_site_contract.disLike("https://orno.io") 32 | social_site_contract.disLike("https://orno.io") 33 | social_site_contract.like("https://orno.io") 34 | social_site_contract.like("https://orno.io") 35 | 36 | assert social_site_contract.getLikes("https://orno.io") == likes + 4 37 | assert social_site_contract.getDisLikes("https://orno.io") == disLikes + 2 38 | -------------------------------------------------------------------------------- /eternal-storage/tests/test_social_site_v2.py: -------------------------------------------------------------------------------- 1 | import brownie 2 | 3 | 4 | def test_storage_keeps_values_if_switched_to_v2(social_site_contract_v2): 5 | orig = social_site_contract_v2[0] 6 | updated = social_site_contract_v2[1] 7 | 8 | orig.like("https://orno.io/article") 9 | orig.disLike("https://myblog.io/article") 10 | 11 | assert orig.getLikes("https://orno.io/article") == 1 12 | assert updated.getLikes("https://orno.io/article") == 1 13 | 14 | assert orig.getDisLikes("https://myblog.io/article") == 1 15 | assert updated.getDisLikes("https://myblog.io/article") == 1 16 | 17 | 18 | def test_v2_only_allows_like_once(social_site_contract_v2): 19 | orig = social_site_contract_v2[0] 20 | updated = social_site_contract_v2[1] 21 | 22 | updated.like("https://orno.io/article") 23 | 24 | assert orig.getLikes("https://orno.io/article") == 2 25 | assert updated.getLikes("https://orno.io/article") == 2 26 | 27 | with brownie.reverts("ERROR: User has already liked"): 28 | updated.like("https://orno.io/article") 29 | 30 | 31 | def test_v2_only_allows_dislike_once(social_site_contract_v2): 32 | orig = social_site_contract_v2[0] 33 | updated = social_site_contract_v2[1] 34 | 35 | assert orig.getDisLikes("https://myblog.io/article") == 1 36 | assert updated.getDisLikes("https://myblog.io/article") == 1 37 | 38 | updated.disLike("https://myblog.io/article") 39 | 40 | with brownie.reverts("ERROR: User has already disliked"): 41 | updated.disLike("https://myblog.io/article") 42 | --------------------------------------------------------------------------------