From 68e48d80abc9a630e39291667b2cf190d37f36fc Mon Sep 17 00:00:00 2001
From: Florian <f.leger@intent-technologies.eu>
Date: Sun, 12 Jun 2022 20:07:14 +0200
Subject: [PATCH] core: initialize the project

- Implementation of the [EIP-2535 Diamonds](https://github.com/ethereum/EIPs/issues/2535) standard by using the [diamond-1-hardhat](https://github.com/mudgen/diamond-1-hardhat) boilerplate
- Migrated to Typescript
- Added empty Facets and some declarations in the AppStorage
- Updated dependencies
- Added typechain-types for a better contracts integration in TypeScript
---
 contracts/GuildDiamond.sol                    |  65 +++++
 contracts/facets/DiamondCutFacet.sol          |  27 ++
 contracts/facets/DiamondLoupeFacet.sol        | 149 +++++++++++
 contracts/facets/LootboxFacet.sol             |  15 ++
 contracts/facets/OwnershipFacet.sol           |  16 ++
 contracts/facets/PlayerFacet.sol              |  20 ++
 contracts/facets/RulesFacet.sol               |   9 +
 contracts/facets/TreasuryFacet.sol            |   9 +
 contracts/interfaces/IDiamondCut.sol          |  32 +++
 contracts/interfaces/IDiamondLoupe.sol        |  38 +++
 contracts/interfaces/IERC165.sol              |  12 +
 contracts/interfaces/IERC173.sol              |  19 ++
 contracts/libraries/LibAppStorage.sol         |  28 ++
 contracts/libraries/LibDiamond.sol            | 162 +++++++++++
 contracts/upgradeInitializers/DiamondInit.sol |  27 ++
 hardhat.config.ts                             |  33 +++
 package.json                                  |  43 +++
 scripts/deploy.ts                             |  81 ++++++
 scripts/libraries/diamond.ts                  |  87 ++++++
 test/cacheBugTest.ts                          | 112 ++++++++
 test/diamondTest.ts                           | 251 ++++++++++++++++++
 tsconfig.json                                 |  14 +
 types.ts                                      |  10 +
 23 files changed, 1259 insertions(+)
 create mode 100644 contracts/GuildDiamond.sol
 create mode 100644 contracts/facets/DiamondCutFacet.sol
 create mode 100644 contracts/facets/DiamondLoupeFacet.sol
 create mode 100644 contracts/facets/LootboxFacet.sol
 create mode 100644 contracts/facets/OwnershipFacet.sol
 create mode 100644 contracts/facets/PlayerFacet.sol
 create mode 100644 contracts/facets/RulesFacet.sol
 create mode 100644 contracts/facets/TreasuryFacet.sol
 create mode 100644 contracts/interfaces/IDiamondCut.sol
 create mode 100644 contracts/interfaces/IDiamondLoupe.sol
 create mode 100644 contracts/interfaces/IERC165.sol
 create mode 100644 contracts/interfaces/IERC173.sol
 create mode 100644 contracts/libraries/LibAppStorage.sol
 create mode 100644 contracts/libraries/LibDiamond.sol
 create mode 100644 contracts/upgradeInitializers/DiamondInit.sol
 create mode 100644 hardhat.config.ts
 create mode 100644 package.json
 create mode 100644 scripts/deploy.ts
 create mode 100644 scripts/libraries/diamond.ts
 create mode 100644 test/cacheBugTest.ts
 create mode 100644 test/diamondTest.ts
 create mode 100644 tsconfig.json
 create mode 100644 types.ts

diff --git a/contracts/GuildDiamond.sol b/contracts/GuildDiamond.sol
new file mode 100644
index 0000000..1b71b8c
--- /dev/null
+++ b/contracts/GuildDiamond.sol
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+/******************************************************************************\
+* Author: Nick Mudge <nick@perfectabstractions.com> (https://twitter.com/mudgen)
+* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
+*
+* Implementation of a diamond.
+/******************************************************************************/
+
+import { LibDiamond } from "./libraries/LibDiamond.sol";
+import { IDiamondCut } from "./interfaces/IDiamondCut.sol";
+import { AppStorage } from "./libraries/LibAppStorage.sol";
+
+contract GuildDiamond {
+    AppStorage s;
+
+    constructor(address _contractOwner, address _diamondCutFacet) payable {
+        LibDiamond.setContractOwner(_contractOwner);
+
+        // Add the diamondCut external function from the diamondCutFacet
+        IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);
+        bytes4[] memory functionSelectors = new bytes4[](1);
+        functionSelectors[0] = IDiamondCut.diamondCut.selector;
+        cut[0] = IDiamondCut.FacetCut({
+            facetAddress: _diamondCutFacet,
+            action: IDiamondCut.FacetCutAction.Add,
+            functionSelectors: functionSelectors
+        });
+        LibDiamond.diamondCut(cut, address(0), "");
+    }
+
+    // Find facet for function that is called and execute the
+    // function if a facet is found and return any value.
+    fallback() external payable {
+        LibDiamond.DiamondStorage storage ds;
+        bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION;
+        // get diamond storage
+        assembly {
+            ds.slot := position
+        }
+        // get facet from function selector
+        address facet = ds.facetAddressAndSelectorPosition[msg.sig].facetAddress;
+        require(facet != address(0), "Diamond: Function does not exist");
+        // Execute external function from facet using delegatecall and return any value.
+        assembly {
+            // copy function selector and any arguments
+            calldatacopy(0, 0, calldatasize())
+             // execute function call using the facet
+            let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
+            // get any return value
+            returndatacopy(0, 0, returndatasize())
+            // return any return value or error back to the caller
+            switch result
+                case 0 {
+                    revert(0, returndatasize())
+                }
+                default {
+                    return(0, returndatasize())
+                }
+        }
+    }
+
+    receive() external payable {}
+}
diff --git a/contracts/facets/DiamondCutFacet.sol b/contracts/facets/DiamondCutFacet.sol
new file mode 100644
index 0000000..bc0a69a
--- /dev/null
+++ b/contracts/facets/DiamondCutFacet.sol
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+/******************************************************************************\
+* Author: Nick Mudge <nick@perfectabstractions.com> (https://twitter.com/mudgen)
+* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
+/******************************************************************************/
+
+import { IDiamondCut } from "../interfaces/IDiamondCut.sol";
+import { LibDiamond } from "../libraries/LibDiamond.sol";
+
+contract DiamondCutFacet is IDiamondCut {
+    /// @notice Add/replace/remove any number of functions and optionally execute
+    ///         a function with delegatecall
+    /// @param _diamondCut Contains the facet addresses and function selectors
+    /// @param _init The address of the contract or facet to execute _calldata
+    /// @param _calldata A function call, including function selector and arguments
+    ///                  _calldata is executed with delegatecall on _init
+    function diamondCut(
+        FacetCut[] calldata _diamondCut,
+        address _init,
+        bytes calldata _calldata
+    ) external override {
+        LibDiamond.enforceIsContractOwner();
+        LibDiamond.diamondCut(_diamondCut, _init, _calldata);
+    }
+}
diff --git a/contracts/facets/DiamondLoupeFacet.sol b/contracts/facets/DiamondLoupeFacet.sol
new file mode 100644
index 0000000..81d2c02
--- /dev/null
+++ b/contracts/facets/DiamondLoupeFacet.sol
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+/******************************************************************************\
+* Author: Nick Mudge <nick@perfectabstractions.com> (https://twitter.com/mudgen)
+* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
+/******************************************************************************/
+
+// The functions in DiamondLoupeFacet MUST be added to a diamond.
+// The EIP-2535 Diamond standard requires these functions.
+
+import { LibDiamond } from  "../libraries/LibDiamond.sol";
+import { IDiamondLoupe } from "../interfaces/IDiamondLoupe.sol";
+import { IERC165 } from "../interfaces/IERC165.sol";
+
+contract DiamondLoupeFacet is IDiamondLoupe, IERC165 {
+    // Diamond Loupe Functions
+    ////////////////////////////////////////////////////////////////////
+    /// These functions are expected to be called frequently by tools.
+    //
+    // struct Facet {
+    //     address facetAddress;
+    //     bytes4[] functionSelectors;
+    // }
+    /// @notice Gets all facets and their selectors.
+    /// @return facets_ Facet
+    function facets() external override view returns (Facet[] memory facets_) {
+        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
+        uint256 selectorCount = ds.selectors.length;
+        // create an array set to the maximum size possible
+        facets_ = new Facet[](selectorCount);
+        // create an array for counting the number of selectors for each facet
+        uint8[] memory numFacetSelectors = new uint8[](selectorCount);
+        // total number of facets
+        uint256 numFacets;
+        // loop through function selectors
+        for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) {
+            bytes4 selector = ds.selectors[selectorIndex];
+            address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress;
+            bool continueLoop = false;
+            // find the functionSelectors array for selector and add selector to it
+            for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) {
+                if (facets_[facetIndex].facetAddress == facetAddress_) {
+                    facets_[facetIndex].functionSelectors[numFacetSelectors[facetIndex]] = selector;
+                    // probably will never have more than 256 functions from one facet contract
+                    require(numFacetSelectors[facetIndex] < 255);
+                    numFacetSelectors[facetIndex]++;
+                    continueLoop = true;
+                    break;
+                }
+            }
+            // if functionSelectors array exists for selector then continue loop
+            if (continueLoop) {
+                continueLoop = false;
+                continue;
+            }
+            // create a new functionSelectors array for selector
+            facets_[numFacets].facetAddress = facetAddress_;
+            facets_[numFacets].functionSelectors = new bytes4[](selectorCount);
+            facets_[numFacets].functionSelectors[0] = selector;
+            numFacetSelectors[numFacets] = 1;
+            numFacets++;
+        }
+        for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) {
+            uint256 numSelectors = numFacetSelectors[facetIndex];
+            bytes4[] memory selectors = facets_[facetIndex].functionSelectors;
+            // setting the number of selectors
+            assembly {
+                mstore(selectors, numSelectors)
+            }
+        }
+        // setting the number of facets
+        assembly {
+            mstore(facets_, numFacets)
+        }
+    }
+
+    /// @notice Gets all the function selectors supported by a specific facet.
+    /// @param _facet The facet address.
+    /// @return _facetFunctionSelectors The selectors associated with a facet address.
+    function facetFunctionSelectors(address _facet) external override view returns (bytes4[] memory _facetFunctionSelectors) {
+        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
+        uint256 selectorCount = ds.selectors.length;
+        uint256 numSelectors;
+        _facetFunctionSelectors = new bytes4[](selectorCount);
+        // loop through function selectors
+        for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) {
+            bytes4 selector = ds.selectors[selectorIndex];
+            address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress;
+            if (_facet == facetAddress_) {
+                _facetFunctionSelectors[numSelectors] = selector;
+                numSelectors++;
+            }
+        }
+        // Set the number of selectors in the array
+        assembly {
+            mstore(_facetFunctionSelectors, numSelectors)
+        }
+    }
+
+    /// @notice Get all the facet addresses used by a diamond.
+    /// @return facetAddresses_
+    function facetAddresses() external override view returns (address[] memory facetAddresses_) {
+        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
+        uint256 selectorCount = ds.selectors.length;
+        // create an array set to the maximum size possible
+        facetAddresses_ = new address[](selectorCount);
+        uint256 numFacets;
+        // loop through function selectors
+        for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) {
+            bytes4 selector = ds.selectors[selectorIndex];
+            address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress;
+            bool continueLoop = false;
+            // see if we have collected the address already and break out of loop if we have
+            for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) {
+                if (facetAddress_ == facetAddresses_[facetIndex]) {
+                    continueLoop = true;
+                    break;
+                }
+            }
+            // continue loop if we already have the address
+            if (continueLoop) {
+                continueLoop = false;
+                continue;
+            }
+            // include address
+            facetAddresses_[numFacets] = facetAddress_;
+            numFacets++;
+        }
+        // Set the number of facet addresses in the array
+        assembly {
+            mstore(facetAddresses_, numFacets)
+        }
+    }
+
+    /// @notice Gets the facet address that supports the given selector.
+    /// @dev If facet is not found return address(0).
+    /// @param _functionSelector The function selector.
+    /// @return facetAddress_ The facet address.
+    function facetAddress(bytes4 _functionSelector) external override view returns (address facetAddress_) {
+        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
+        facetAddress_ = ds.facetAddressAndSelectorPosition[_functionSelector].facetAddress;
+    }
+
+    // This implements ERC-165.
+    function supportsInterface(bytes4 _interfaceId) external override view returns (bool) {
+        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
+        return ds.supportedInterfaces[_interfaceId];
+    }
+}
diff --git a/contracts/facets/LootboxFacet.sol b/contracts/facets/LootboxFacet.sol
new file mode 100644
index 0000000..109ffd5
--- /dev/null
+++ b/contracts/facets/LootboxFacet.sol
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AppStorage} from "../libraries/LibAppStorage.sol";
+
+contract LootboxFacet {
+    AppStorage internal s;
+    event OpenLootboxEvent(address player, uint32 lootboxId);
+
+    function openLootbox(address player, uint32 lootboxId) external {
+        require(player != address(0), "Player address is not valid");
+        require(s.playersExists[player], "Player does not exist");
+    }
+
+}
diff --git a/contracts/facets/OwnershipFacet.sol b/contracts/facets/OwnershipFacet.sol
new file mode 100644
index 0000000..45cba09
--- /dev/null
+++ b/contracts/facets/OwnershipFacet.sol
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { LibDiamond } from "../libraries/LibDiamond.sol";
+import { IERC173 } from "../interfaces/IERC173.sol";
+
+contract OwnershipFacet is IERC173 {
+    function transferOwnership(address _newOwner) external override {
+        LibDiamond.enforceIsContractOwner();
+        LibDiamond.setContractOwner(_newOwner);
+    }
+
+    function owner() external override view returns (address owner_) {
+        owner_ = LibDiamond.contractOwner();
+    }
+}
diff --git a/contracts/facets/PlayerFacet.sol b/contracts/facets/PlayerFacet.sol
new file mode 100644
index 0000000..3d22316
--- /dev/null
+++ b/contracts/facets/PlayerFacet.sol
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AppStorage} from "../libraries/LibAppStorage.sol";
+
+contract PlayerFacet {
+    AppStorage internal s;
+    event LevelUpEvent(address player, uint16 level);
+
+    function levelUp(address player) external {
+        require(player != address(0), "Player address is not valid");
+        //require(s.playersExists[player], "Player does not exist");
+        //s.players[player].level++;
+    }
+
+    // for testing purposes
+    function supportsInterface(bytes4 _interfaceID) external view returns (bool) {
+        return false;
+    }
+}
diff --git a/contracts/facets/RulesFacet.sol b/contracts/facets/RulesFacet.sol
new file mode 100644
index 0000000..d6081ae
--- /dev/null
+++ b/contracts/facets/RulesFacet.sol
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AppStorage} from "../libraries/LibAppStorage.sol";
+
+contract RulesFacet {
+    AppStorage internal s;
+
+}
diff --git a/contracts/facets/TreasuryFacet.sol b/contracts/facets/TreasuryFacet.sol
new file mode 100644
index 0000000..541b72e
--- /dev/null
+++ b/contracts/facets/TreasuryFacet.sol
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AppStorage} from "../libraries/LibAppStorage.sol";
+
+contract TreasuryFacet {
+    AppStorage internal s;
+
+}
diff --git a/contracts/interfaces/IDiamondCut.sol b/contracts/interfaces/IDiamondCut.sol
new file mode 100644
index 0000000..2972f69
--- /dev/null
+++ b/contracts/interfaces/IDiamondCut.sol
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+/******************************************************************************\
+* Author: Nick Mudge <nick@perfectabstractions.com> (https://twitter.com/mudgen)
+* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
+/******************************************************************************/
+
+interface IDiamondCut {
+    enum FacetCutAction {Add, Replace, Remove}
+    // Add=0, Replace=1, Remove=2
+
+    struct FacetCut {
+        address facetAddress;
+        FacetCutAction action;
+        bytes4[] functionSelectors;
+    }
+
+    /// @notice Add/replace/remove any number of functions and optionally execute
+    ///         a function with delegatecall
+    /// @param _diamondCut Contains the facet addresses and function selectors
+    /// @param _init The address of the contract or facet to execute _calldata
+    /// @param _calldata A function call, including function selector and arguments
+    ///                  _calldata is executed with delegatecall on _init
+    function diamondCut(
+        FacetCut[] calldata _diamondCut,
+        address _init,
+        bytes calldata _calldata
+    ) external;
+
+    event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
+}
diff --git a/contracts/interfaces/IDiamondLoupe.sol b/contracts/interfaces/IDiamondLoupe.sol
new file mode 100644
index 0000000..c3b2570
--- /dev/null
+++ b/contracts/interfaces/IDiamondLoupe.sol
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+/******************************************************************************\
+* Author: Nick Mudge <nick@perfectabstractions.com> (https://twitter.com/mudgen)
+* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
+/******************************************************************************/
+
+// A loupe is a small magnifying glass used to look at diamonds.
+// These functions look at diamonds
+interface IDiamondLoupe {
+    /// These functions are expected to be called frequently
+    /// by tools.
+
+    struct Facet {
+        address facetAddress;
+        bytes4[] functionSelectors;
+    }
+
+    /// @notice Gets all facet addresses and their four byte function selectors.
+    /// @return facets_ Facet
+    function facets() external view returns (Facet[] memory facets_);
+
+    /// @notice Gets all the function selectors supported by a specific facet.
+    /// @param _facet The facet address.
+    /// @return facetFunctionSelectors_
+    function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_);
+
+    /// @notice Get all the facet addresses used by a diamond.
+    /// @return facetAddresses_
+    function facetAddresses() external view returns (address[] memory facetAddresses_);
+
+    /// @notice Gets the facet that supports the given selector.
+    /// @dev If facet is not found return address(0).
+    /// @param _functionSelector The function selector.
+    /// @return facetAddress_ The facet address.
+    function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_);
+}
diff --git a/contracts/interfaces/IERC165.sol b/contracts/interfaces/IERC165.sol
new file mode 100644
index 0000000..04b7bcc
--- /dev/null
+++ b/contracts/interfaces/IERC165.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+interface IERC165 {
+    /// @notice Query if a contract implements an interface
+    /// @param interfaceId The interface identifier, as specified in ERC-165
+    /// @dev Interface identification is specified in ERC-165. This function
+    ///  uses less than 30,000 gas.
+    /// @return `true` if the contract implements `interfaceID` and
+    ///  `interfaceID` is not 0xffffffff, `false` otherwise
+    function supportsInterface(bytes4 interfaceId) external view returns (bool);
+}
diff --git a/contracts/interfaces/IERC173.sol b/contracts/interfaces/IERC173.sol
new file mode 100644
index 0000000..a708048
--- /dev/null
+++ b/contracts/interfaces/IERC173.sol
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+/// @title ERC-173 Contract Ownership Standard
+///  Note: the ERC-165 identifier for this interface is 0x7f5828d0
+/* is ERC165 */
+interface IERC173 {
+    /// @dev This emits when ownership of a contract changes.
+    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
+
+    /// @notice Get the address of the owner
+    /// @return owner_ The address of the owner.
+    function owner() external view returns (address owner_);
+
+    /// @notice Set the address of the new owner of the contract
+    /// @dev Set _newOwner to address(0) to renounce any ownership.
+    /// @param _newOwner The address of the new owner of the contract
+    function transferOwnership(address _newOwner) external;
+}
diff --git a/contracts/libraries/LibAppStorage.sol b/contracts/libraries/LibAppStorage.sol
new file mode 100644
index 0000000..dc35287
--- /dev/null
+++ b/contracts/libraries/LibAppStorage.sol
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.13;
+
+    struct AppStorage {
+        address gallionLabs;
+        address guildTokenContract;
+        address guildContract;
+        mapping(address => bool) playersExists;
+        mapping(address => Player) players;
+        mapping(uint32 => Lootbox) lootboxes;
+    }
+
+    struct Player {
+        address owner;
+        uint16 level;
+    }
+
+    struct Lootbox {
+        address owner;
+        Rarity rarity;
+    }
+
+    enum Rarity {
+        common,
+        rare,
+        epic,
+        legendary
+    }
diff --git a/contracts/libraries/LibDiamond.sol b/contracts/libraries/LibDiamond.sol
new file mode 100644
index 0000000..16ea39c
--- /dev/null
+++ b/contracts/libraries/LibDiamond.sol
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+/******************************************************************************\
+* Author: Nick Mudge <nick@perfectabstractions.com> (https://twitter.com/mudgen)
+* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
+/******************************************************************************/
+import { IDiamondCut } from "../interfaces/IDiamondCut.sol";
+
+// Remember to add the loupe functions from DiamondLoupeFacet to the diamond.
+// The loupe functions are required by the EIP2535 Diamonds standard
+
+library LibDiamond {
+    bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
+
+    struct FacetAddressAndSelectorPosition {
+        address facetAddress;
+        uint16 selectorPosition;
+    }
+
+    struct DiamondStorage {
+        // function selector => facet address and selector position in selectors array
+        mapping(bytes4 => FacetAddressAndSelectorPosition) facetAddressAndSelectorPosition;
+        bytes4[] selectors;
+        mapping(bytes4 => bool) supportedInterfaces;
+        // owner of the contract
+        address contractOwner;
+    }
+
+    function diamondStorage() internal pure returns (DiamondStorage storage ds) {
+        bytes32 position = DIAMOND_STORAGE_POSITION;
+        assembly {
+            ds.slot := position
+        }
+    }
+
+    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
+
+    function setContractOwner(address _newOwner) internal {
+        DiamondStorage storage ds = diamondStorage();
+        address previousOwner = ds.contractOwner;
+        ds.contractOwner = _newOwner;
+        emit OwnershipTransferred(previousOwner, _newOwner);
+    }
+
+    function contractOwner() internal view returns (address contractOwner_) {
+        contractOwner_ = diamondStorage().contractOwner;
+    }
+
+    function enforceIsContractOwner() internal view {
+        require(msg.sender == diamondStorage().contractOwner, "LibDiamond: Must be contract owner");
+    }
+
+    event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata);
+
+    // Internal function version of diamondCut
+    function diamondCut(
+        IDiamondCut.FacetCut[] memory _diamondCut,
+        address _init,
+        bytes memory _calldata
+    ) internal {
+        for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) {
+            IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action;
+            if (action == IDiamondCut.FacetCutAction.Add) {
+                addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
+            } else if (action == IDiamondCut.FacetCutAction.Replace) {
+                replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
+            } else if (action == IDiamondCut.FacetCutAction.Remove) {
+                removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
+            } else {
+                revert("LibDiamondCut: Incorrect FacetCutAction");
+            }
+        }
+        emit DiamondCut(_diamondCut, _init, _calldata);
+        initializeDiamondCut(_init, _calldata);
+    }
+
+    function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
+        require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut");
+        DiamondStorage storage ds = diamondStorage();
+        uint16 selectorCount = uint16(ds.selectors.length);
+        require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)");
+        enforceHasContractCode(_facetAddress, "LibDiamondCut: Add facet has no code");
+        for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) {
+            bytes4 selector = _functionSelectors[selectorIndex];
+            address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector].facetAddress;
+            require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists");
+            ds.facetAddressAndSelectorPosition[selector] = FacetAddressAndSelectorPosition(_facetAddress, selectorCount);
+            ds.selectors.push(selector);
+            selectorCount++;
+        }
+    }
+
+    function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
+        require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut");
+        DiamondStorage storage ds = diamondStorage();
+        require(_facetAddress != address(0), "LibDiamondCut: Replace facet can't be address(0)");
+        enforceHasContractCode(_facetAddress, "LibDiamondCut: Replace facet has no code");
+        for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) {
+            bytes4 selector = _functionSelectors[selectorIndex];
+            address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector].facetAddress;
+            // can't replace immutable functions -- functions defined directly in the diamond
+            require(oldFacetAddress != address(this), "LibDiamondCut: Can't replace immutable function");
+            require(oldFacetAddress != _facetAddress, "LibDiamondCut: Can't replace function with same function");
+            require(oldFacetAddress != address(0), "LibDiamondCut: Can't replace function that doesn't exist");
+            // replace old facet address
+            ds.facetAddressAndSelectorPosition[selector].facetAddress = _facetAddress;
+        }
+    }
+
+    function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
+        require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut");
+        DiamondStorage storage ds = diamondStorage();
+        uint256 selectorCount = ds.selectors.length;
+        require(_facetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)");
+        for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) {
+            bytes4 selector = _functionSelectors[selectorIndex];
+            FacetAddressAndSelectorPosition memory oldFacetAddressAndSelectorPosition = ds.facetAddressAndSelectorPosition[selector];
+            require(oldFacetAddressAndSelectorPosition.facetAddress != address(0), "LibDiamondCut: Can't remove function that doesn't exist");
+            // can't remove immutable functions -- functions defined directly in the diamond
+            require(oldFacetAddressAndSelectorPosition.facetAddress != address(this), "LibDiamondCut: Can't remove immutable function.");
+            // replace selector with last selector
+            selectorCount--;
+            if (oldFacetAddressAndSelectorPosition.selectorPosition != selectorCount) {
+                bytes4 lastSelector = ds.selectors[selectorCount];
+                ds.selectors[oldFacetAddressAndSelectorPosition.selectorPosition] = lastSelector;
+                ds.facetAddressAndSelectorPosition[lastSelector].selectorPosition = oldFacetAddressAndSelectorPosition.selectorPosition;
+            }
+            // delete last selector
+            ds.selectors.pop();
+            delete ds.facetAddressAndSelectorPosition[selector];
+        }
+    }
+
+    function initializeDiamondCut(address _init, bytes memory _calldata) internal {
+        if (_init == address(0)) {
+            require(_calldata.length == 0, "LibDiamondCut: _init is address(0) but_calldata is not empty");
+        } else {
+            require(_calldata.length > 0, "LibDiamondCut: _calldata is empty but _init is not address(0)");
+            if (_init != address(this)) {
+                enforceHasContractCode(_init, "LibDiamondCut: _init address has no code");
+            }
+            (bool success, bytes memory error) = _init.delegatecall(_calldata);
+            if (!success) {
+                if (error.length > 0) {
+                    // bubble up the error
+                    revert(string(error));
+                } else {
+                    revert("LibDiamondCut: _init function reverted");
+                }
+            }
+        }
+    }
+
+    function enforceHasContractCode(address _contract, string memory _errorMessage) internal view {
+        uint256 contractSize;
+        assembly {
+            contractSize := extcodesize(_contract)
+        }
+        require(contractSize > 0, _errorMessage);
+    }
+}
diff --git a/contracts/upgradeInitializers/DiamondInit.sol b/contracts/upgradeInitializers/DiamondInit.sol
new file mode 100644
index 0000000..e2018ff
--- /dev/null
+++ b/contracts/upgradeInitializers/DiamondInit.sol
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: MIT
+pragma solidity 0.8.13;
+
+import {LibDiamond} from "../libraries/LibDiamond.sol";
+import {IDiamondLoupe} from "../interfaces/IDiamondLoupe.sol";
+import {IDiamondCut} from "../interfaces/IDiamondCut.sol";
+import {IERC173} from "../interfaces/IERC173.sol";
+import {IERC165} from "../interfaces/IERC165.sol";
+import {AppStorage} from "../libraries/LibAppStorage.sol";
+
+contract DiamondInit {
+    AppStorage internal s;
+
+    struct Args {
+        address gallionLabs;
+    }
+
+    function init(Args memory _args) external {
+        s.gallionLabs = _args.gallionLabs;
+        // adding ERC165 data
+        LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage();
+        ds.supportedInterfaces[type(IERC165).interfaceId] = true;
+        ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true;
+        ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true;
+        ds.supportedInterfaces[type(IERC173).interfaceId] = true;
+    }
+}
diff --git a/hardhat.config.ts b/hardhat.config.ts
new file mode 100644
index 0000000..dc4bc72
--- /dev/null
+++ b/hardhat.config.ts
@@ -0,0 +1,33 @@
+
+/* global ethers task */
+import { task } from 'hardhat/config';
+import "@nomiclabs/hardhat-waffle";
+import "@typechain/hardhat";
+
+require('@nomiclabs/hardhat-waffle');
+
+// This is a sample Hardhat task. To learn how to create your own go to
+// https://hardhat.org/guides/create-task.html
+task('accounts', 'Prints the list of accounts', async (args, hre) => {
+  const accounts = await hre.ethers.getSigners()
+
+  for (const account of accounts) {
+    console.log(account.address)
+  }
+})
+
+// You need to export an object to set up your config
+// Go to https://hardhat.org/config/ to learn more
+
+/**
+ * @type import('hardhat/config').HardhatUserConfig
+ */
+module.exports = {
+  solidity: '0.8.13',
+  settings: {
+    optimizer: {
+      enabled: true,
+      runs: 200
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..ece7105
--- /dev/null
+++ b/package.json
@@ -0,0 +1,43 @@
+{
+  "name": "gallion-contract",
+  "version": "0.0.1",
+  "description": "",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "prettier": "prettier --write contracts/**/*.sol",
+    "solhint": "solhint 'contracts/**/*.sol'"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/Gallion-labs/gallion-contracts"
+  },
+  "author": "Florian Léger & Luc Labbé",
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/Gallion-labs/gallion-contracts/issues"
+  },
+  "homepage": "https://github.com/Gallion-labs/gallion-contract#readme",
+  "devDependencies": {
+    "@nomiclabs/hardhat-ethers": "^2.0.6",
+    "@nomiclabs/hardhat-waffle": "^2.0.3",
+    "@typechain/hardhat": "^6.1.0",
+    "@typechain/ethers-v5": "^10.1.0",
+    "chai": "^4.3.6",
+    "@types/chai": "^4.2.21",
+    "@types/mocha": "^9.1.1",
+    "dotenv": "^8.6.0",
+    "ethereum-waffle": "^3.4.4",
+    "ethers": "^5.6.8",
+    "hardhat": "^2.9.9",
+    "prettier": "^2.6.2",
+    "prettier-plugin-solidity": "^1.0.0-beta.19",
+    "standard": "^16.0.3",
+    "solhint": "^3.3.7",
+    "solc": "^0.8.14-fixed",
+    "ts-node": "^10.8.1",
+    "@types/node": "^17.0.42",
+    "typechain": "^8.1.0",
+    "typescript": "^4.7.3",
+    "@openzeppelin/contracts": "^4.6.0"
+  }
+}
diff --git a/scripts/deploy.ts b/scripts/deploy.ts
new file mode 100644
index 0000000..729bc04
--- /dev/null
+++ b/scripts/deploy.ts
@@ -0,0 +1,81 @@
+import { getSelectors, FacetCutAction } from './libraries/diamond';
+import { ethers } from 'hardhat';
+
+async function deployDiamond() {
+    const accounts = await ethers.getSigners()
+    const contractOwner = accounts[0]
+
+    // deploy DiamondCutFacet
+    const DiamondCutFacet = await ethers.getContractFactory('DiamondCutFacet')
+    const diamondCutFacet = await DiamondCutFacet.deploy()
+    await diamondCutFacet.deployed()
+    console.log('DiamondCutFacet deployed:', diamondCutFacet.address)
+
+    // deploy Guild Diamond
+    const GuildDiamond = await ethers.getContractFactory('GuildDiamond')
+    const diamond = await GuildDiamond.deploy(contractOwner.address, diamondCutFacet.address)
+    await diamond.deployed()
+    console.log('Diamond deployed:', diamond.address)
+
+    // deploy DiamondInit
+    // DiamondInit provides a function that is called when the diamond is upgraded to initialize state variables
+    // Read about how the diamondCut function works here: https://eips.ethereum.org/EIPS/eip-2535#addingreplacingremoving-functions
+    const DiamondInit = await ethers.getContractFactory('DiamondInit')
+    const diamondInit = await DiamondInit.deploy()
+    await diamondInit.deployed()
+    console.log('DiamondInit deployed:', diamondInit.address)
+
+    // deploy facets
+    console.log('')
+    console.log('Deploying facets')
+    const FacetNames = [
+        'DiamondLoupeFacet',
+        'OwnershipFacet'
+    ]
+    const cut = []
+    for (const FacetName of FacetNames) {
+        const Facet = await ethers.getContractFactory(FacetName)
+        const facet = await Facet.deploy()
+        await facet.deployed()
+        console.log(`${ FacetName } deployed: ${ facet.address }`)
+        cut.push({
+            facetAddress: facet.address,
+            action: FacetCutAction.Add,
+            functionSelectors: getSelectors(facet)
+        })
+    }
+
+    // upgrade diamond with facets
+    console.log('')
+    console.log('Diamond Cut:', cut)
+    const diamondCut = await ethers.getContractAt('IDiamondCut', diamond.address)
+    let tx
+    let receipt
+    // call to init function
+    let functionCall = diamondInit.interface.encodeFunctionData('init', [
+        {
+            gallionLabs: '0x8F7d7E9Adfa6da73273391C57bab0eF22651c7Bb'
+        }
+    ])
+    tx = await diamondCut.diamondCut(cut, diamondInit.address, functionCall)
+    console.log('Diamond cut tx: ', tx.hash)
+    receipt = await tx.wait()
+    if (!receipt.status) {
+        throw Error(`Diamond upgrade failed: ${ tx.hash }`)
+    }
+    console.log('Completed diamond cut')
+    return diamond.address
+}
+
+// We recommend this pattern to be able to use async/await everywhere
+// and properly handle errors.
+if (require.main === module) {
+    deployDiamond()
+        .then(() => process.exit(0))
+        .catch(error => {
+            console.error(error)
+            process.exit(1)
+        })
+}
+
+exports.deployDiamond = deployDiamond
diff --git a/scripts/libraries/diamond.ts b/scripts/libraries/diamond.ts
new file mode 100644
index 0000000..e692ffe
--- /dev/null
+++ b/scripts/libraries/diamond.ts
@@ -0,0 +1,87 @@
+/* global ethers */
+
+import { Contract } from 'ethers';
+import { ethers } from 'hardhat';
+import { Address } from '../../types';
+import { IDiamondLoupe } from '../../typechain-types/facets/DiamondLoupeFacet';
+import FacetStruct = IDiamondLoupe.FacetStruct;
+
+export const FacetCutAction = { Add: 0, Replace: 1, Remove: 2 };
+
+class Selectors extends Array<string> {
+    public contract: Contract;
+
+    public constructor(contract: Contract, ...selectors: string[]) {
+        super(...selectors);
+        this.contract = contract;
+    }
+
+    /**
+     * Used with getSelectors to remove selectors from an array of selectors.
+     * @param functionNames - an array of function signatures
+     */
+    public remove = (functionNames: string[]): Selectors => {
+        const _signatures = this.filter((v) => {
+            for (const functionName of functionNames) {
+                if (v === this.contract.interface.getSighash(functionName)) {
+                    return false;
+                }
+            }
+            return true;
+        }) as Selectors;
+
+        return new Selectors(this.contract, ..._signatures);
+    }
+
+    /**
+     * Used with getSelectors to get selectors from an array of selectors.
+     * @param functionNames - an array of function signatures
+     */
+    public get = (functionNames: string[]): Selectors => {
+        const _signatures = this.filter((v) => {
+            for (const functionName of functionNames) {
+                if (v === this.contract.interface.getSighash(functionName)) {
+                    return true;
+                }
+            }
+            return false;
+        });
+
+        return new Selectors(this.contract, ..._signatures);
+    }
+}
+
+// get function selectors from ABI
+export function getSelectors(contract: Contract): Selectors {
+    const signatures = Object.keys(contract.interface.functions);
+    const _signatures = signatures.reduce((acc: string[], val) => {
+        if (val !== 'init(bytes)') {
+            acc.push(contract.interface.getSighash(val));
+        }
+        return acc;
+    }, []);
+
+    return new Selectors(contract, ..._signatures);
+}
+
+// get function selector from function signature
+export function getSelector(func: string): string {
+    const abiInterface = new ethers.utils.Interface([func]);
+    return abiInterface.getSighash(ethers.utils.Fragment.from(func));
+}
+
+// remove selectors using an array of signatures
+export function removeSelectors(selectors: Selectors, signatures: string[]): Selectors {
+    const _interface = new ethers.utils.Interface(signatures.map(v => 'function ' + v));
+    const _signatures = signatures.map(v => _interface.getSighash(v));
+    return selectors.filter(v => !_signatures.includes(v)) as Selectors;
+}
+
+// find a particular address position in the return value of diamondLoupeFacet.facets()
+export function findAddressPositionInFacets(facetAddress: Address, facets: FacetStruct[]): number | undefined {
+    for (let i = 0; i < facets.length; i++) {
+        if (facets[i].facetAddress === facetAddress) {
+            return i;
+        }
+    }
+}
diff --git a/test/cacheBugTest.ts b/test/cacheBugTest.ts
new file mode 100644
index 0000000..f0b031c
--- /dev/null
+++ b/test/cacheBugTest.ts
@@ -0,0 +1,112 @@
+/* global ethers describe before it */
+/* eslint-disable prefer-const */
+
+import { DiamondLoupeFacet, PlayerFacet } from '../typechain-types';
+import { ethers } from 'hardhat';
+
+const { deployDiamond } = require('../scripts/deploy.ts')
+
+const { FacetCutAction } = require('../scripts/libraries/diamond.ts')
+
+const { assert } = require('chai')
+
+// The diamond example comes with 8 function selectors
+// [cut, loupe, loupe, loupe, loupe, erc165, transferOwnership, owner]
+// This bug manifests if you delete something from the final
+// selector slot array, so we'll fill up a new slot with
+// things, and have a fresh row to work with.
+describe('Cache bug test', async () => {
+  let diamondLoupeFacet: DiamondLoupeFacet;
+  let playerFacet: PlayerFacet;
+  const ownerSel = '0x8da5cb5b'
+
+  const sel0 = '0x19e3b533' // fills up slot 1
+  const sel1 = '0x0716c2ae' // fills up slot 1
+  const sel2 = '0x11046047' // fills up slot 1
+  const sel3 = '0xcf3bbe18' // fills up slot 1
+  const sel4 = '0x24c1d5a7' // fills up slot 1
+  const sel5 = '0xcbb835f6' // fills up slot 1
+  const sel6 = '0xcbb835f7' // fills up slot 1
+  const sel7 = '0xcbb835f8' // fills up slot 2
+  const sel8 = '0xcbb835f9' // fills up slot 2
+  const sel9 = '0xcbb835fa' // fills up slot 2
+  const sel10 = '0xcbb835fb' // fills up slot 2
+
+  before(async function () {
+    let tx
+    let receipt
+
+    let selectors = [
+      sel0,
+      sel1,
+      sel2,
+      sel3,
+      sel4,
+      sel5,
+      sel6,
+      sel7,
+      sel8,
+      sel9,
+      sel10
+    ]
+
+    let diamondAddress = await deployDiamond()
+    let diamondCutFacet = await ethers.getContractAt('DiamondCutFacet', diamondAddress)
+    diamondLoupeFacet = await ethers.getContractAt('DiamondLoupeFacet', diamondAddress)
+    const PlayerFacet = await ethers.getContractFactory('PlayerFacet')
+    playerFacet = await PlayerFacet.deploy()
+    await playerFacet.deployed()
+
+    // add functions
+    tx = await diamondCutFacet.diamondCut([
+      {
+        facetAddress: playerFacet.address,
+        action: FacetCutAction.Add,
+        functionSelectors: selectors
+      }
+    ], ethers.constants.AddressZero, '0x', { gasLimit: 800000 })
+    receipt = await tx.wait()
+    if (!receipt.status) {
+      throw Error(`Diamond upgrade failed: ${tx.hash}`)
+    }
+
+    // Remove function selectors
+    // Function selector for the owner function in slot 0
+    selectors = [
+      ownerSel, // owner selector
+      sel5,
+      sel10
+    ]
+    tx = await diamondCutFacet.diamondCut([
+      {
+        facetAddress: ethers.constants.AddressZero,
+        action: FacetCutAction.Remove,
+        functionSelectors: selectors
+      }
+    ], ethers.constants.AddressZero, '0x', { gasLimit: 800000 })
+    receipt = await tx.wait()
+    if (!receipt.status) {
+      throw Error(`Diamond upgrade failed: ${tx.hash}`)
+    }
+  })
+
+  it('should not exhibit the cache bug', async () => {
+    // Get the playerFacet's registered functions
+    let selectors = await diamondLoupeFacet.facetFunctionSelectors(playerFacet.address)
+
+    // Check individual correctness
+    assert.isTrue(selectors.includes(sel0), 'Does not contain sel0')
+    assert.isTrue(selectors.includes(sel1), 'Does not contain sel1')
+    assert.isTrue(selectors.includes(sel2), 'Does not contain sel2')
+    assert.isTrue(selectors.includes(sel3), 'Does not contain sel3')
+    assert.isTrue(selectors.includes(sel4), 'Does not contain sel4')
+    assert.isTrue(selectors.includes(sel6), 'Does not contain sel6')
+    assert.isTrue(selectors.includes(sel7), 'Does not contain sel7')
+    assert.isTrue(selectors.includes(sel8), 'Does not contain sel8')
+    assert.isTrue(selectors.includes(sel9), 'Does not contain sel9')
+
+    assert.isFalse(selectors.includes(ownerSel), 'Contains ownerSel')
+    assert.isFalse(selectors.includes(sel10), 'Contains sel10')
+    assert.isFalse(selectors.includes(sel5), 'Contains sel5')
+  })
+})
diff --git a/test/diamondTest.ts b/test/diamondTest.ts
new file mode 100644
index 0000000..909588a
--- /dev/null
+++ b/test/diamondTest.ts
@@ -0,0 +1,251 @@
+import { ethers } from 'hardhat';
+import { Address } from '../types';
+import { DiamondCutFacet, DiamondLoupeFacet, OwnershipFacet, PlayerFacet } from '../typechain-types';
+
+const {
+    getSelectors,
+    FacetCutAction,
+    removeSelectors,
+    findAddressPositionInFacets
+} = require('../scripts/libraries/diamond.ts')
+
+const { deployDiamond } = require('../scripts/deploy.ts')
+
+const { assert } = require('chai')
+
+describe('GuildDiamondTest', async function () {
+    let diamondAddress: Address;
+    let diamondCutFacet: DiamondCutFacet;
+    let diamondLoupeFacet: DiamondLoupeFacet;
+    let ownershipFacet: OwnershipFacet;
+    let tx;
+    let receipt;
+    let result;
+    const addresses: Address[] = [];
+
+    before(async function () {
+        diamondAddress = await deployDiamond();
+        diamondCutFacet = (await ethers.getContractAt('DiamondCutFacet', diamondAddress) as DiamondCutFacet);
+        diamondLoupeFacet = (await ethers.getContractAt('DiamondLoupeFacet', diamondAddress) as DiamondLoupeFacet);
+        ownershipFacet = (await ethers.getContractAt('OwnershipFacet', diamondAddress) as OwnershipFacet);
+    });
+
+    it('should have three facets -- call to facetAddresses function', async () => {
+        for (const address of await diamondLoupeFacet.facetAddresses()) {
+            addresses.push(address);
+        }
+
+        assert.equal(addresses.length, 3);
+    });
+
+    it('facets should have the right function selectors -- call to facetFunctionSelectors function', async () => {
+        let selectors = getSelectors(diamondCutFacet);
+        result = await diamondLoupeFacet.facetFunctionSelectors(addresses[0]);
+        assert.sameMembers(result, selectors);
+        selectors = getSelectors(diamondLoupeFacet);
+        result = await diamondLoupeFacet.facetFunctionSelectors(addresses[1]);
+        assert.sameMembers(result, selectors);
+        selectors = getSelectors(ownershipFacet);
+        result = await diamondLoupeFacet.facetFunctionSelectors(addresses[2]);
+        assert.sameMembers(result, selectors);
+    });
+
+    it('selectors should be associated to facets correctly -- multiple calls to facetAddress function', async () => {
+        assert.equal(
+            addresses[0],
+            await diamondLoupeFacet.facetAddress('0x1f931c1c')
+        );
+        assert.equal(
+            addresses[1],
+            await diamondLoupeFacet.facetAddress('0xcdffacc6')
+        );
+        assert.equal(
+            addresses[1],
+            await diamondLoupeFacet.facetAddress('0x01ffc9a7')
+        );
+        assert.equal(
+            addresses[2],
+            await diamondLoupeFacet.facetAddress('0xf2fde38b')
+        );
+    });
+
+    it('should add levelUp functions', async () => {
+        const PlayerFacet = await ethers.getContractFactory('PlayerFacet');
+        const playerFacet = await PlayerFacet.deploy();
+        await playerFacet.deployed();
+        addresses.push(playerFacet.address);
+        const selectors = getSelectors(playerFacet).remove(['supportsInterface(bytes4)']);
+        tx = await diamondCutFacet.diamondCut(
+            [{
+                facetAddress: playerFacet.address,
+                action: FacetCutAction.Add,
+                functionSelectors: selectors
+            }],
+            ethers.constants.AddressZero, '0x', { gasLimit: 800000 });
+        receipt = await tx.wait();
+        if (!receipt.status) {
+            throw Error(`Diamond upgrade failed: ${ tx.hash }`);
+        }
+        result = await diamondLoupeFacet.facetFunctionSelectors(playerFacet.address);
+        assert.sameMembers(result, selectors);
+    })
+
+    it('should call levelUp function', async () => {
+        const playerFacet: PlayerFacet = await ethers.getContractAt('PlayerFacet', diamondAddress);
+        await playerFacet.levelUp('0x8F7d7E9Adfa6da73273391C57bab0eF22651c7Bb');
+    })
+
+    it('should replace supportsInterface function', async () => {
+        const PlayerFacet = await ethers.getContractFactory('PlayerFacet');
+        const selectors = getSelectors(PlayerFacet).get(['supportsInterface(bytes4)']);
+        const testFacetAddress = addresses[3];
+        tx = await diamondCutFacet.diamondCut(
+            [{
+                facetAddress: testFacetAddress,
+                action: FacetCutAction.Replace,
+                functionSelectors: selectors
+            }],
+            ethers.constants.AddressZero, '0x', { gasLimit: 800000 });
+        receipt = await tx.wait();
+        if (!receipt.status) {
+            throw Error(`Diamond upgrade failed: ${ tx.hash }`);
+        }
+        result = await diamondLoupeFacet.facetFunctionSelectors(testFacetAddress);
+        assert.sameMembers(result, getSelectors(PlayerFacet));
+    })
+
+    it('should add openLootbox function', async () => {
+        const LootboxFacet = await ethers.getContractFactory('LootboxFacet');
+        const lootboxFacet = await LootboxFacet.deploy();
+        await lootboxFacet.deployed();
+        addresses.push(lootboxFacet.address);
+        const selectors = getSelectors(lootboxFacet);
+        tx = await diamondCutFacet.diamondCut(
+            [{
+                facetAddress: lootboxFacet.address,
+                action: FacetCutAction.Add,
+                functionSelectors: selectors
+            }],
+            ethers.constants.AddressZero, '0x', { gasLimit: 800000 });
+        receipt = await tx.wait();
+        if (!receipt.status) {
+            throw Error(`Diamond upgrade failed: ${ tx.hash }`);
+        }
+        result = await diamondLoupeFacet.facetFunctionSelectors(lootboxFacet.address);
+        assert.sameMembers(result, selectors);
+    })
+
+    it('should remove openLootbox function', async () => {
+        const lootboxFacet = await ethers.getContractAt('LootboxFacet', diamondAddress);
+        const functionsToKeep: string[] = [];
+        const selectors = getSelectors(lootboxFacet).remove(functionsToKeep);
+        tx = await diamondCutFacet.diamondCut(
+            [{
+                facetAddress: ethers.constants.AddressZero,
+                action: FacetCutAction.Remove,
+                functionSelectors: selectors
+            }],
+            ethers.constants.AddressZero, '0x', { gasLimit: 800000 });
+        receipt = await tx.wait();
+        if (!receipt.status) {
+            throw Error(`Diamond upgrade failed: ${ tx.hash }`);
+        }
+        result = await diamondLoupeFacet.facetFunctionSelectors(addresses[4]);
+        assert.sameMembers(result, getSelectors(lootboxFacet).get(functionsToKeep));
+    })
+
+    /*it('should remove some test1 functions', async () => {
+        const playerFacet = await ethers.getContractAt('PlayerFacet', diamondAddress)
+        const functionsToKeep = ['test1Func2()', 'test1Func11()', 'test1Func12()']
+        const selectors = getSelectors(playerFacet).remove(functionsToKeep)
+        tx = await diamondCutFacet.diamondCut(
+            [{
+                facetAddress: ethers.constants.AddressZero,
+                action: FacetCutAction.Remove,
+                functionSelectors: selectors
+            }],
+            ethers.constants.AddressZero, '0x', { gasLimit: 800000 })
+        receipt = await tx.wait()
+        if (!receipt.status) {
+            throw Error(`Diamond upgrade failed: ${ tx.hash }`)
+        }
+        result = await diamondLoupeFacet.facetFunctionSelectors(addresses[3])
+        assert.sameMembers(result, getSelectors(playerFacet).get(functionsToKeep))
+    })*/
+
+    it('remove all functions and facets accept \'diamondCut\' and \'facets\'', async () => {
+        let selectors = []
+        let facets = await diamondLoupeFacet.facets()
+        for (let i = 0; i < facets.length; i++) {
+            selectors.push(...facets[i].functionSelectors)
+        }
+        selectors = removeSelectors(selectors, ['facets()', 'diamondCut(tuple(address,uint8,bytes4[])[],address,bytes)'])
+        tx = await diamondCutFacet.diamondCut(
+            [{
+                facetAddress: ethers.constants.AddressZero,
+                action: FacetCutAction.Remove,
+                functionSelectors: selectors
+            }],
+            ethers.constants.AddressZero, '0x', { gasLimit: 800000 })
+        receipt = await tx.wait()
+        if (!receipt.status) {
+            throw Error(`Diamond upgrade failed: ${ tx.hash }`)
+        }
+        facets = await diamondLoupeFacet.facets()
+        assert.equal(facets.length, 2)
+        assert.equal(facets[0][0], addresses[0])
+        assert.sameMembers(facets[0][1], ['0x1f931c1c'])
+        assert.equal(facets[1][0], addresses[1])
+        assert.sameMembers(facets[1][1], ['0x7a0ed627'])
+    })
+
+    it('add most functions and facets', async () => {
+        const diamondLoupeFacetSelectors = getSelectors(diamondLoupeFacet).remove(['supportsInterface(bytes4)'])
+        const PlayerFacet = await ethers.getContractFactory('PlayerFacet')
+        const LootboxFacet = await ethers.getContractFactory('LootboxFacet')
+        // Any number of functions from any number of facets can be added/replaced/removed in a
+        // single transaction
+        const cut = [
+            {
+                facetAddress: addresses[1],
+                action: FacetCutAction.Add,
+                functionSelectors: diamondLoupeFacetSelectors.remove(['facets()'])
+            },
+            {
+                facetAddress: addresses[2],
+                action: FacetCutAction.Add,
+                functionSelectors: getSelectors(ownershipFacet)
+            },
+            {
+                facetAddress: addresses[3],
+                action: FacetCutAction.Add,
+                functionSelectors: getSelectors(PlayerFacet)
+            },
+            {
+                facetAddress: addresses[4],
+                action: FacetCutAction.Add,
+                functionSelectors: getSelectors(LootboxFacet)
+            }
+        ]
+        tx = await diamondCutFacet.diamondCut(cut, ethers.constants.AddressZero, '0x', { gasLimit: 8000000 })
+        receipt = await tx.wait()
+        if (!receipt.status) {
+            throw Error(`Diamond upgrade failed: ${ tx.hash }`)
+        }
+        const facets = await diamondLoupeFacet.facets()
+        const facetAddresses = await diamondLoupeFacet.facetAddresses()
+        assert.equal(facetAddresses.length, 5)
+        assert.equal(facets.length, 5)
+        assert.sameMembers(facetAddresses, addresses)
+        assert.equal(facets[0][0], facetAddresses[0], 'first facet')
+        assert.equal(facets[1][0], facetAddresses[1], 'second facet')
+        assert.equal(facets[2][0], facetAddresses[2], 'third facet')
+        assert.equal(facets[3][0], facetAddresses[3], 'fourth facet')
+        assert.equal(facets[4][0], facetAddresses[4], 'fifth facet')
+        assert.sameMembers(facets[findAddressPositionInFacets(addresses[0], facets)][1], getSelectors(diamondCutFacet))
+        assert.sameMembers(facets[findAddressPositionInFacets(addresses[1], facets)][1], diamondLoupeFacetSelectors)
+        assert.sameMembers(facets[findAddressPositionInFacets(addresses[2], facets)][1], getSelectors(ownershipFacet))
+        assert.sameMembers(facets[findAddressPositionInFacets(addresses[3], facets)][1], getSelectors(PlayerFacet))
+        assert.sameMembers(facets[findAddressPositionInFacets(addresses[4], facets)][1], getSelectors(LootboxFacet))
+    })
+})
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..539e9d8
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,14 @@
+{
+  "compilerOptions": {
+    "target": "es2018",
+    "module": "CommonJS",
+    "strict": true,
+    "allowJs": true,
+    "esModuleInterop": true,
+    "outDir": "dist",
+    "resolveJsonModule": true,
+    "noImplicitAny": true
+  },
+  "include": ["./scripts/**/*", "./test/**/*", "*.ts", "**/*.ts", "./tasks"],
+  "files": ["./hardhat.config.ts"]
+}
diff --git a/types.ts b/types.ts
new file mode 100644
index 0000000..5058103
--- /dev/null
+++ b/types.ts
@@ -0,0 +1,10 @@
+export interface AppStorage {
+    gallionLabs: Address;
+}
+
+export type Address = string;
+
+
+export function isAddress(address: string): address is Address {
+    return address.length === 42 && !!address.match(/^0x[0-9a-fA-F]{40}$/);
+}