From db1437bafbdf134e3c86792c410a33c63c7390b4 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sat, 11 Dec 2021 18:56:19 -0800 Subject: [PATCH] moonworm-generated Python interface for diamond contracts --- .gitignore | 1 + dao/Diamond.py | 176 +++++++++++++++++++++++++++ dao/DiamondCutFacet.py | 195 ++++++++++++++++++++++++++++++ dao/DiamondInit.py | 175 +++++++++++++++++++++++++++ dao/DiamondLoupeFacet.py | 249 +++++++++++++++++++++++++++++++++++++++ dao/OwnershipFacet.py | 197 +++++++++++++++++++++++++++++++ 6 files changed, 993 insertions(+) create mode 100644 dao/Diamond.py create mode 100644 dao/DiamondCutFacet.py create mode 100644 dao/DiamondInit.py create mode 100644 dao/DiamondLoupeFacet.py create mode 100644 dao/OwnershipFacet.py diff --git a/.gitignore b/.gitignore index b41106f..84e811e 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ dmypy.json # Custom .dao/ .secrets/ +.vscode/ diff --git a/dao/Diamond.py b/dao/Diamond.py new file mode 100644 index 0000000..bc4a055 --- /dev/null +++ b/dao/Diamond.py @@ -0,0 +1,176 @@ +# Code generated by moonworm : https://github.com/bugout-dev/moonworm +# Moonworm version : 0.1.6 + +import argparse +import json +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from brownie import Contract, network, project +from brownie.network.contract import ContractContainer +from eth_typing.evm import ChecksumAddress + + +PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts") + + +def boolean_argument_type(raw_value: str) -> bool: + TRUE_VALUES = ["1", "t", "y", "true", "yes"] + FALSE_VALUES = ["0", "f", "n", "false", "no"] + + if raw_value.lower() in TRUE_VALUES: + return True + elif raw_value.lower() in FALSE_VALUES: + return False + + raise ValueError( + f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" + ) + + +def bytes_argument_type(raw_value: str) -> bytes: + return raw_value.encode() + + +def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + abi_json = build.get("abi") + if abi_json is None: + raise ValueError(f"Could not find ABI definition in: {abi_full_path}") + + return abi_json + + +def contract_from_build(abi_name: str) -> ContractContainer: + # This is workaround because brownie currently doesn't support loading the same project multiple + # times. This causes problems when using multiple contracts from the same project in the same + # python project. + PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) + + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + return ContractContainer(PROJECT, build) + + +class Diamond: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "Diamond" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("Diamond") + if self.address is not None: + self.contract: Optional[Contract] = Contract.from_abi( + self.contract_name, self.address, self.abi + ) + + def deploy( + self, + _contract_owner: ChecksumAddress, + _diamond_cut_facet: ChecksumAddress, + transaction_config, + ): + contract_class = contract_from_build(self.contract_name) + deployed_contract = contract_class.deploy( + _contract_owner, _diamond_cut_facet, transaction_config + ) + self.address = deployed_contract.address + self.contract = deployed_contract + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + +def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: + signer = network.accounts.load(args.sender, args.password) + transaction_config: Dict[str, Any] = {"from": signer} + if args.gas_price is not None: + transaction_config["gas_price"] = args.gas_price + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + return transaction_config + + +def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: + parser.add_argument( + "--network", required=True, help="Name of brownie network to connect to" + ) + parser.add_argument( + "--address", required=False, help="Address of deployed contract to connect to" + ) + if not transact: + return + parser.add_argument( + "--sender", required=True, help="Path to keystore file for transaction sender" + ) + parser.add_argument( + "--password", + required=False, + help="Password to keystore file (if you do not provide it, you will be prompted for it)", + ) + parser.add_argument( + "--gas-price", default=None, help="Gas price at which to submit transaction" + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = Diamond(None) + result = contract.deploy( + _contract_owner=args.contract_owner_arg, + _diamond_cut_facet=args.diamond_cut_facet_arg, + transaction_config=transaction_config, + ) + print(result) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for Diamond") + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers() + + deploy_parser = subcommands.add_parser("deploy") + add_default_arguments(deploy_parser, True) + deploy_parser.add_argument( + "--contract-owner-arg", required=True, help="Type: address" + ) + deploy_parser.add_argument( + "--diamond-cut-facet-arg", required=True, help="Type: address" + ) + deploy_parser.set_defaults(func=handle_deploy) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/dao/DiamondCutFacet.py b/dao/DiamondCutFacet.py new file mode 100644 index 0000000..2503c00 --- /dev/null +++ b/dao/DiamondCutFacet.py @@ -0,0 +1,195 @@ +# Code generated by moonworm : https://github.com/bugout-dev/moonworm +# Moonworm version : 0.1.6 + +import argparse +import json +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from brownie import Contract, network, project +from brownie.network.contract import ContractContainer +from eth_typing.evm import ChecksumAddress + + +PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts") + + +def boolean_argument_type(raw_value: str) -> bool: + TRUE_VALUES = ["1", "t", "y", "true", "yes"] + FALSE_VALUES = ["0", "f", "n", "false", "no"] + + if raw_value.lower() in TRUE_VALUES: + return True + elif raw_value.lower() in FALSE_VALUES: + return False + + raise ValueError( + f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" + ) + + +def bytes_argument_type(raw_value: str) -> bytes: + return raw_value.encode() + + +def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + abi_json = build.get("abi") + if abi_json is None: + raise ValueError(f"Could not find ABI definition in: {abi_full_path}") + + return abi_json + + +def contract_from_build(abi_name: str) -> ContractContainer: + # This is workaround because brownie currently doesn't support loading the same project multiple + # times. This causes problems when using multiple contracts from the same project in the same + # python project. + PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) + + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + return ContractContainer(PROJECT, build) + + +class DiamondCutFacet: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "DiamondCutFacet" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("DiamondCutFacet") + if self.address is not None: + self.contract: Optional[Contract] = Contract.from_abi( + self.contract_name, self.address, self.abi + ) + + def deploy(self, transaction_config): + contract_class = contract_from_build(self.contract_name) + deployed_contract = contract_class.deploy(transaction_config) + self.address = deployed_contract.address + self.contract = deployed_contract + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + def diamond_cut( + self, + _diamond_cut: List, + _init: ChecksumAddress, + _calldata: bytes, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.diamondCut( + _diamond_cut, _init, _calldata, transaction_config + ) + + +def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: + signer = network.accounts.load(args.sender, args.password) + transaction_config: Dict[str, Any] = {"from": signer} + if args.gas_price is not None: + transaction_config["gas_price"] = args.gas_price + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + return transaction_config + + +def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: + parser.add_argument( + "--network", required=True, help="Name of brownie network to connect to" + ) + parser.add_argument( + "--address", required=False, help="Address of deployed contract to connect to" + ) + if not transact: + return + parser.add_argument( + "--sender", required=True, help="Path to keystore file for transaction sender" + ) + parser.add_argument( + "--password", + required=False, + help="Password to keystore file (if you do not provide it, you will be prompted for it)", + ) + parser.add_argument( + "--gas-price", default=None, help="Gas price at which to submit transaction" + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = DiamondCutFacet(None) + result = contract.deploy(transaction_config=transaction_config) + print(result) + + +def handle_diamond_cut(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DiamondCutFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.diamond_cut( + _diamond_cut=args.diamond_cut_arg, + _init=args.init_arg, + _calldata=args.calldata_arg, + transaction_config=transaction_config, + ) + print(result) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for DiamondCutFacet") + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers() + + deploy_parser = subcommands.add_parser("deploy") + add_default_arguments(deploy_parser, True) + deploy_parser.set_defaults(func=handle_deploy) + + diamond_cut_parser = subcommands.add_parser("diamond-cut") + add_default_arguments(diamond_cut_parser, True) + diamond_cut_parser.add_argument( + "--diamond-cut-arg", required=True, help="Type: tuple[]", nargs="+" + ) + diamond_cut_parser.add_argument("--init-arg", required=True, help="Type: address") + diamond_cut_parser.add_argument( + "--calldata-arg", required=True, help="Type: bytes", type=bytes_argument_type + ) + diamond_cut_parser.set_defaults(func=handle_diamond_cut) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/dao/DiamondInit.py b/dao/DiamondInit.py new file mode 100644 index 0000000..374d252 --- /dev/null +++ b/dao/DiamondInit.py @@ -0,0 +1,175 @@ +# Code generated by moonworm : https://github.com/bugout-dev/moonworm +# Moonworm version : 0.1.6 + +import argparse +import json +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from brownie import Contract, network, project +from brownie.network.contract import ContractContainer +from eth_typing.evm import ChecksumAddress + + +PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts") + + +def boolean_argument_type(raw_value: str) -> bool: + TRUE_VALUES = ["1", "t", "y", "true", "yes"] + FALSE_VALUES = ["0", "f", "n", "false", "no"] + + if raw_value.lower() in TRUE_VALUES: + return True + elif raw_value.lower() in FALSE_VALUES: + return False + + raise ValueError( + f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" + ) + + +def bytes_argument_type(raw_value: str) -> bytes: + return raw_value.encode() + + +def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + abi_json = build.get("abi") + if abi_json is None: + raise ValueError(f"Could not find ABI definition in: {abi_full_path}") + + return abi_json + + +def contract_from_build(abi_name: str) -> ContractContainer: + # This is workaround because brownie currently doesn't support loading the same project multiple + # times. This causes problems when using multiple contracts from the same project in the same + # python project. + PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) + + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + return ContractContainer(PROJECT, build) + + +class DiamondInit: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "DiamondInit" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("DiamondInit") + if self.address is not None: + self.contract: Optional[Contract] = Contract.from_abi( + self.contract_name, self.address, self.abi + ) + + def deploy(self, transaction_config): + contract_class = contract_from_build(self.contract_name) + deployed_contract = contract_class.deploy(transaction_config) + self.address = deployed_contract.address + self.contract = deployed_contract + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + def init(self, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.init(transaction_config) + + +def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: + signer = network.accounts.load(args.sender, args.password) + transaction_config: Dict[str, Any] = {"from": signer} + if args.gas_price is not None: + transaction_config["gas_price"] = args.gas_price + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + return transaction_config + + +def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: + parser.add_argument( + "--network", required=True, help="Name of brownie network to connect to" + ) + parser.add_argument( + "--address", required=False, help="Address of deployed contract to connect to" + ) + if not transact: + return + parser.add_argument( + "--sender", required=True, help="Path to keystore file for transaction sender" + ) + parser.add_argument( + "--password", + required=False, + help="Password to keystore file (if you do not provide it, you will be prompted for it)", + ) + parser.add_argument( + "--gas-price", default=None, help="Gas price at which to submit transaction" + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = DiamondInit(None) + result = contract.deploy(transaction_config=transaction_config) + print(result) + + +def handle_init(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DiamondInit(args.address) + transaction_config = get_transaction_config(args) + result = contract.init(transaction_config=transaction_config) + print(result) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for DiamondInit") + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers() + + deploy_parser = subcommands.add_parser("deploy") + add_default_arguments(deploy_parser, True) + deploy_parser.set_defaults(func=handle_deploy) + + init_parser = subcommands.add_parser("init") + add_default_arguments(init_parser, True) + init_parser.set_defaults(func=handle_init) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/dao/DiamondLoupeFacet.py b/dao/DiamondLoupeFacet.py new file mode 100644 index 0000000..00726dd --- /dev/null +++ b/dao/DiamondLoupeFacet.py @@ -0,0 +1,249 @@ +# Code generated by moonworm : https://github.com/bugout-dev/moonworm +# Moonworm version : 0.1.6 + +import argparse +import json +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from brownie import Contract, network, project +from brownie.network.contract import ContractContainer +from eth_typing.evm import ChecksumAddress + + +PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts") + + +def boolean_argument_type(raw_value: str) -> bool: + TRUE_VALUES = ["1", "t", "y", "true", "yes"] + FALSE_VALUES = ["0", "f", "n", "false", "no"] + + if raw_value.lower() in TRUE_VALUES: + return True + elif raw_value.lower() in FALSE_VALUES: + return False + + raise ValueError( + f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" + ) + + +def bytes_argument_type(raw_value: str) -> bytes: + return raw_value.encode() + + +def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + abi_json = build.get("abi") + if abi_json is None: + raise ValueError(f"Could not find ABI definition in: {abi_full_path}") + + return abi_json + + +def contract_from_build(abi_name: str) -> ContractContainer: + # This is workaround because brownie currently doesn't support loading the same project multiple + # times. This causes problems when using multiple contracts from the same project in the same + # python project. + PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) + + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + return ContractContainer(PROJECT, build) + + +class DiamondLoupeFacet: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "DiamondLoupeFacet" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("DiamondLoupeFacet") + if self.address is not None: + self.contract: Optional[Contract] = Contract.from_abi( + self.contract_name, self.address, self.abi + ) + + def deploy(self, transaction_config): + contract_class = contract_from_build(self.contract_name) + deployed_contract = contract_class.deploy(transaction_config) + self.address = deployed_contract.address + self.contract = deployed_contract + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + def facet_address(self, _function_selector: bytes) -> Any: + self.assert_contract_is_instantiated() + return self.contract.facetAddress.call(_function_selector) + + def facet_addresses(self) -> Any: + self.assert_contract_is_instantiated() + return self.contract.facetAddresses.call() + + def facet_function_selectors(self, _facet: ChecksumAddress) -> Any: + self.assert_contract_is_instantiated() + return self.contract.facetFunctionSelectors.call(_facet) + + def facets(self) -> Any: + self.assert_contract_is_instantiated() + return self.contract.facets.call() + + def supports_interface(self, _interface_id: bytes) -> Any: + self.assert_contract_is_instantiated() + return self.contract.supportsInterface.call(_interface_id) + + +def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: + signer = network.accounts.load(args.sender, args.password) + transaction_config: Dict[str, Any] = {"from": signer} + if args.gas_price is not None: + transaction_config["gas_price"] = args.gas_price + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + return transaction_config + + +def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: + parser.add_argument( + "--network", required=True, help="Name of brownie network to connect to" + ) + parser.add_argument( + "--address", required=False, help="Address of deployed contract to connect to" + ) + if not transact: + return + parser.add_argument( + "--sender", required=True, help="Path to keystore file for transaction sender" + ) + parser.add_argument( + "--password", + required=False, + help="Password to keystore file (if you do not provide it, you will be prompted for it)", + ) + parser.add_argument( + "--gas-price", default=None, help="Gas price at which to submit transaction" + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = DiamondLoupeFacet(None) + result = contract.deploy(transaction_config=transaction_config) + print(result) + + +def handle_facet_address(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DiamondLoupeFacet(args.address) + result = contract.facet_address(_function_selector=args.function_selector_arg) + print(result) + + +def handle_facet_addresses(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DiamondLoupeFacet(args.address) + result = contract.facet_addresses() + print(result) + + +def handle_facet_function_selectors(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DiamondLoupeFacet(args.address) + result = contract.facet_function_selectors(_facet=args.facet_arg) + print(result) + + +def handle_facets(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DiamondLoupeFacet(args.address) + result = contract.facets() + print(result) + + +def handle_supports_interface(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = DiamondLoupeFacet(args.address) + result = contract.supports_interface(_interface_id=args.interface_id_arg) + print(result) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for DiamondLoupeFacet") + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers() + + deploy_parser = subcommands.add_parser("deploy") + add_default_arguments(deploy_parser, True) + deploy_parser.set_defaults(func=handle_deploy) + + facet_address_parser = subcommands.add_parser("facet-address") + add_default_arguments(facet_address_parser, False) + facet_address_parser.add_argument( + "--function-selector-arg", + required=True, + help="Type: bytes4", + type=bytes_argument_type, + ) + facet_address_parser.set_defaults(func=handle_facet_address) + + facet_addresses_parser = subcommands.add_parser("facet-addresses") + add_default_arguments(facet_addresses_parser, False) + facet_addresses_parser.set_defaults(func=handle_facet_addresses) + + facet_function_selectors_parser = subcommands.add_parser("facet-function-selectors") + add_default_arguments(facet_function_selectors_parser, False) + facet_function_selectors_parser.add_argument( + "--facet-arg", required=True, help="Type: address" + ) + facet_function_selectors_parser.set_defaults(func=handle_facet_function_selectors) + + facets_parser = subcommands.add_parser("facets") + add_default_arguments(facets_parser, False) + facets_parser.set_defaults(func=handle_facets) + + supports_interface_parser = subcommands.add_parser("supports-interface") + add_default_arguments(supports_interface_parser, False) + supports_interface_parser.add_argument( + "--interface-id-arg", + required=True, + help="Type: bytes4", + type=bytes_argument_type, + ) + supports_interface_parser.set_defaults(func=handle_supports_interface) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/dao/OwnershipFacet.py b/dao/OwnershipFacet.py new file mode 100644 index 0000000..5b51fd8 --- /dev/null +++ b/dao/OwnershipFacet.py @@ -0,0 +1,197 @@ +# Code generated by moonworm : https://github.com/bugout-dev/moonworm +# Moonworm version : 0.1.6 + +import argparse +import json +import os +from pathlib import Path +from typing import Any, Dict, List, Optional, Union + +from brownie import Contract, network, project +from brownie.network.contract import ContractContainer +from eth_typing.evm import ChecksumAddress + + +PROJECT_DIRECTORY = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts") + + +def boolean_argument_type(raw_value: str) -> bool: + TRUE_VALUES = ["1", "t", "y", "true", "yes"] + FALSE_VALUES = ["0", "f", "n", "false", "no"] + + if raw_value.lower() in TRUE_VALUES: + return True + elif raw_value.lower() in FALSE_VALUES: + return False + + raise ValueError( + f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}" + ) + + +def bytes_argument_type(raw_value: str) -> bytes: + return raw_value.encode() + + +def get_abi_json(abi_name: str) -> List[Dict[str, Any]]: + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + abi_json = build.get("abi") + if abi_json is None: + raise ValueError(f"Could not find ABI definition in: {abi_full_path}") + + return abi_json + + +def contract_from_build(abi_name: str) -> ContractContainer: + # This is workaround because brownie currently doesn't support loading the same project multiple + # times. This causes problems when using multiple contracts from the same project in the same + # python project. + PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY)) + + abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json") + if not os.path.isfile(abi_full_path): + raise IOError( + f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?" + ) + + with open(abi_full_path, "r") as ifp: + build = json.load(ifp) + + return ContractContainer(PROJECT, build) + + +class OwnershipFacet: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "OwnershipFacet" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("OwnershipFacet") + if self.address is not None: + self.contract: Optional[Contract] = Contract.from_abi( + self.contract_name, self.address, self.abi + ) + + def deploy(self, transaction_config): + contract_class = contract_from_build(self.contract_name) + deployed_contract = contract_class.deploy(transaction_config) + self.address = deployed_contract.address + self.contract = deployed_contract + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + def owner(self) -> Any: + self.assert_contract_is_instantiated() + return self.contract.owner.call() + + def transfer_ownership( + self, _new_owner: ChecksumAddress, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.transferOwnership(_new_owner, transaction_config) + + +def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]: + signer = network.accounts.load(args.sender, args.password) + transaction_config: Dict[str, Any] = {"from": signer} + if args.gas_price is not None: + transaction_config["gas_price"] = args.gas_price + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + return transaction_config + + +def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None: + parser.add_argument( + "--network", required=True, help="Name of brownie network to connect to" + ) + parser.add_argument( + "--address", required=False, help="Address of deployed contract to connect to" + ) + if not transact: + return + parser.add_argument( + "--sender", required=True, help="Path to keystore file for transaction sender" + ) + parser.add_argument( + "--password", + required=False, + help="Password to keystore file (if you do not provide it, you will be prompted for it)", + ) + parser.add_argument( + "--gas-price", default=None, help="Gas price at which to submit transaction" + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = OwnershipFacet(None) + result = contract.deploy(transaction_config=transaction_config) + print(result) + + +def handle_owner(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = OwnershipFacet(args.address) + result = contract.owner() + print(result) + + +def handle_transfer_ownership(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = OwnershipFacet(args.address) + transaction_config = get_transaction_config(args) + result = contract.transfer_ownership( + _new_owner=args.new_owner_arg, transaction_config=transaction_config + ) + print(result) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for OwnershipFacet") + parser.set_defaults(func=lambda _: parser.print_help()) + subcommands = parser.add_subparsers() + + deploy_parser = subcommands.add_parser("deploy") + add_default_arguments(deploy_parser, True) + deploy_parser.set_defaults(func=handle_deploy) + + owner_parser = subcommands.add_parser("owner") + add_default_arguments(owner_parser, False) + owner_parser.set_defaults(func=handle_owner) + + transfer_ownership_parser = subcommands.add_parser("transfer-ownership") + add_default_arguments(transfer_ownership_parser, True) + transfer_ownership_parser.add_argument( + "--new-owner-arg", required=True, help="Type: address" + ) + transfer_ownership_parser.set_defaults(func=handle_transfer_ownership) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main()