diff --git a/cli/web3cli/core.py b/cli/web3cli/core.py index 660cb83f..324b4542 100644 --- a/cli/web3cli/core.py +++ b/cli/web3cli/core.py @@ -23,6 +23,7 @@ ReentrancyExploitable, CraftingFacet, GOFPFacet, + InventoryFacet, ) FACETS: Dict[str, Any] = { @@ -33,6 +34,7 @@ "ReentrancyExploitable": ReentrancyExploitable, "CraftingFacet": CraftingFacet, "GOFPFacet": GOFPFacet, + "InventoryFacet": InventoryFacet, } FACET_INIT_CALLDATA: Dict[str, str] = { @@ -42,6 +44,9 @@ "GOFPFacet": lambda address, *args: GOFPFacet.GOFPFacet( address ).contract.init.encode_input(*args), + "InventoryFacet": lambda address, *args: InventoryFacet.InventoryFacet( + address + ).contract.init.encode_input(*args), } DIAMOND_FACET_PRECEDENCE: List[str] = [ @@ -58,8 +63,9 @@ class EngineFeatures(Enum): - DROPPER = "dropper" - GOFP = "GardenOfForkingPaths" + DROPPER = "DropperFacet" + GOFP = "GOFPFacet" + INVENTORY = "InventoryFacet" def feature_from_facet_name(facet_name: str) -> Optional[EngineFeatures]: @@ -72,11 +78,13 @@ def feature_from_facet_name(facet_name: str) -> Optional[EngineFeatures]: FEATURE_FACETS: Dict[EngineFeatures, List[str]] = { EngineFeatures.DROPPER: ["DropperFacet"], EngineFeatures.GOFP: ["GOFPFacet"], + EngineFeatures.INVENTORY: ["InventoryFacet"], } FEATURE_IGNORES: Dict[EngineFeatures, List[str]] = { EngineFeatures.DROPPER: {"methods": ["init"], "selectors": []}, EngineFeatures.GOFP: {"methods": ["init"], "selectors": []}, + EngineFeatures.INVENTORY: {"methods": ["init"], "selectors": []}, } FACET_ACTIONS: Dict[str, int] = {"add": 0, "replace": 1, "remove": 2} @@ -188,7 +196,10 @@ def facet_cut( diamond = DiamondCutFacet.DiamondCutFacet(diamond_address) calldata = b"" - if FACET_INIT_CALLDATA.get(facet_name) is not None: + if ( + initializer_address != ZERO_ADDRESS + and FACET_INIT_CALLDATA.get(facet_name) is not None + ): if initializer_args is None: initializer_args = [] calldata = FACET_INIT_CALLDATA[facet_name]( @@ -200,40 +211,14 @@ def facet_cut( return transaction -def crafting_gogogo( - owner_address: str, transaction_config: Dict[str, Any] -) -> Dict[str, Any]: - result = diamond_gogogo(owner_address, transaction_config) - - try: - crafting_facet = CraftingFacet.CraftingFacet(None) - crafting_facet.deploy(transaction_config) - except Exception as e: - print(e) - result["error"] = f"Failed to deploy CraftingFacet: {e}" - return result - - result["CraftingFacet"] = crafting_facet.address - - try: - facet_cut( - result["Diamond"], - "CraftingFacet", - crafting_facet.address, - "add", - transaction_config, - ) - except Exception as e: - print(e) - result["error"] = f"Failed to diamondCut cut CraftingFacet: {e}" - return result - - result["attached"].append("CraftingFacet") - return result - - def diamond_gogogo( - owner_address: str, transaction_config: Dict[str, Any] + owner_address: str, + transaction_config: Dict[str, Any], + diamond_cut_address: Optional[str] = None, + diamond_address: Optional[str] = None, + diamond_loupe_address: Optional[str] = None, + ownership_address: Optional[str] = None, + verify_contracts: Optional[bool] = False, ) -> Dict[str, Any]: """ Deploy diamond along with all its basic facets and attach those facets to the diamond. @@ -241,42 +226,61 @@ def diamond_gogogo( Returns addresses of all the deployed contracts with the contract names as keys. """ result: Dict[str, Any] = {"contracts": {}, "attached": []} - - try: - diamond_cut_facet = DiamondCutFacet.DiamondCutFacet(None) - diamond_cut_facet.deploy(transaction_config) - except Exception as e: - print(e) - result["error"] = "Failed to deploy DiamondCutFacet" - return result - result["contracts"]["DiamondCutFacet"] = diamond_cut_facet.address - - try: - diamond = Diamond.Diamond(None) - diamond.deploy(owner_address, diamond_cut_facet.address, transaction_config) - except Exception as e: - print(e) - result["error"] = "Failed to deploy Diamond" - return result - result["contracts"]["Diamond"] = diamond.address - - try: - diamond_loupe_facet = DiamondLoupeFacet.DiamondLoupeFacet(None) - diamond_loupe_facet.deploy(transaction_config) - except Exception as e: - print(e) - result["error"] = "Failed to deploy DiamondLoupeFacet" - return result - result["contracts"]["DiamondLoupeFacet"] = diamond_loupe_facet.address - - try: - ownership_facet = OwnershipFacet.OwnershipFacet(None) - ownership_facet.deploy(transaction_config) - except Exception as e: - print(e) - result["error"] = "Failed to deploy OwnershipFacet" - return result - result["contracts"]["OwnershipFacet"] = ownership_facet.address + if verify_contracts: + result["verified"] = [] + result["verification_errors"] = [] + + if diamond_cut_address is None: + try: + diamond_cut_facet = DiamondCutFacet.DiamondCutFacet(None) + diamond_cut_facet.deploy(transaction_config) + except Exception as e: + print(e) + result["error"] = "Failed to deploy DiamondCutFacet" + return result + result["contracts"]["DiamondCutFacet"] = diamond_cut_facet.address + else: + result["contracts"]["DiamondCutFacet"] = diamond_cut_address + diamond_cut_facet = DiamondCutFacet.DiamondCutFacet(diamond_cut_address) + + if diamond_address is None: + try: + diamond = Diamond.Diamond(None) + diamond.deploy(owner_address, diamond_cut_facet.address, transaction_config) + except Exception as e: + print(e) + result["error"] = "Failed to deploy Diamond" + return result + result["contracts"]["Diamond"] = diamond.address + else: + result["contracts"]["Diamond"] = diamond_address + diamond = Diamond.Diamond(diamond_address) + + if diamond_loupe_address is None: + try: + diamond_loupe_facet = DiamondLoupeFacet.DiamondLoupeFacet(None) + diamond_loupe_facet.deploy(transaction_config) + except Exception as e: + print(e) + result["error"] = "Failed to deploy DiamondLoupeFacet" + return result + result["contracts"]["DiamondLoupeFacet"] = diamond_loupe_facet.address + else: + result["contracts"]["DiamondLoupeFacet"] = diamond_loupe_address + diamond_loupe_facet = DiamondLoupeFacet.DiamondLoupeFacet(diamond_loupe_address) + + if ownership_address is None: + try: + ownership_facet = OwnershipFacet.OwnershipFacet(None) + ownership_facet.deploy(transaction_config) + except Exception as e: + print(e) + result["error"] = "Failed to deploy OwnershipFacet" + return result + result["contracts"]["OwnershipFacet"] = ownership_facet.address + else: + result["contracts"]["OwnershipFacet"] = ownership_address + ownership_facet = OwnershipFacet.OwnershipFacet(ownership_address) try: facet_cut( @@ -306,6 +310,62 @@ def diamond_gogogo( return result result["attached"].append("OwnershipFacet") + if verify_contracts: + try: + diamond_cut_facet.verify_contract() + result["verified"].append("DiamondCutFacet") + except Exception as e: + result["verification_errors"].append(repr(e)) + + try: + diamond.verify_contract() + result["verified"].append("Diamond") + except Exception as e: + result["verification_errors"].append(repr(e)) + + try: + diamond_loupe_facet.verify_contract() + result["verified"].append("DiamondLoupeFacet") + except Exception as e: + result["verification_errors"].append(repr(e)) + + try: + ownership_facet.verify_contract() + except Exception as e: + result["verification_errors"].append(repr(e)) + + return result + + +def crafting_gogogo( + owner_address: str, transaction_config: Dict[str, Any] +) -> Dict[str, Any]: + result = diamond_gogogo(owner_address, transaction_config) + + try: + crafting_facet = CraftingFacet.CraftingFacet(None) + crafting_facet.deploy(transaction_config) + except Exception as e: + print(e) + result["error"] = f"Failed to deploy CraftingFacet: {e}" + return result + + result["CraftingFacet"] = crafting_facet.address + + try: + facet_cut( + result["Diamond"], + "CraftingFacet", + crafting_facet.address, + "add", + transaction_config, + ) + except Exception as e: + print(e) + result["error"] = f"Failed to diamondCut cut CraftingFacet: {e}" + return result + + result["attached"].append("CraftingFacet") return result @@ -385,7 +445,6 @@ def create_lootboxes_from_config( ) for lootbox in config: # This is for validating data - lootbox_items = [] for item in lootbox["items"]: if item["rewardType"] == 20: @@ -569,6 +628,67 @@ def gofp_gogogo( return deployment_info +def inventory_gogogo( + admin_terminus_address: str, + admin_terminus_pool_id: int, + subject_erc721_address: str, + transaction_config: Dict[str, Any], + diamond_cut_address: Optional[str] = None, + diamond_address: Optional[str] = None, + diamond_loupe_address: Optional[str] = None, + ownership_address: Optional[str] = None, + inventory_facet_address: Optional[str] = None, + verify_contracts: Optional[bool] = False, +) -> Dict[str, Any]: + """ + Deploys an EIP2535 Diamond contract and an InventoryFacet and mounts the InventoryFacet onto the Diamond contract. + + Returns the addresses and attachments. + """ + deployment_info = diamond_gogogo( + owner_address=transaction_config["from"].address, + transaction_config=transaction_config, + diamond_cut_address=diamond_cut_address, + diamond_address=diamond_address, + diamond_loupe_address=diamond_loupe_address, + ownership_address=ownership_address, + verify_contracts=verify_contracts, + ) + + if inventory_facet_address is None: + inventory_facet = InventoryFacet.InventoryFacet(None) + inventory_facet.deploy(transaction_config=transaction_config) + else: + inventory_facet = InventoryFacet.InventoryFacet(inventory_facet_address) + + deployment_info["contracts"]["InventoryFacet"] = inventory_facet.address + + if verify_contracts: + try: + inventory_facet.verify_contract() + deployment_info["verified"].append("InventoryFacet") + except Exception as e: + deployment_info["verification_errors"].append(repr(e)) + + facet_cut( + deployment_info["contracts"]["Diamond"], + "InventoryFacet", + inventory_facet.address, + "add", + transaction_config, + initializer_address=inventory_facet.address, + feature=EngineFeatures.INVENTORY, + initializer_args=[ + admin_terminus_address, + admin_terminus_pool_id, + subject_erc721_address, + ], + ) + deployment_info["attached"].append("InventoryFacet") + + return deployment_info + + def handle_facet_cut(args: argparse.Namespace) -> None: network.connect(args.network) diamond_address = args.address @@ -635,6 +755,27 @@ def handle_gofp_gogogo(args: argparse.Namespace) -> None: json.dump(result, sys.stdout, indent=4) +def handle_inventory_gogogo(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = InventoryFacet.get_transaction_config(args) + result = inventory_gogogo( + admin_terminus_address=args.admin_terminus_address, + admin_terminus_pool_id=args.admin_terminus_pool_id, + subject_erc721_address=args.subject_erc721_address, + transaction_config=transaction_config, + diamond_cut_address=args.diamond_cut_address, + diamond_address=args.diamond_address, + diamond_loupe_address=args.diamond_loupe_address, + ownership_address=args.ownership_address, + inventory_facet_address=args.inventory_facet_address, + verify_contracts=args.verify_contracts, + ) + if args.outfile is not None: + with args.outfile: + json.dump(result, args.outfile) + json.dump(result, sys.stdout, indent=4) + + def handle_crafting_gogogo(args: argparse.Namespace) -> None: network.connect(args.network) @@ -774,6 +915,71 @@ def generate_cli(): ) gofp_gogogo_parser.set_defaults(func=handle_gofp_gogogo) + inventory_gogogo_parser = subcommands.add_parser( + "inventory-gogogo", + description="Deploy Inventory diamond contract", + ) + Diamond.add_default_arguments(inventory_gogogo_parser, transact=True) + inventory_gogogo_parser.add_argument( + "--verify-contracts", + action="store_true", + help="Verify contracts", + ) + inventory_gogogo_parser.add_argument( + "--admin-terminus-address", + required=True, + help="Address of Terminus contract defining access control for this GardenOfForkingPaths contract", + ) + inventory_gogogo_parser.add_argument( + "--admin-terminus-pool-id", + required=True, + type=int, + help="Pool ID of Terminus pool for administrators of this GardenOfForkingPaths contract", + ) + inventory_gogogo_parser.add_argument( + "--subject-erc721-address", + required=True, + help="Address of ERC721 contract that the Inventory modifies", + ) + inventory_gogogo_parser.add_argument( + "--diamond-cut-address", + required=False, + default=None, + help="Address to deployed DiamondCutFacet. If provided, this command skips deployment of a new DiamondCutFacet.", + ) + inventory_gogogo_parser.add_argument( + "--diamond-address", + required=False, + default=None, + help="Address to deployed Diamond contract. If provided, this command skips deployment of a new Diamond contract and simply mounts the required facets onto the existing Diamond contract. Assumes that there is no collision of selectors.", + ) + inventory_gogogo_parser.add_argument( + "--diamond-loupe-address", + required=False, + default=None, + help="Address to deployed DiamondLoupeFacet. If provided, this command skips deployment of a new DiamondLoupeFacet. It mounts the existing DiamondLoupeFacet onto the Diamond.", + ) + inventory_gogogo_parser.add_argument( + "--ownership-address", + required=False, + default=None, + help="Address to deployed OwnershipFacet. If provided, this command skips deployment of a new OwnershipFacet. It mounts the existing OwnershipFacet onto the Diamond.", + ) + inventory_gogogo_parser.add_argument( + "--inventory-facet-address", + required=False, + default=None, + help="Address to deployed InventoryFacet. If provided, this command skips deployment of a new InventoryFacet. It mounts the existing InventoryFacet onto the Diamond.", + ) + inventory_gogogo_parser.add_argument( + "-o", + "--outfile", + type=argparse.FileType("w"), + default=None, + help="(Optional) file to write deployed addresses to", + ) + inventory_gogogo_parser.set_defaults(func=handle_inventory_gogogo) + lootbox_gogogo_parser = subcommands.add_parser( "lootbox-gogogo", help="Deploys Lootbox contract",