diff --git a/.github/workflows/chainlink.yml b/.github/workflows/chainlink.yml index fc95f0c9..fc7f8161 100644 --- a/.github/workflows/chainlink.yml +++ b/.github/workflows/chainlink.yml @@ -1,39 +1,41 @@ name: Chainlink protocol tests on: - pull_request: - paths: - - "cli/chainlink/**" - - "contracts/mock/MockChainlinkCoordinator.sol" - - "contracts/mock/MockLinkToken.sol" - - "contracts/mock/MockVRFUser.sol" - - ".github/workflows/chainlink.yml" - branches: - - main + pull_request: + paths: + - "cli/chainlink/**" + - "contracts/mock/MockChainlinkCoordinator.sol" + - "contracts/mock/MockLinkToken.sol" + - "contracts/mock/MockVRFUser.sol" + - ".github/workflows/chainlink.yml" + branches: + - main jobs: - build: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: "16" - - uses: actions/setup-python@v2 - with: - python-version: "3.9" - - name: Install ganache - run: npm install -g ganache-cli - - name: Upgrade pip - env: - BROWNIE_LIB: 1 - run: pip install -U pip - - name: Install additional dev dependencies - run: | - pip install black moonworm - - name: Install dependencies for CLI - working-directory: cli/ - run: | - pip install -e . - - name: Run tests - working-directory: cli/ - run: bash test.sh chainlink.test_chainlink + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + env: + BROWNIE_LIB: 1 + run: | + pip install -e . + - name: Run tests + working-directory: cli/ + run: bash test.sh chainlink.test_chainlink diff --git a/.github/workflows/crafting.yml b/.github/workflows/crafting.yml index 576aa1fe..b239006e 100644 --- a/.github/workflows/crafting.yml +++ b/.github/workflows/crafting.yml @@ -1,42 +1,44 @@ name: Crafting system tests on: - pull_request: - paths: - - "contracts/crafting/**" - - "contracts/mock/**" - - "cli/enginecli/test_crafting.py" - - "cli/enginecli/CraftingFacet.py" - - "cli/enginecli/Mock*.py" - - "cli/enginecli/Diamond*" - - "cli/enginecli/OwnershipFacet.py" - - ".github/workflows/crafting.yml" - branches: - - main + pull_request: + paths: + - "contracts/crafting/**" + - "contracts/mock/**" + - "cli/web3cli/test_crafting.py" + - "cli/web3cli/CraftingFacet.py" + - "cli/web3cli/Mock*.py" + - "cli/web3cli/Diamond*" + - "cli/web3cli/OwnershipFacet.py" + - ".github/workflows/crafting.yml" + branches: + - main jobs: - build: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: "16" - - uses: actions/setup-python@v2 - with: - python-version: "3.9" - - name: Install ganache - run: npm install -g ganache-cli - - name: Upgrade pip - env: - BROWNIE_LIB: 1 - run: pip install -U pip - - name: Install additional dev dependencies - run: | - pip install black moonworm - - name: Install dependencies for CLI - working-directory: cli/ - run: | - pip install -e . - - name: Run tests - working-directory: cli/ - run: bash test.sh enginecli.test_crafting + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + env: + BROWNIE_LIB: 1 + run: | + pip install -e . + - name: Run tests + working-directory: cli/ + run: bash test.sh web3cli.test_crafting diff --git a/.github/workflows/dropper.yml b/.github/workflows/dropper.yml index 1fd33443..90f4a312 100644 --- a/.github/workflows/dropper.yml +++ b/.github/workflows/dropper.yml @@ -1,40 +1,42 @@ name: Dropper system tests on: - pull_request: - paths: - - "contracts/Dropper.sol" - - "contracts/mock/**" - - "cli/enginecli/test_dropper.py" - - "cli/enginecli/Dropper.py" - - "cli/enginecli/Mock*.py" - - ".github/workflows/dropper.yml" - branches: - - main + pull_request: + paths: + - "contracts/Dropper.sol" + - "contracts/mock/**" + - "cli/web3cli/test_dropper.py" + - "cli/web3cli/Dropper.py" + - "cli/web3cli/Mock*.py" + - ".github/workflows/dropper.yml" + branches: + - main jobs: - build: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: "16" - - uses: actions/setup-python@v2 - with: - python-version: "3.9" - - name: Install ganache - run: npm install -g ganache-cli - - name: Upgrade pip - env: - BROWNIE_LIB: 1 - run: pip install -U pip - - name: Install additional dev dependencies - run: | - pip install black moonworm - - name: Install dependencies for CLI - working-directory: cli/ - run: | - pip install -e . - - name: Run tests - working-directory: cli/ - run: bash test.sh enginecli.test_dropper + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + env: + BROWNIE_LIB: 1 + run: | + pip install -e . + - name: Run tests + working-directory: cli/ + run: bash test.sh web3cli.test_dropper diff --git a/.github/workflows/gofp.yml b/.github/workflows/gofp.yml index 3747dbb5..7d981bd4 100644 --- a/.github/workflows/gofp.yml +++ b/.github/workflows/gofp.yml @@ -1,42 +1,44 @@ name: Garden of Forking Paths system tests on: - pull_request: - paths: - - "contracts/mechanics/garden-of-forking-paths/**" - - "contracts/mock/**" - - "cli/enginecli/test_gofp.py" - - "cli/enginecli/GOFPFacet.py" - - "cli/enginecli/Mock*.py" - - "cli/enginecli/Diamond*" - - "cli/enginecli/OwnershipFacet.py" - - ".github/workflows/gofp.yml" - branches: - - main + pull_request: + paths: + - "contracts/mechanics/garden-of-forking-paths/**" + - "contracts/mock/**" + - "cli/web3cli/test_gofp.py" + - "cli/web3cli/GOFPFacet.py" + - "cli/web3cli/Mock*.py" + - "cli/web3cli/Diamond*" + - "cli/web3cli/OwnershipFacet.py" + - ".github/workflows/gofp.yml" + branches: + - main jobs: - build: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: "16" - - uses: actions/setup-python@v2 - with: - python-version: "3.9" - - name: Install ganache - run: npm install -g ganache-cli - - name: Upgrade pip - env: - BROWNIE_LIB: 1 - run: pip install -U pip - - name: Install additional dev dependencies - run: | - pip install black moonworm - - name: Install dependencies for CLI - working-directory: cli/ - run: | - pip install -e . - - name: Run tests - working-directory: cli/ - run: bash test.sh enginecli.test_gofp + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + env: + BROWNIE_LIB: 1 + run: | + pip install -e . + - name: Run tests + working-directory: cli/ + run: bash test.sh web3cli.test_gofp diff --git a/.github/workflows/inventory.yml b/.github/workflows/inventory.yml new file mode 100644 index 00000000..55dcf0eb --- /dev/null +++ b/.github/workflows/inventory.yml @@ -0,0 +1,43 @@ +name: Inventory system tests + +on: + pull_request: + paths: + - "contracts/inventory/**" + - "contracts/mock/**" + - "cli/web3cli/test_inventory.py" + - "cli/web3cli/InventoryFacet.py" + - "cli/web3cli/IInventory.py" + - "cli/web3cli/inventory_events.py" + - ".github/workflows/inventory.yml" + branches: + - main +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + env: + BROWNIE_LIB: 1 + run: | + pip install -e . + - name: Run tests + working-directory: cli/ + run: bash test.sh web3cli.test_inventory diff --git a/.github/workflows/lootbox.yml b/.github/workflows/lootbox.yml index 4d392f35..feaea0a4 100644 --- a/.github/workflows/lootbox.yml +++ b/.github/workflows/lootbox.yml @@ -1,46 +1,48 @@ name: Lootbox system tests on: - pull_request: - paths: - - "contracts/Lootbox*" - - "contracts/mock/**" - - "cli/enginecli/test_lootbox.py" - - "cli/enginecli/test_random_lootbox.py" - - "cli/enginecli/Lootbox*.py" - - "cli/enginecli/Mock*.py" - - "cli/enginecli/Diamond*" - - "cli/enginecli/OwnershipFacet.py" - - ".github/workflows/lootbox.yml" - branches: - - main + pull_request: + paths: + - "contracts/Lootbox*" + - "contracts/mock/**" + - "cli/web3cli/test_lootbox.py" + - "cli/web3cli/test_random_lootbox.py" + - "cli/web3cli/Lootbox*.py" + - "cli/web3cli/Mock*.py" + - "cli/web3cli/Diamond*" + - "cli/web3cli/OwnershipFacet.py" + - ".github/workflows/lootbox.yml" + branches: + - main jobs: - build: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: "16" - - uses: actions/setup-python@v2 - with: - python-version: "3.9" - - name: Install ganache - run: npm install -g ganache-cli - - name: Upgrade pip - env: - BROWNIE_LIB: 1 - run: pip install -U pip - - name: Install additional dev dependencies - run: | - pip install black moonworm - - name: Install dependencies for CLI - working-directory: cli/ - run: | - pip install -e . - - name: Run lootbox tests - working-directory: cli/ - run: bash test.sh enginecli.test_lootbox - - name: Run random lootbox tests - working-directory: cli/ - run: bash test.sh enginecli.test_random_lootbox + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + env: + BROWNIE_LIB: 1 + run: | + pip install -e . + - name: Run lootbox tests + working-directory: cli/ + run: bash test.sh web3cli.test_lootbox + - name: Run random lootbox tests + working-directory: cli/ + run: bash test.sh web3cli.test_random_lootbox diff --git a/.github/workflows/reentrancy-guard.yml b/.github/workflows/reentrancy-guard.yml index 10e9f14f..b8c3245b 100644 --- a/.github/workflows/reentrancy-guard.yml +++ b/.github/workflows/reentrancy-guard.yml @@ -1,42 +1,44 @@ name: Diamond Reentrancy Guard tests on: - pull_request: - paths: - - "contracts/test/**" - - "contracts/diamond/security/DiamondReentrancyGuard.sol" - - "cli/enginecli/test_reentrancy_guard.py" - - "cli/enginecli/ExploitContract.py" - - "cli/enginecli/ReentrancyExploitable.py" - - "cli/enginecli/Diamond*" - - "cli/enginecli/OwnershipFacet.py" - - ".github/workflows/reentrancy-guard.yml" - branches: - - main + pull_request: + paths: + - "contracts/test/**" + - "contracts/diamond/security/DiamondReentrancyGuard.sol" + - "cli/web3cli/test_reentrancy_guard.py" + - "cli/web3cli/ExploitContract.py" + - "cli/web3cli/ReentrancyExploitable.py" + - "cli/web3cli/Diamond*" + - "cli/web3cli/OwnershipFacet.py" + - ".github/workflows/reentrancy-guard.yml" + branches: + - main jobs: - build: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: "16" - - uses: actions/setup-python@v2 - with: - python-version: "3.9" - - name: Install ganache - run: npm install -g ganache-cli - - name: Upgrade pip - env: - BROWNIE_LIB: 1 - run: pip install -U pip - - name: Install additional dev dependencies - run: | - pip install black moonworm - - name: Install dependencies for CLI - working-directory: cli/ - run: | - pip install -e . - - name: Run tests - working-directory: cli/ - run: bash test.sh enginecli.test_crafting + build: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "16" + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - name: Install ganache + run: npm install -g ganache-cli + - name: Upgrade pip + env: + BROWNIE_LIB: 1 + run: pip install -U pip + - name: Install additional dev dependencies + run: | + pip install black moonworm + - name: Install dependencies for CLI + working-directory: cli/ + env: + BROWNIE_LIB: 1 + run: | + pip install -e . + - name: Run tests + working-directory: cli/ + run: bash test.sh web3cli.test_crafting diff --git a/.github/workflows/sdk.release.yml b/.github/workflows/sdk.release.yml index e7d014de..e57c291f 100644 --- a/.github/workflows/sdk.release.yml +++ b/.github/workflows/sdk.release.yml @@ -1,29 +1,29 @@ name: Release Engine SDK to NPM on: - push: - tags: - - 'sdk/v*' + push: + tags: + - "sdk/v*" defaults: - run: - working-directory: sdk + run: + working-directory: sdk jobs: - publish: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: Use Node.js 15.x - uses: actions/setup-node@v2 - with: - node-version: '15.x' - registry-url: 'https://registry.npmjs.org' - - name: Build and install dependencies - run: | - npm install - npm run build - - name: Publish package - env: - NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} - run: npm publish --access public + publish: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Use Node.js 15.x + uses: actions/setup-node@v2 + with: + node-version: "15.x" + registry-url: "https://registry.npmjs.org" + - name: Build and install dependencies + run: | + npm install + npm run build + - name: Publish package + env: + NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} + run: npm publish --access public diff --git a/.gitignore b/.gitignore index 5492dd9f..c409ba2b 100644 --- a/.gitignore +++ b/.gitignore @@ -128,28 +128,33 @@ dmypy.json # Pyre type checker .pyre/ -# Custom +# Moonstream + +# Python virtual envs .lootbox/ .secrets/ .vscode/ .engine/ .engineapi/ .enginecli/ +.web3/ +# Environment variable definitions prod.env test.env dev.env prod.test.env dev.test.env +# Alembic configs alembic.dev.ini alembic.prod.ini alembic.test.ini +# Scratch directories scratch/ outdir/ - #Node node_modules/ yarn.lock diff --git a/brownie-config.yaml b/brownie-config.yaml index f416b2fd..835be82c 100644 --- a/brownie-config.yaml +++ b/brownie-config.yaml @@ -1,7 +1,7 @@ dependencies: - - "OpenZeppelin/openzeppelin-contracts@4.3.2" + - "OpenZeppelin/openzeppelin-contracts@4.4.0" - "bugout-dev/dao@0.0.7" - - "smartcontractkit/chainlink@1.10.0" + - "smartcontractkit/chainlink@1.13.3" compiler: solc: @@ -9,4 +9,4 @@ compiler: - "@openzeppelin-contracts=OpenZeppelin/openzeppelin-contracts@4.4.0" - "@openzeppelin/contracts=OpenZeppelin/openzeppelin-contracts@4.4.0" - "@moonstream=bugout-dev/dao@0.0.7" - - "@chainlink=smartcontractkit/chainlink@1.10.0" + - "@chainlink=smartcontractkit/chainlink@1.13.3" diff --git a/cli/setup.py b/cli/setup.py index 17293178..81f259c5 100644 --- a/cli/setup.py +++ b/cli/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -with open("enginecli/version.txt") as ifp: +with open("web3cli/version.txt") as ifp: VERSION = ifp.read().strip() long_description = "" @@ -8,7 +8,7 @@ long_description = ifp.read() setup( - name="enginecli", + name="web3cli", version=VERSION, packages=find_packages(), install_requires=["boto3", "eth-brownie", "tqdm", "tabulate"], @@ -30,7 +30,7 @@ python_requires=">=3.6", entry_points={ "console_scripts": [ - "enginecli=enginecli.cli:main", + "web3cli=web3cli.cli:main", ] }, include_package_data=True, diff --git a/cli/enginecli/ClaimProxy.py b/cli/web3cli/ClaimProxy.py similarity index 100% rename from cli/enginecli/ClaimProxy.py rename to cli/web3cli/ClaimProxy.py diff --git a/cli/enginecli/CraftingFacet.py b/cli/web3cli/CraftingFacet.py similarity index 100% rename from cli/enginecli/CraftingFacet.py rename to cli/web3cli/CraftingFacet.py diff --git a/cli/enginecli/Diamond.py b/cli/web3cli/Diamond.py similarity index 100% rename from cli/enginecli/Diamond.py rename to cli/web3cli/Diamond.py diff --git a/cli/enginecli/DiamondCutFacet.py b/cli/web3cli/DiamondCutFacet.py similarity index 100% rename from cli/enginecli/DiamondCutFacet.py rename to cli/web3cli/DiamondCutFacet.py diff --git a/cli/enginecli/DiamondLoupeFacet.py b/cli/web3cli/DiamondLoupeFacet.py similarity index 100% rename from cli/enginecli/DiamondLoupeFacet.py rename to cli/web3cli/DiamondLoupeFacet.py diff --git a/cli/enginecli/Dropper.py b/cli/web3cli/Dropper.py similarity index 100% rename from cli/enginecli/Dropper.py rename to cli/web3cli/Dropper.py diff --git a/cli/enginecli/DropperFacet.py b/cli/web3cli/DropperFacet.py similarity index 100% rename from cli/enginecli/DropperFacet.py rename to cli/web3cli/DropperFacet.py diff --git a/cli/enginecli/ERC1155CompatibleClaimProxy.py b/cli/web3cli/ERC1155CompatibleClaimProxy.py similarity index 100% rename from cli/enginecli/ERC1155CompatibleClaimProxy.py rename to cli/web3cli/ERC1155CompatibleClaimProxy.py diff --git a/cli/enginecli/ERC721CompatibleClaimProxy.py b/cli/web3cli/ERC721CompatibleClaimProxy.py similarity index 100% rename from cli/enginecli/ERC721CompatibleClaimProxy.py rename to cli/web3cli/ERC721CompatibleClaimProxy.py diff --git a/cli/enginecli/ExploitContract.py b/cli/web3cli/ExploitContract.py similarity index 100% rename from cli/enginecli/ExploitContract.py rename to cli/web3cli/ExploitContract.py diff --git a/cli/enginecli/GOFPFacet.py b/cli/web3cli/GOFPFacet.py similarity index 100% rename from cli/enginecli/GOFPFacet.py rename to cli/web3cli/GOFPFacet.py diff --git a/cli/web3cli/IInventory.py b/cli/web3cli/IInventory.py new file mode 100644 index 00000000..efdbd70e --- /dev/null +++ b/cli/web3cli/IInventory.py @@ -0,0 +1,636 @@ +# Code generated by moonworm : https://github.com/bugout-dev/moonworm +# Moonworm version : 0.6.2 + +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) -> str: + return raw_value + + +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 IInventory: + def __init__(self, contract_address: Optional[ChecksumAddress]): + self.contract_name = "IInventory" + self.address = contract_address + self.contract = None + self.abi = get_abi_json("IInventory") + 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 + return deployed_contract.tx + + def assert_contract_is_instantiated(self) -> None: + if self.contract is None: + raise Exception("contract has not been instantiated") + + def verify_contract(self): + self.assert_contract_is_instantiated() + contract_class = contract_from_build(self.contract_name) + contract_class.publish_source(self.contract) + + def admin_terminus_info( + self, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.adminTerminusInfo.call(block_identifier=block_number) + + def create_slot(self, persistent: bool, slot_uri: str, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.createSlot(persistent, slot_uri, transaction_config) + + def equip( + self, + subject_token_id: int, + slot: int, + item_type: int, + item_address: ChecksumAddress, + item_token_id: int, + amount: int, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.equip( + subject_token_id, + slot, + item_type, + item_address, + item_token_id, + amount, + transaction_config, + ) + + def get_equipped_item( + self, + subject_token_id: int, + slot: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getEquippedItem.call( + subject_token_id, slot, block_identifier=block_number + ) + + def get_slot_by_id( + self, slot_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getSlotById.call(slot_id, block_identifier=block_number) + + def get_slot_uri( + self, slot_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.getSlotURI.call(slot_id, block_identifier=block_number) + + def mark_item_as_equippable_in_slot( + self, + slot: int, + item_type: int, + item_address: ChecksumAddress, + item_pool_id: int, + max_amount: int, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.markItemAsEquippableInSlot( + slot, item_type, item_address, item_pool_id, max_amount, transaction_config + ) + + def max_amount_of_item_in_slot( + self, + slot: int, + item_type: int, + item_address: ChecksumAddress, + item_pool_id: int, + block_number: Optional[Union[str, int]] = "latest", + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.maxAmountOfItemInSlot.call( + slot, item_type, item_address, item_pool_id, block_identifier=block_number + ) + + def num_slots(self, block_number: Optional[Union[str, int]] = "latest") -> Any: + self.assert_contract_is_instantiated() + return self.contract.numSlots.call(block_identifier=block_number) + + def set_slot_persistent( + self, slot_id: int, persistent: bool, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setSlotPersistent(slot_id, persistent, transaction_config) + + def set_slot_uri(self, new_slot_uri: str, slot_id: int, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setSlotURI(new_slot_uri, slot_id, transaction_config) + + def slot_is_persistent( + self, slot_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.slotIsPersistent.call( + slot_id, block_identifier=block_number + ) + + def subject(self, block_number: Optional[Union[str, int]] = "latest") -> Any: + self.assert_contract_is_instantiated() + return self.contract.subject.call(block_identifier=block_number) + + def unequip( + self, + subject_token_id: int, + slot: int, + unequip_all: bool, + amount: int, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.unequip( + subject_token_id, slot, unequip_all, amount, 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.max_fee_per_gas is not None: + transaction_config["max_fee"] = args.max_fee_per_gas + if args.max_priority_fee_per_gas is not None: + transaction_config["priority_fee"] = args.max_priority_fee_per_gas + if args.confirmations is not None: + transaction_config["required_confs"] = args.confirmations + if args.nonce is not None: + transaction_config["nonce"] = args.nonce + 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: + parser.add_argument( + "--block-number", + required=False, + type=int, + help="Call at the given block number, defaults to latest", + ) + 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( + "--max-fee-per-gas", + default=None, + help="Max fee per gas for EIP1559 transactions", + ) + parser.add_argument( + "--max-priority-fee-per-gas", + default=None, + help="Max priority fee per gas for EIP1559 transactions", + ) + parser.add_argument( + "--confirmations", + type=int, + default=None, + help="Number of confirmations to await before considering a transaction completed", + ) + parser.add_argument( + "--nonce", type=int, default=None, help="Nonce for the transaction (optional)" + ) + parser.add_argument( + "--value", default=None, help="Value of the transaction in wei(optional)" + ) + parser.add_argument("--verbose", action="store_true", help="Print verbose output") + + +def handle_deploy(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = get_transaction_config(args) + contract = IInventory(None) + result = contract.deploy(transaction_config=transaction_config) + print(result) + if args.verbose: + print(result.info()) + + +def handle_verify_contract(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + result = contract.verify_contract() + print(result) + + +def handle_admin_terminus_info(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + result = contract.admin_terminus_info(block_number=args.block_number) + print(result) + + +def handle_create_slot(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + transaction_config = get_transaction_config(args) + result = contract.create_slot( + persistent=args.persistent, + slot_uri=args.slot_uri, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_equip(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + transaction_config = get_transaction_config(args) + result = contract.equip( + subject_token_id=args.subject_token_id, + slot=args.slot, + item_type=args.item_type, + item_address=args.item_address, + item_token_id=args.item_token_id, + amount=args.amount, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_get_equipped_item(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + result = contract.get_equipped_item( + subject_token_id=args.subject_token_id, + slot=args.slot, + block_number=args.block_number, + ) + print(result) + + +def handle_get_slot_by_id(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + result = contract.get_slot_by_id( + slot_id=args.slot_id, block_number=args.block_number + ) + print(result) + + +def handle_get_slot_uri(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + result = contract.get_slot_uri(slot_id=args.slot_id, block_number=args.block_number) + print(result) + + +def handle_mark_item_as_equippable_in_slot(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + transaction_config = get_transaction_config(args) + result = contract.mark_item_as_equippable_in_slot( + slot=args.slot, + item_type=args.item_type, + item_address=args.item_address, + item_pool_id=args.item_pool_id, + max_amount=args.max_amount, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_max_amount_of_item_in_slot(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + result = contract.max_amount_of_item_in_slot( + slot=args.slot, + item_type=args.item_type, + item_address=args.item_address, + item_pool_id=args.item_pool_id, + block_number=args.block_number, + ) + print(result) + + +def handle_num_slots(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + result = contract.num_slots(block_number=args.block_number) + print(result) + + +def handle_set_slot_persistent(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_slot_persistent( + slot_id=args.slot_id, + persistent=args.persistent, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_set_slot_uri(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_slot_uri( + new_slot_uri=args.new_slot_uri, + slot_id=args.slot_id, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_slot_is_persistent(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + result = contract.slot_is_persistent( + slot_id=args.slot_id, block_number=args.block_number + ) + print(result) + + +def handle_subject(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + result = contract.subject(block_number=args.block_number) + print(result) + + +def handle_unequip(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + transaction_config = get_transaction_config(args) + result = contract.unequip( + subject_token_id=args.subject_token_id, + slot=args.slot, + unequip_all=args.unequip_all, + amount=args.amount, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def generate_cli() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="CLI for IInventory") + 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) + + verify_contract_parser = subcommands.add_parser("verify-contract") + add_default_arguments(verify_contract_parser, False) + verify_contract_parser.set_defaults(func=handle_verify_contract) + + admin_terminus_info_parser = subcommands.add_parser("admin-terminus-info") + add_default_arguments(admin_terminus_info_parser, False) + admin_terminus_info_parser.set_defaults(func=handle_admin_terminus_info) + + create_slot_parser = subcommands.add_parser("create-slot") + add_default_arguments(create_slot_parser, True) + create_slot_parser.add_argument( + "--persistent", required=True, help="Type: bool", type=boolean_argument_type + ) + create_slot_parser.add_argument( + "--slot-uri", required=True, help="Type: string", type=str + ) + create_slot_parser.set_defaults(func=handle_create_slot) + + equip_parser = subcommands.add_parser("equip") + add_default_arguments(equip_parser, True) + equip_parser.add_argument( + "--subject-token-id", required=True, help="Type: uint256", type=int + ) + equip_parser.add_argument("--slot", required=True, help="Type: uint256", type=int) + equip_parser.add_argument( + "--item-type", required=True, help="Type: uint256", type=int + ) + equip_parser.add_argument("--item-address", required=True, help="Type: address") + equip_parser.add_argument( + "--item-token-id", required=True, help="Type: uint256", type=int + ) + equip_parser.add_argument("--amount", required=True, help="Type: uint256", type=int) + equip_parser.set_defaults(func=handle_equip) + + get_equipped_item_parser = subcommands.add_parser("get-equipped-item") + add_default_arguments(get_equipped_item_parser, False) + get_equipped_item_parser.add_argument( + "--subject-token-id", required=True, help="Type: uint256", type=int + ) + get_equipped_item_parser.add_argument( + "--slot", required=True, help="Type: uint256", type=int + ) + get_equipped_item_parser.set_defaults(func=handle_get_equipped_item) + + get_slot_by_id_parser = subcommands.add_parser("get-slot-by-id") + add_default_arguments(get_slot_by_id_parser, False) + get_slot_by_id_parser.add_argument( + "--slot-id", required=True, help="Type: uint256", type=int + ) + get_slot_by_id_parser.set_defaults(func=handle_get_slot_by_id) + + get_slot_uri_parser = subcommands.add_parser("get-slot-uri") + add_default_arguments(get_slot_uri_parser, False) + get_slot_uri_parser.add_argument( + "--slot-id", required=True, help="Type: uint256", type=int + ) + get_slot_uri_parser.set_defaults(func=handle_get_slot_uri) + + mark_item_as_equippable_in_slot_parser = subcommands.add_parser( + "mark-item-as-equippable-in-slot" + ) + add_default_arguments(mark_item_as_equippable_in_slot_parser, True) + mark_item_as_equippable_in_slot_parser.add_argument( + "--slot", required=True, help="Type: uint256", type=int + ) + mark_item_as_equippable_in_slot_parser.add_argument( + "--item-type", required=True, help="Type: uint256", type=int + ) + mark_item_as_equippable_in_slot_parser.add_argument( + "--item-address", required=True, help="Type: address" + ) + mark_item_as_equippable_in_slot_parser.add_argument( + "--item-pool-id", required=True, help="Type: uint256", type=int + ) + mark_item_as_equippable_in_slot_parser.add_argument( + "--max-amount", required=True, help="Type: uint256", type=int + ) + mark_item_as_equippable_in_slot_parser.set_defaults( + func=handle_mark_item_as_equippable_in_slot + ) + + max_amount_of_item_in_slot_parser = subcommands.add_parser( + "max-amount-of-item-in-slot" + ) + add_default_arguments(max_amount_of_item_in_slot_parser, False) + max_amount_of_item_in_slot_parser.add_argument( + "--slot", required=True, help="Type: uint256", type=int + ) + max_amount_of_item_in_slot_parser.add_argument( + "--item-type", required=True, help="Type: uint256", type=int + ) + max_amount_of_item_in_slot_parser.add_argument( + "--item-address", required=True, help="Type: address" + ) + max_amount_of_item_in_slot_parser.add_argument( + "--item-pool-id", required=True, help="Type: uint256", type=int + ) + max_amount_of_item_in_slot_parser.set_defaults( + func=handle_max_amount_of_item_in_slot + ) + + num_slots_parser = subcommands.add_parser("num-slots") + add_default_arguments(num_slots_parser, False) + num_slots_parser.set_defaults(func=handle_num_slots) + + set_slot_persistent_parser = subcommands.add_parser("set-slot-persistent") + add_default_arguments(set_slot_persistent_parser, True) + set_slot_persistent_parser.add_argument( + "--slot-id", required=True, help="Type: uint256", type=int + ) + set_slot_persistent_parser.add_argument( + "--persistent", required=True, help="Type: bool", type=boolean_argument_type + ) + set_slot_persistent_parser.set_defaults(func=handle_set_slot_persistent) + + set_slot_uri_parser = subcommands.add_parser("set-slot-uri") + add_default_arguments(set_slot_uri_parser, True) + set_slot_uri_parser.add_argument( + "--new-slot-uri", required=True, help="Type: string", type=str + ) + set_slot_uri_parser.add_argument( + "--slot-id", required=True, help="Type: uint256", type=int + ) + set_slot_uri_parser.set_defaults(func=handle_set_slot_uri) + + slot_is_persistent_parser = subcommands.add_parser("slot-is-persistent") + add_default_arguments(slot_is_persistent_parser, False) + slot_is_persistent_parser.add_argument( + "--slot-id", required=True, help="Type: uint256", type=int + ) + slot_is_persistent_parser.set_defaults(func=handle_slot_is_persistent) + + subject_parser = subcommands.add_parser("subject") + add_default_arguments(subject_parser, False) + subject_parser.set_defaults(func=handle_subject) + + unequip_parser = subcommands.add_parser("unequip") + add_default_arguments(unequip_parser, True) + unequip_parser.add_argument( + "--subject-token-id", required=True, help="Type: uint256", type=int + ) + unequip_parser.add_argument("--slot", required=True, help="Type: uint256", type=int) + unequip_parser.add_argument( + "--unequip-all", required=True, help="Type: bool", type=boolean_argument_type + ) + unequip_parser.add_argument( + "--amount", required=True, help="Type: uint256", type=int + ) + unequip_parser.set_defaults(func=handle_unequip) + + return parser + + +def main() -> None: + parser = generate_cli() + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/cli/enginecli/ITerminus.py b/cli/web3cli/ITerminus.py similarity index 100% rename from cli/enginecli/ITerminus.py rename to cli/web3cli/ITerminus.py diff --git a/cli/enginecli/InventoryFacet.py b/cli/web3cli/InventoryFacet.py similarity index 74% rename from cli/enginecli/InventoryFacet.py rename to cli/web3cli/InventoryFacet.py index b36d64a7..a50be9b4 100644 --- a/cli/enginecli/InventoryFacet.py +++ b/cli/web3cli/InventoryFacet.py @@ -96,44 +96,15 @@ def verify_contract(self): contract_class = contract_from_build(self.contract_name) contract_class.publish_source(self.contract) - def add_backpack_to_subject( - self, - slot_qty: int, - to_subject_token_id: int, - slot_type: int, - slot_uri: str, - transaction_config, - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.addBackpackToSubject( - slot_qty, to_subject_token_id, slot_type, slot_uri, transaction_config - ) - def admin_terminus_info( self, block_number: Optional[Union[str, int]] = "latest" ) -> Any: self.assert_contract_is_instantiated() return self.contract.adminTerminusInfo.call(block_identifier=block_number) - def assign_slot_type(self, slot: int, slot_type: int, transaction_config) -> Any: - self.assert_contract_is_instantiated() - return self.contract.assignSlotType(slot, slot_type, transaction_config) - - def create_slot( - self, unequippable: bool, slot_type: int, slot_uri: str, transaction_config - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.createSlot( - unequippable, slot_type, slot_uri, transaction_config - ) - - def create_slot_type( - self, slot_type: int, slot_type_name: str, transaction_config - ) -> Any: + def create_slot(self, persistent: bool, slot_uri: str, transaction_config) -> Any: self.assert_contract_is_instantiated() - return self.contract.createSlotType( - slot_type, slot_type_name, transaction_config - ) + return self.contract.createSlot(persistent, slot_uri, transaction_config) def equip( self, @@ -156,25 +127,6 @@ def equip( transaction_config, ) - def equip_batch( - self, subject_token_id: int, slots: List, items: List, transaction_config - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.equipBatch( - subject_token_id, slots, items, transaction_config - ) - - def get_all_equipped_items( - self, - subject_token_id: int, - slots: List, - block_number: Optional[Union[str, int]] = "latest", - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.getAllEquippedItems.call( - subject_token_id, slots, block_identifier=block_number - ) - def get_equipped_item( self, subject_token_id: int, @@ -192,26 +144,12 @@ def get_slot_by_id( self.assert_contract_is_instantiated() return self.contract.getSlotById.call(slot_id, block_identifier=block_number) - def get_slot_type( - self, slot_type: int, block_number: Optional[Union[str, int]] = "latest" - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.getSlotType.call(slot_type, block_identifier=block_number) - def get_slot_uri( self, slot_id: int, block_number: Optional[Union[str, int]] = "latest" ) -> Any: self.assert_contract_is_instantiated() return self.contract.getSlotURI.call(slot_id, block_identifier=block_number) - def get_subject_token_slots( - self, subject_token_id: int, block_number: Optional[Union[str, int]] = "latest" - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.getSubjectTokenSlots.call( - subject_token_id, block_identifier=block_number - ) - def init( self, admin_terminus_address: ChecksumAddress, @@ -299,23 +237,21 @@ def on_erc721_received( arg1, arg2, arg3, arg4, transaction_config ) - def set_slot_unequippable( - self, unquippable: bool, slot_id: int, transaction_config + def set_slot_persistent( + self, slot_id: int, persistent: bool, transaction_config ) -> Any: self.assert_contract_is_instantiated() - return self.contract.setSlotUnequippable( - unquippable, slot_id, transaction_config - ) + return self.contract.setSlotPersistent(slot_id, persistent, transaction_config) def set_slot_uri(self, new_slot_uri: str, slot_id: int, transaction_config) -> Any: self.assert_contract_is_instantiated() - return self.contract.setSlotUri(new_slot_uri, slot_id, transaction_config) + return self.contract.setSlotURI(new_slot_uri, slot_id, transaction_config) - def slot_is_unequippable( + def slot_is_persistent( self, slot_id: int, block_number: Optional[Union[str, int]] = "latest" ) -> Any: self.assert_contract_is_instantiated() - return self.contract.slotIsUnequippable.call( + return self.contract.slotIsPersistent.call( slot_id, block_identifier=block_number ) @@ -429,22 +365,6 @@ def handle_verify_contract(args: argparse.Namespace) -> None: print(result) -def handle_add_backpack_to_subject(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = InventoryFacet(args.address) - transaction_config = get_transaction_config(args) - result = contract.add_backpack_to_subject( - slot_qty=args.slot_qty, - to_subject_token_id=args.to_subject_token_id, - slot_type=args.slot_type, - slot_uri=args.slot_uri, - transaction_config=transaction_config, - ) - print(result) - if args.verbose: - print(result.info()) - - def handle_admin_terminus_info(args: argparse.Namespace) -> None: network.connect(args.network) contract = InventoryFacet(args.address) @@ -452,25 +372,12 @@ def handle_admin_terminus_info(args: argparse.Namespace) -> None: print(result) -def handle_assign_slot_type(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = InventoryFacet(args.address) - transaction_config = get_transaction_config(args) - result = contract.assign_slot_type( - slot=args.slot, slot_type=args.slot_type, transaction_config=transaction_config - ) - print(result) - if args.verbose: - print(result.info()) - - def handle_create_slot(args: argparse.Namespace) -> None: network.connect(args.network) contract = InventoryFacet(args.address) transaction_config = get_transaction_config(args) result = contract.create_slot( - unequippable=args.unequippable, - slot_type=args.slot_type, + persistent=args.persistent, slot_uri=args.slot_uri, transaction_config=transaction_config, ) @@ -479,20 +386,6 @@ def handle_create_slot(args: argparse.Namespace) -> None: print(result.info()) -def handle_create_slot_type(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = InventoryFacet(args.address) - transaction_config = get_transaction_config(args) - result = contract.create_slot_type( - slot_type=args.slot_type, - slot_type_name=args.slot_type_name, - transaction_config=transaction_config, - ) - print(result) - if args.verbose: - print(result.info()) - - def handle_equip(args: argparse.Namespace) -> None: network.connect(args.network) contract = InventoryFacet(args.address) @@ -511,32 +404,6 @@ def handle_equip(args: argparse.Namespace) -> None: print(result.info()) -def handle_equip_batch(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = InventoryFacet(args.address) - transaction_config = get_transaction_config(args) - result = contract.equip_batch( - subject_token_id=args.subject_token_id, - slots=args.slots, - items=args.items, - transaction_config=transaction_config, - ) - print(result) - if args.verbose: - print(result.info()) - - -def handle_get_all_equipped_items(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = InventoryFacet(args.address) - result = contract.get_all_equipped_items( - subject_token_id=args.subject_token_id, - slots=args.slots, - block_number=args.block_number, - ) - print(result) - - def handle_get_equipped_item(args: argparse.Namespace) -> None: network.connect(args.network) contract = InventoryFacet(args.address) @@ -557,15 +424,6 @@ def handle_get_slot_by_id(args: argparse.Namespace) -> None: print(result) -def handle_get_slot_type(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = InventoryFacet(args.address) - result = contract.get_slot_type( - slot_type=args.slot_type, block_number=args.block_number - ) - print(result) - - def handle_get_slot_uri(args: argparse.Namespace) -> None: network.connect(args.network) contract = InventoryFacet(args.address) @@ -573,15 +431,6 @@ def handle_get_slot_uri(args: argparse.Namespace) -> None: print(result) -def handle_get_subject_token_slots(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = InventoryFacet(args.address) - result = contract.get_subject_token_slots( - subject_token_id=args.subject_token_id, block_number=args.block_number - ) - print(result) - - def handle_init(args: argparse.Namespace) -> None: network.connect(args.network) contract = InventoryFacet(args.address) @@ -684,13 +533,13 @@ def handle_on_erc721_received(args: argparse.Namespace) -> None: print(result.info()) -def handle_set_slot_unequippable(args: argparse.Namespace) -> None: +def handle_set_slot_persistent(args: argparse.Namespace) -> None: network.connect(args.network) contract = InventoryFacet(args.address) transaction_config = get_transaction_config(args) - result = contract.set_slot_unequippable( - unquippable=args.unquippable, + result = contract.set_slot_persistent( slot_id=args.slot_id, + persistent=args.persistent, transaction_config=transaction_config, ) print(result) @@ -712,10 +561,10 @@ def handle_set_slot_uri(args: argparse.Namespace) -> None: print(result.info()) -def handle_slot_is_unequippable(args: argparse.Namespace) -> None: +def handle_slot_is_persistent(args: argparse.Namespace) -> None: network.connect(args.network) contract = InventoryFacet(args.address) - result = contract.slot_is_unequippable( + result = contract.slot_is_persistent( slot_id=args.slot_id, block_number=args.block_number ) print(result) @@ -766,59 +615,20 @@ def generate_cli() -> argparse.ArgumentParser: add_default_arguments(verify_contract_parser, False) verify_contract_parser.set_defaults(func=handle_verify_contract) - add_backpack_to_subject_parser = subcommands.add_parser("add-backpack-to-subject") - add_default_arguments(add_backpack_to_subject_parser, True) - add_backpack_to_subject_parser.add_argument( - "--slot-qty", required=True, help="Type: uint256", type=int - ) - add_backpack_to_subject_parser.add_argument( - "--to-subject-token-id", required=True, help="Type: uint256", type=int - ) - add_backpack_to_subject_parser.add_argument( - "--slot-type", required=True, help="Type: uint256", type=int - ) - add_backpack_to_subject_parser.add_argument( - "--slot-uri", required=True, help="Type: string", type=str - ) - add_backpack_to_subject_parser.set_defaults(func=handle_add_backpack_to_subject) - admin_terminus_info_parser = subcommands.add_parser("admin-terminus-info") add_default_arguments(admin_terminus_info_parser, False) admin_terminus_info_parser.set_defaults(func=handle_admin_terminus_info) - assign_slot_type_parser = subcommands.add_parser("assign-slot-type") - add_default_arguments(assign_slot_type_parser, True) - assign_slot_type_parser.add_argument( - "--slot", required=True, help="Type: uint256", type=int - ) - assign_slot_type_parser.add_argument( - "--slot-type", required=True, help="Type: uint256", type=int - ) - assign_slot_type_parser.set_defaults(func=handle_assign_slot_type) - create_slot_parser = subcommands.add_parser("create-slot") add_default_arguments(create_slot_parser, True) create_slot_parser.add_argument( - "--unequippable", required=True, help="Type: bool", type=boolean_argument_type - ) - create_slot_parser.add_argument( - "--slot-type", required=True, help="Type: uint256", type=int + "--persistent", required=True, help="Type: bool", type=boolean_argument_type ) create_slot_parser.add_argument( "--slot-uri", required=True, help="Type: string", type=str ) create_slot_parser.set_defaults(func=handle_create_slot) - create_slot_type_parser = subcommands.add_parser("create-slot-type") - add_default_arguments(create_slot_type_parser, True) - create_slot_type_parser.add_argument( - "--slot-type", required=True, help="Type: uint256", type=int - ) - create_slot_type_parser.add_argument( - "--slot-type-name", required=True, help="Type: string", type=str - ) - create_slot_type_parser.set_defaults(func=handle_create_slot_type) - equip_parser = subcommands.add_parser("equip") add_default_arguments(equip_parser, True) equip_parser.add_argument( @@ -835,29 +645,6 @@ def generate_cli() -> argparse.ArgumentParser: equip_parser.add_argument("--amount", required=True, help="Type: uint256", type=int) equip_parser.set_defaults(func=handle_equip) - equip_batch_parser = subcommands.add_parser("equip-batch") - add_default_arguments(equip_batch_parser, True) - equip_batch_parser.add_argument( - "--subject-token-id", required=True, help="Type: uint256", type=int - ) - equip_batch_parser.add_argument( - "--slots", required=True, help="Type: uint256[]", nargs="+" - ) - equip_batch_parser.add_argument( - "--items", required=True, help="Type: tuple[]", nargs="+" - ) - equip_batch_parser.set_defaults(func=handle_equip_batch) - - get_all_equipped_items_parser = subcommands.add_parser("get-all-equipped-items") - add_default_arguments(get_all_equipped_items_parser, False) - get_all_equipped_items_parser.add_argument( - "--subject-token-id", required=True, help="Type: uint256", type=int - ) - get_all_equipped_items_parser.add_argument( - "--slots", required=True, help="Type: uint256[]", nargs="+" - ) - get_all_equipped_items_parser.set_defaults(func=handle_get_all_equipped_items) - get_equipped_item_parser = subcommands.add_parser("get-equipped-item") add_default_arguments(get_equipped_item_parser, False) get_equipped_item_parser.add_argument( @@ -875,13 +662,6 @@ def generate_cli() -> argparse.ArgumentParser: ) get_slot_by_id_parser.set_defaults(func=handle_get_slot_by_id) - get_slot_type_parser = subcommands.add_parser("get-slot-type") - add_default_arguments(get_slot_type_parser, False) - get_slot_type_parser.add_argument( - "--slot-type", required=True, help="Type: uint256", type=int - ) - get_slot_type_parser.set_defaults(func=handle_get_slot_type) - get_slot_uri_parser = subcommands.add_parser("get-slot-uri") add_default_arguments(get_slot_uri_parser, False) get_slot_uri_parser.add_argument( @@ -889,13 +669,6 @@ def generate_cli() -> argparse.ArgumentParser: ) get_slot_uri_parser.set_defaults(func=handle_get_slot_uri) - get_subject_token_slots_parser = subcommands.add_parser("get-subject-token-slots") - add_default_arguments(get_subject_token_slots_parser, False) - get_subject_token_slots_parser.add_argument( - "--subject-token-id", required=True, help="Type: uint256", type=int - ) - get_subject_token_slots_parser.set_defaults(func=handle_get_subject_token_slots) - init_parser = subcommands.add_parser("init") add_default_arguments(init_parser, True) init_parser.add_argument( @@ -1010,15 +783,15 @@ def generate_cli() -> argparse.ArgumentParser: ) on_erc721_received_parser.set_defaults(func=handle_on_erc721_received) - set_slot_unequippable_parser = subcommands.add_parser("set-slot-unequippable") - add_default_arguments(set_slot_unequippable_parser, True) - set_slot_unequippable_parser.add_argument( - "--unquippable", required=True, help="Type: bool", type=boolean_argument_type - ) - set_slot_unequippable_parser.add_argument( + set_slot_persistent_parser = subcommands.add_parser("set-slot-persistent") + add_default_arguments(set_slot_persistent_parser, True) + set_slot_persistent_parser.add_argument( "--slot-id", required=True, help="Type: uint256", type=int ) - set_slot_unequippable_parser.set_defaults(func=handle_set_slot_unequippable) + set_slot_persistent_parser.add_argument( + "--persistent", required=True, help="Type: bool", type=boolean_argument_type + ) + set_slot_persistent_parser.set_defaults(func=handle_set_slot_persistent) set_slot_uri_parser = subcommands.add_parser("set-slot-uri") add_default_arguments(set_slot_uri_parser, True) @@ -1030,12 +803,12 @@ def generate_cli() -> argparse.ArgumentParser: ) set_slot_uri_parser.set_defaults(func=handle_set_slot_uri) - slot_is_unequippable_parser = subcommands.add_parser("slot-is-unequippable") - add_default_arguments(slot_is_unequippable_parser, False) - slot_is_unequippable_parser.add_argument( + slot_is_persistent_parser = subcommands.add_parser("slot-is-persistent") + add_default_arguments(slot_is_persistent_parser, False) + slot_is_persistent_parser.add_argument( "--slot-id", required=True, help="Type: uint256", type=int ) - slot_is_unequippable_parser.set_defaults(func=handle_slot_is_unequippable) + slot_is_persistent_parser.set_defaults(func=handle_slot_is_persistent) subject_parser = subcommands.add_parser("subject") add_default_arguments(subject_parser, False) diff --git a/cli/enginecli/Lootbox.py b/cli/web3cli/Lootbox.py similarity index 100% rename from cli/enginecli/Lootbox.py rename to cli/web3cli/Lootbox.py diff --git a/cli/enginecli/MockERC1155.py b/cli/web3cli/MockERC1155.py similarity index 100% rename from cli/enginecli/MockERC1155.py rename to cli/web3cli/MockERC1155.py diff --git a/cli/enginecli/MockERC721.py b/cli/web3cli/MockERC721.py similarity index 100% rename from cli/enginecli/MockERC721.py rename to cli/web3cli/MockERC721.py diff --git a/cli/enginecli/MockErc20.py b/cli/web3cli/MockErc20.py similarity index 100% rename from cli/enginecli/MockErc20.py rename to cli/web3cli/MockErc20.py diff --git a/cli/enginecli/MockTerminus.py b/cli/web3cli/MockTerminus.py similarity index 100% rename from cli/enginecli/MockTerminus.py rename to cli/web3cli/MockTerminus.py diff --git a/cli/enginecli/OwnershipFacet.py b/cli/web3cli/OwnershipFacet.py similarity index 100% rename from cli/enginecli/OwnershipFacet.py rename to cli/web3cli/OwnershipFacet.py diff --git a/cli/enginecli/ReentrancyExploitable.py b/cli/web3cli/ReentrancyExploitable.py similarity index 100% rename from cli/enginecli/ReentrancyExploitable.py rename to cli/web3cli/ReentrancyExploitable.py diff --git a/cli/enginecli/__init__.py b/cli/web3cli/__init__.py similarity index 100% rename from cli/enginecli/__init__.py rename to cli/web3cli/__init__.py diff --git a/cli/enginecli/abi.py b/cli/web3cli/abi.py similarity index 100% rename from cli/enginecli/abi.py rename to cli/web3cli/abi.py diff --git a/cli/enginecli/cli.py b/cli/web3cli/cli.py similarity index 98% rename from cli/enginecli/cli.py rename to cli/web3cli/cli.py index 65974ea2..30c55f8e 100644 --- a/cli/enginecli/cli.py +++ b/cli/web3cli/cli.py @@ -1,7 +1,7 @@ import argparse import logging -from enginecli.ITerminus import ITerminus +from .ITerminus import ITerminus from . import ( core, diff --git a/cli/enginecli/core.py b/cli/web3cli/core.py similarity index 73% rename from cli/enginecli/core.py rename to cli/web3cli/core.py index 660cb83f..324b4542 100644 --- a/cli/enginecli/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", diff --git a/cli/enginecli/drop.py b/cli/web3cli/drop.py similarity index 100% rename from cli/enginecli/drop.py rename to cli/web3cli/drop.py diff --git a/cli/enginecli/flows.py b/cli/web3cli/flows.py similarity index 99% rename from cli/enginecli/flows.py rename to cli/web3cli/flows.py index 938400cc..ac44fab3 100644 --- a/cli/enginecli/flows.py +++ b/cli/web3cli/flows.py @@ -7,7 +7,7 @@ import time from typing import Any, Dict, Optional, List, Set -from enginecli.core import lootbox_item_to_tuple +from .core import lootbox_item_to_tuple from .MockErc20 import MockErc20 from brownie import network diff --git a/cli/enginecli/gas_profiler.py b/cli/web3cli/gas_profiler.py similarity index 100% rename from cli/enginecli/gas_profiler.py rename to cli/web3cli/gas_profiler.py diff --git a/cli/web3cli/inventory_events.py b/cli/web3cli/inventory_events.py new file mode 100644 index 00000000..ab31fc63 --- /dev/null +++ b/cli/web3cli/inventory_events.py @@ -0,0 +1,210 @@ +ADMINISTRATOR_DESIGNATED_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "adminTerminusAddress", + "type": "address", + }, + { + "indexed": True, + "internalType": "uint256", + "name": "adminTerminusPoolId", + "type": "uint256", + }, + ], + "name": "AdministratorDesignated", + "type": "event", +} + +NEW_SUBJECT_ADDRESS_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "contractAddress", + "type": "address", + } + ], + "name": "NewSubjectAddress", + "type": "event", +} + +SLOT_CREATED_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "creator", + "type": "address", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "slot", + "type": "uint256", + }, + ], + "name": "SlotCreated", + "type": "event", +} + +ITEM_MARKED_AS_EQUIPPABLE_IN_SLOT_ABI = { + "anonymous": False, + "inputs": [ + {"indexed": True, "internalType": "uint256", "name": "slot", "type": "uint256"}, + { + "indexed": True, + "internalType": "uint256", + "name": "itemType", + "type": "uint256", + }, + { + "indexed": True, + "internalType": "address", + "name": "itemAddress", + "type": "address", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "itemPoolId", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "maxAmount", + "type": "uint256", + }, + ], + "name": "ItemMarkedAsEquippableInSlot", + "type": "event", +} + +ITEM_EQUIPPED_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "subjectTokenId", + "type": "uint256", + }, + {"indexed": True, "internalType": "uint256", "name": "slot", "type": "uint256"}, + { + "indexed": False, + "internalType": "uint256", + "name": "itemType", + "type": "uint256", + }, + { + "indexed": True, + "internalType": "address", + "name": "itemAddress", + "type": "address", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "itemTokenId", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "amount", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "address", + "name": "equippedBy", + "type": "address", + }, + ], + "name": "ItemEquipped", + "type": "event", +} + +ITEM_UNEQUIPPED_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "subjectTokenId", + "type": "uint256", + }, + {"indexed": True, "internalType": "uint256", "name": "slot", "type": "uint256"}, + { + "indexed": False, + "internalType": "uint256", + "name": "itemType", + "type": "uint256", + }, + { + "indexed": True, + "internalType": "address", + "name": "itemAddress", + "type": "address", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "itemTokenId", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "uint256", + "name": "amount", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "address", + "name": "unequippedBy", + "type": "address", + }, + ], + "name": "ItemUnequipped", + "type": "event", +} + +NEW_SLOT_URI_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "slotId", + "type": "uint256", + } + ], + "name": "NewSlotURI", + "type": "event", +} + +NEW_SLOT_PERSISTENCE_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "uint256", + "name": "slotId", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "bool", + "name": "persistent", + "type": "bool", + }, + ], + "name": "NewSlotPersistence", + "type": "event", +} diff --git a/cli/enginecli/setup_drop.py b/cli/web3cli/setup_drop.py similarity index 100% rename from cli/enginecli/setup_drop.py rename to cli/web3cli/setup_drop.py diff --git a/cli/enginecli/test_crafting.py b/cli/web3cli/test_crafting.py similarity index 100% rename from cli/enginecli/test_crafting.py rename to cli/web3cli/test_crafting.py diff --git a/cli/enginecli/test_dropper.py b/cli/web3cli/test_dropper.py similarity index 100% rename from cli/enginecli/test_dropper.py rename to cli/web3cli/test_dropper.py diff --git a/cli/enginecli/test_gofp.py b/cli/web3cli/test_gofp.py similarity index 100% rename from cli/enginecli/test_gofp.py rename to cli/web3cli/test_gofp.py diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py new file mode 100644 index 00000000..d47eb236 --- /dev/null +++ b/cli/web3cli/test_inventory.py @@ -0,0 +1,2165 @@ +import unittest + +from brownie import accounts, network, web3 as web3_client, ZERO_ADDRESS +from brownie.exceptions import VirtualMachineError +from brownie.network import chain +from moonworm.watch import _fetch_events_chunk + +from . import InventoryFacet, MockErc20, MockERC721, MockTerminus, inventory_events +from .core import inventory_gogogo + +MAX_UINT = 2**256 - 1 + + +class InventoryTestCase(unittest.TestCase): + @classmethod + def deploy_inventory(cls) -> str: + """ + Deploys an Inventory and returns the address of the deployed contract. + """ + deployed_contracts = inventory_gogogo( + cls.terminus.address, + cls.admin_terminus_pool_id, + cls.nft.address, + cls.owner_tx_config, + ) + return deployed_contracts["contracts"]["Diamond"] + + @classmethod + def setUpClass(cls) -> None: + try: + network.connect() + except: + pass + + cls.owner = accounts[0] + cls.owner_tx_config = {"from": cls.owner} + + cls.admin = accounts[1] + cls.player = accounts[2] + cls.random_person = accounts[3] + + cls.nft = MockERC721.MockERC721(None) + cls.nft.deploy(cls.owner_tx_config) + + cls.item_nft = MockERC721.MockERC721(None) + cls.item_nft.deploy(cls.owner_tx_config) + + cls.terminus = MockTerminus.MockTerminus(None) + cls.terminus.deploy(cls.owner_tx_config) + + cls.payment_token = MockErc20.MockErc20(None) + cls.payment_token.deploy("lol", "lol", cls.owner_tx_config) + + cls.terminus.set_payment_token(cls.payment_token.address, cls.owner_tx_config) + cls.terminus.set_pool_base_price(1, cls.owner_tx_config) + + cls.payment_token.mint(cls.owner.address, 999999, cls.owner_tx_config) + + cls.payment_token.approve(cls.terminus.address, MAX_UINT, cls.owner_tx_config) + + cls.terminus.create_pool_v1(1, False, True, cls.owner_tx_config) + cls.admin_terminus_pool_id = cls.terminus.total_pools() + + # Mint admin badge to administrator account + cls.terminus.mint( + cls.admin.address, cls.admin_terminus_pool_id, 1, "", cls.owner_tx_config + ) + + cls.predeployment_block = len(chain) + inventory_address = cls.deploy_inventory() + cls.inventory = InventoryFacet.InventoryFacet(inventory_address) + cls.postdeployment_block = len(chain) + + +class InventorySetupTests(InventoryTestCase): + def test_admin_terminus_info(self): + terminus_info = self.inventory.admin_terminus_info() + self.assertEqual(terminus_info[0], self.terminus.address) + self.assertEqual(terminus_info[1], self.admin_terminus_pool_id) + + def test_administrator_designated_event(self): + administrator_designated_events = _fetch_events_chunk( + web3_client, + inventory_events.ADMINISTRATOR_DESIGNATED_ABI, + self.predeployment_block, + self.postdeployment_block, + ) + self.assertEqual(len(administrator_designated_events), 1) + + self.assertEqual( + administrator_designated_events[0]["args"]["adminTerminusAddress"], + self.terminus.address, + ) + self.assertEqual( + administrator_designated_events[0]["args"]["adminTerminusPoolId"], + self.admin_terminus_pool_id, + ) + + def test_subject_erc721_address(self): + self.assertEqual(self.inventory.subject(), self.nft.address) + + def test_contract_address_designated_event(self): + contract_address_designated_events = _fetch_events_chunk( + web3_client, + inventory_events.NEW_SUBJECT_ADDRESS_ABI, + self.predeployment_block, + self.postdeployment_block, + ) + self.assertEqual(len(contract_address_designated_events), 1) + + self.assertEqual( + contract_address_designated_events[0]["args"]["contractAddress"], + self.nft.address, + ) + + +class TestAdminFlow(InventoryTestCase): + def test_admin_can_create_persistent_slot(self): + persistent = True + + num_slots_0 = self.inventory.num_slots() + tx_receipt = self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + num_slots_1 = self.inventory.num_slots() + + self.assertEqual(num_slots_1, num_slots_0 + 1) + self.assertEqual(self.inventory.slot_is_persistent(num_slots_1), persistent) + + inventory_slot_created_events = _fetch_events_chunk( + web3_client, + inventory_events.SLOT_CREATED_ABI, + tx_receipt.block_number, + tx_receipt.block_number, + ) + self.assertEqual(len(inventory_slot_created_events), 1) + self.assertEqual( + inventory_slot_created_events[0]["args"]["creator"], + self.admin.address, + ) + self.assertEqual( + inventory_slot_created_events[0]["args"]["slot"], + num_slots_1, + ) + + new_slot_uri_events = _fetch_events_chunk( + web3_client, + inventory_events.NEW_SLOT_URI_ABI, + tx_receipt.block_number, + tx_receipt.block_number, + ) + self.assertEqual(len(new_slot_uri_events), 1) + self.assertEqual(new_slot_uri_events[0]["args"]["slotId"], num_slots_1) + + new_slot_persistence_events = _fetch_events_chunk( + web3_client, + inventory_events.NEW_SLOT_PERSISTENCE_ABI, + tx_receipt.block_number, + tx_receipt.block_number, + ) + self.assertEqual(len(new_slot_persistence_events), 1) + self.assertEqual(new_slot_persistence_events[0]["args"]["slotId"], num_slots_1) + self.assertEqual( + new_slot_persistence_events[0]["args"]["persistent"], persistent + ) + + def test_admin_can_create_impersistent_slot(self): + persistent = False + + num_slots_0 = self.inventory.num_slots() + tx_receipt = self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + num_slots_1 = self.inventory.num_slots() + + self.assertEqual(num_slots_1, num_slots_0 + 1) + self.assertEqual(self.inventory.slot_is_persistent(num_slots_1), persistent) + + inventory_slot_created_events = _fetch_events_chunk( + web3_client, + inventory_events.SLOT_CREATED_ABI, + tx_receipt.block_number, + tx_receipt.block_number, + ) + self.assertEqual(len(inventory_slot_created_events), 1) + self.assertEqual( + inventory_slot_created_events[0]["args"]["creator"], + self.admin.address, + ) + self.assertEqual( + inventory_slot_created_events[0]["args"]["slot"], + num_slots_1, + ) + + new_slot_uri_events = _fetch_events_chunk( + web3_client, + inventory_events.NEW_SLOT_URI_ABI, + tx_receipt.block_number, + tx_receipt.block_number, + ) + self.assertEqual(len(new_slot_uri_events), 1) + self.assertEqual(new_slot_uri_events[0]["args"]["slotId"], num_slots_1) + + new_slot_persistence_events = _fetch_events_chunk( + web3_client, + inventory_events.NEW_SLOT_PERSISTENCE_ABI, + tx_receipt.block_number, + tx_receipt.block_number, + ) + self.assertEqual(len(new_slot_persistence_events), 1) + self.assertEqual(new_slot_persistence_events[0]["args"]["slotId"], num_slots_1) + self.assertEqual( + new_slot_persistence_events[0]["args"]["persistent"], persistent + ) + + def test_admin_can_set_slot_uri(self): + persistent = True + + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + num_slots_1 = self.inventory.num_slots() + + # set the slot uri + tx_receipt = self.inventory.set_slot_uri( + "some_fancy_slot_uri", + num_slots_1, + transaction_config={"from": self.admin}, + ) + + new_slot_uri = self.inventory.get_slot_uri(num_slots_1) + + # the slot uri is updated + self.assertEqual(new_slot_uri, "some_fancy_slot_uri") + + new_slot_uri_events = _fetch_events_chunk( + web3_client, + inventory_events.NEW_SLOT_URI_ABI, + tx_receipt.block_number, + tx_receipt.block_number, + ) + self.assertEqual(len(new_slot_uri_events), 1) + self.assertEqual(new_slot_uri_events[0]["args"]["slotId"], num_slots_1) + + def test_nonadmin_cannot_create_slot(self): + persistent = True + + num_slots_0 = self.inventory.num_slots() + with self.assertRaises(VirtualMachineError): + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.player}, + ) + num_slots_1 = self.inventory.num_slots() + + self.assertEqual(num_slots_1, num_slots_0) + + def test_nonadmin_cannot_set_slot_uri(self): + persistent = True + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + num_slots_0 = self.inventory.num_slots() + + # set the slot uri + with self.assertRaises(VirtualMachineError): + self.inventory.set_slot_uri( + "some_fancy_slot_uri", + 1, + transaction_config={"from": self.player}, + ) + + num_slots_1 = self.inventory.num_slots() + + self.assertEqual(num_slots_1, num_slots_0) + + def test_admin_cannot_mark_contracts_with_invalid_type_as_eligible_for_slots( + self, + ): + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + invalid_type = 0 + + with self.assertRaises(VirtualMachineError): + self.inventory.mark_item_as_equippable_in_slot( + slot, + invalid_type, + self.payment_token.address, + 0, + MAX_UINT, + {"from": self.admin}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, invalid_type, self.payment_token.address, 0 + ), + 0, + ) + + def test_admin_can_mark_erc20_tokens_as_eligible_for_slots(self): + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + erc20_type = 20 + + tx_receipt = self.inventory.mark_item_as_equippable_in_slot( + slot, + erc20_type, + self.payment_token.address, + 0, + MAX_UINT, + {"from": self.admin}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc20_type, self.payment_token.address, 0 + ), + MAX_UINT, + ) + + item_marked_as_equippable_in_slot_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_MARKED_AS_EQUIPPABLE_IN_SLOT_ABI, + tx_receipt.block_number, + tx_receipt.block_number, + ) + + self.assertEqual(len(item_marked_as_equippable_in_slot_events), 1) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["slot"], + slot, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["itemType"], + erc20_type, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["itemAddress"], + self.payment_token.address, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["itemPoolId"], + 0, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["maxAmount"], + MAX_UINT, + ) + + def test_nonadmin_cannot_mark_erc20_tokens_as_eligible_for_slots(self): + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + erc20_type = 20 + + with self.assertRaises(VirtualMachineError): + self.inventory.mark_item_as_equippable_in_slot( + slot, + erc20_type, + self.payment_token.address, + 0, + MAX_UINT, + {"from": self.player}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc20_type, self.payment_token.address, 0 + ), + 0, + ) + + def test_admin_cannot_mark_erc20_tokens_as_eligible_for_slots_if_pool_id_is_nonzero( + self, + ): + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + erc20_type = 20 + + with self.assertRaises(VirtualMachineError): + self.inventory.mark_item_as_equippable_in_slot( + slot, + erc20_type, + self.payment_token.address, + 1, + MAX_UINT, + {"from": self.admin}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc20_type, self.payment_token.address, 1 + ), + 0, + ) + + def test_admin_can_mark_erc721_tokens_as_eligible_for_slots(self): + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + erc721_type = 721 + + tx_receipt = self.inventory.mark_item_as_equippable_in_slot( + slot, + erc721_type, + self.nft.address, + 0, + 1, + {"from": self.admin}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc721_type, self.nft.address, 0 + ), + 1, + ) + + item_marked_as_equippable_in_slot_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_MARKED_AS_EQUIPPABLE_IN_SLOT_ABI, + tx_receipt.block_number, + tx_receipt.block_number, + ) + + self.assertEqual(len(item_marked_as_equippable_in_slot_events), 1) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["slot"], + slot, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["itemType"], + erc721_type, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["itemAddress"], + self.nft.address, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["itemPoolId"], + 0, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["maxAmount"], + 1, + ) + + def test_nonadmin_cannot_mark_erc721_tokens_as_eligible_for_slots(self): + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + erc721_type = 721 + + with self.assertRaises(VirtualMachineError): + self.inventory.mark_item_as_equippable_in_slot( + slot, + erc721_type, + self.nft.address, + 0, + 1, + {"from": self.player}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc721_type, self.nft.address, 0 + ), + 0, + ) + + def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_if_pool_id_is_nonzero( + self, + ): + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + erc721_type = 721 + + with self.assertRaises(VirtualMachineError): + self.inventory.mark_item_as_equippable_in_slot( + slot, + erc721_type, + self.item_nft.address, + 1, + 1, + {"from": self.admin}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc721_type, self.payment_token.address, 1 + ), + 0, + ) + + def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_with_max_amount_greater_than_1( + self, + ): + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + erc721_type = 721 + + with self.assertRaises(VirtualMachineError): + self.inventory.mark_item_as_equippable_in_slot( + slot, + erc721_type, + self.item_nft.address, + 0, + 2, + {"from": self.admin}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc721_type, self.payment_token.address, 0 + ), + 0, + ) + + def test_admin_can_mark_erc721_tokens_as_eligible_for_slots_with_max_amount_1_then_0( + self, + ): + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + erc721_type = 721 + + tx_receipt_0 = self.inventory.mark_item_as_equippable_in_slot( + slot, + erc721_type, + self.nft.address, + 0, + 1, + {"from": self.admin}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc721_type, self.nft.address, 0 + ), + 1, + ) + + tx_receipt_1 = self.inventory.mark_item_as_equippable_in_slot( + slot, + erc721_type, + self.nft.address, + 0, + 0, + {"from": self.admin}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc721_type, self.nft.address, 0 + ), + 0, + ) + + item_marked_as_equippable_in_slot_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_MARKED_AS_EQUIPPABLE_IN_SLOT_ABI, + tx_receipt_0.block_number, + tx_receipt_1.block_number, + ) + + self.assertEqual(len(item_marked_as_equippable_in_slot_events), 2) + for i, event in enumerate(item_marked_as_equippable_in_slot_events): + self.assertEqual( + event["args"]["slot"], + slot, + ) + self.assertEqual( + event["args"]["itemType"], + erc721_type, + ) + self.assertEqual( + event["args"]["itemAddress"], + self.nft.address, + ) + self.assertEqual( + event["args"]["itemPoolId"], + 0, + ) + self.assertEqual( + event["args"]["maxAmount"], + 1 - i, + ) + + def test_admin_can_mark_erc1155_tokens_as_eligible_for_slots(self): + # Testing with non-persistent slot. + persistent = True + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + erc1155_type = 1155 + item_pool_id = 42 + max_amount = 1337 + + tx_receipt = self.inventory.mark_item_as_equippable_in_slot( + slot, + erc1155_type, + self.terminus.address, + item_pool_id, + max_amount, + {"from": self.admin}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc1155_type, self.terminus.address, item_pool_id + ), + max_amount, + ) + + item_marked_as_equippable_in_slot_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_MARKED_AS_EQUIPPABLE_IN_SLOT_ABI, + tx_receipt.block_number, + tx_receipt.block_number, + ) + + self.assertEqual(len(item_marked_as_equippable_in_slot_events), 1) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["slot"], + slot, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["itemType"], + erc1155_type, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["itemAddress"], + self.terminus.address, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["itemPoolId"], + item_pool_id, + ) + self.assertEqual( + item_marked_as_equippable_in_slot_events[0]["args"]["maxAmount"], + max_amount, + ) + + def test_nonadmin_cannot_mark_erc1155_tokens_as_eligible_for_slots(self): + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + erc1155_type = 1155 + item_pool_id = 42 + max_amount = 1337 + + with self.assertRaises(VirtualMachineError): + self.inventory.mark_item_as_equippable_in_slot( + slot, + erc1155_type, + self.terminus.address, + item_pool_id, + max_amount, + {"from": self.player}, + ) + + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, erc1155_type, self.terminus.address, item_pool_id + ), + 0, + ) + + def test_admin_can_set_slot_persistent(self): + """ + Checks that an admin user can change the persistence of a slot after it has been created. + """ + # Create slot + persistent = True + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + self.assertTrue(self.inventory.slot_is_persistent(slot)) + + tx_receipt_0 = self.inventory.set_slot_persistent( + slot, + False, + transaction_config={"from": self.admin}, + ) + + self.assertFalse(self.inventory.slot_is_persistent(slot)) + + new_slot_persistence_events = _fetch_events_chunk( + web3_client, + inventory_events.NEW_SLOT_PERSISTENCE_ABI, + tx_receipt_0.block_number, + tx_receipt_0.block_number, + ) + self.assertEqual(len(new_slot_persistence_events), 1) + self.assertEqual(new_slot_persistence_events[0]["args"]["slotId"], slot) + self.assertEqual(new_slot_persistence_events[0]["args"]["persistent"], False) + + tx_receipt_1 = self.inventory.set_slot_persistent( + slot, + True, + transaction_config={"from": self.admin}, + ) + + self.assertTrue(self.inventory.slot_is_persistent(slot)) + + new_slot_persistence_events = _fetch_events_chunk( + web3_client, + inventory_events.NEW_SLOT_PERSISTENCE_ABI, + tx_receipt_1.block_number, + tx_receipt_1.block_number, + ) + self.assertEqual(len(new_slot_persistence_events), 1) + self.assertEqual(new_slot_persistence_events[0]["args"]["slotId"], slot) + self.assertEqual(new_slot_persistence_events[0]["args"]["persistent"], True) + + def test_nonadmin_cannot_make_persistent_slot_impersistent(self): + """ + Checks that a non-admin user cannot set the persistence of a slot from persistent to impersistent + (i.e. from True to False). + """ + persistent = True + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + self.assertTrue(self.inventory.slot_is_persistent(slot)) + + with self.assertRaises(VirtualMachineError): + self.inventory.set_slot_persistent( + slot, + False, + transaction_config={"from": self.player}, + ) + + self.assertTrue(self.inventory.slot_is_persistent(slot)) + + def test_nonadmin_cannot_make_impersistent_slot_persistent(self): + """ + Checks that a non-admin user cannot set the persistence of a slot from impersistent to persistent + (i.e. from False to True). + """ + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + self.assertFalse(self.inventory.slot_is_persistent(slot)) + + with self.assertRaises(VirtualMachineError): + self.inventory.set_slot_persistent( + slot, + True, + transaction_config={"from": self.player}, + ) + + self.assertFalse(self.inventory.slot_is_persistent(slot)) + + +class TestPlayerFlow(InventoryTestCase): + def test_player_can_equip_erc20_items_onto_their_subject_tokens(self): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + self.payment_token.mint(self.player.address, 1000, {"from": self.owner}) + self.payment_token.approve( + self.inventory.address, MAX_UINT, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + # Set ERC20 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 20, self.payment_token.address, 0, 10, {"from": self.admin} + ) + + player_balance_0 = self.payment_token.balance_of(self.player.address) + inventory_balance_0 = self.payment_token.balance_of(self.inventory.address) + + tx_receipt = self.inventory.equip( + subject_token_id, + slot, + 20, + self.payment_token.address, + 0, + 2, + {"from": self.player}, + ) + + player_balance_1 = self.payment_token.balance_of(self.player.address) + inventory_balance_1 = self.payment_token.balance_of(self.inventory.address) + + self.assertEqual(player_balance_1, player_balance_0 - 2) + self.assertEqual(inventory_balance_1, inventory_balance_0 + 2) + + equipped_item = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item, (20, self.payment_token.address, 0, 2)) + + item_equipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_EQUIPPED_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(item_equipped_events), 1) + + self.assertEqual( + item_equipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_equipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_equipped_events[0]["args"]["itemType"], + 20, + ) + self.assertEqual( + item_equipped_events[0]["args"]["itemAddress"], + self.payment_token.address, + ) + self.assertEqual( + item_equipped_events[0]["args"]["itemTokenId"], + 0, + ) + self.assertEqual( + item_equipped_events[0]["args"]["amount"], + 2, + ) + self.assertEqual( + item_equipped_events[0]["args"]["equippedBy"], + self.player.address, + ) + + def test_player_cannot_equip_too_many_erc20_items_onto_their_subject_tokens(self): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + self.payment_token.mint(self.player.address, 1000, {"from": self.owner}) + self.payment_token.approve( + self.inventory.address, MAX_UINT, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + # Set ERC20 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 20, self.payment_token.address, 0, 10, {"from": self.admin} + ) + + player_balance_0 = self.payment_token.balance_of(self.player.address) + inventory_balance_0 = self.payment_token.balance_of(self.inventory.address) + + with self.assertRaises(VirtualMachineError): + self.inventory.equip( + subject_token_id, + slot, + 20, + self.payment_token.address, + 0, + 20, + {"from": self.player}, + ) + + player_balance_1 = self.payment_token.balance_of(self.player.address) + inventory_balance_1 = self.payment_token.balance_of(self.inventory.address) + + self.assertEqual(player_balance_1, player_balance_0) + self.assertEqual(inventory_balance_1, inventory_balance_0) + + equipped_item = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item, (0, ZERO_ADDRESS, 0, 0)) + + def test_player_can_equip_erc721_items_onto_their_subject_tokens(self): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + item_token_id = self.item_nft.total_supply() + self.item_nft.mint(self.player.address, item_token_id, {"from": self.owner}) + self.item_nft.set_approval_for_all( + self.inventory.address, True, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + # Set ERC721 token as equippable in slot with max amount of 1 + self.inventory.mark_item_as_equippable_in_slot( + slot, 721, self.item_nft.address, 0, 1, {"from": self.admin} + ) + + self.assertEqual(self.item_nft.owner_of(item_token_id), self.player.address) + + tx_receipt = self.inventory.equip( + subject_token_id, + slot, + 721, + self.item_nft.address, + item_token_id, + 1, + {"from": self.player}, + ) + + self.assertEqual(self.item_nft.owner_of(item_token_id), self.inventory.address) + + equipped_item = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item, (721, self.item_nft.address, item_token_id, 1)) + + item_equipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_EQUIPPED_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(item_equipped_events), 1) + + self.assertEqual( + item_equipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_equipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_equipped_events[0]["args"]["itemType"], + 721, + ) + self.assertEqual( + item_equipped_events[0]["args"]["itemAddress"], + self.item_nft.address, + ) + self.assertEqual( + item_equipped_events[0]["args"]["itemTokenId"], + item_token_id, + ) + self.assertEqual( + item_equipped_events[0]["args"]["amount"], + 1, + ) + self.assertEqual( + item_equipped_events[0]["args"]["equippedBy"], + self.player.address, + ) + + def test_player_cannot_equip_erc721_items_they_own_onto_subject_tokens_they_do_not_own( + self, + ): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint( + self.random_person.address, subject_token_id, {"from": self.owner} + ) + + item_token_id = self.item_nft.total_supply() + self.item_nft.mint(self.player.address, item_token_id, {"from": self.owner}) + self.item_nft.set_approval_for_all( + self.inventory.address, True, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + # Set ERC721 token as equippable in slot with max amount of 1 + self.inventory.mark_item_as_equippable_in_slot( + slot, 721, self.item_nft.address, 0, 1, {"from": self.admin} + ) + + self.assertEqual(self.item_nft.owner_of(item_token_id), self.player.address) + + with self.assertRaises(VirtualMachineError): + self.inventory.equip( + subject_token_id, + slot, + 721, + self.item_nft.address, + item_token_id, + 1, + {"from": self.player}, + ) + + self.assertEqual(self.item_nft.owner_of(item_token_id), self.player.address) + + equipped_item = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item, (0, ZERO_ADDRESS, 0, 0)) + + def test_player_cannot_equip_erc721_items_which_they_do_not_own(self): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + item_token_id = self.item_nft.total_supply() + self.item_nft.mint( + self.random_person.address, item_token_id, {"from": self.owner} + ) + self.item_nft.set_approval_for_all( + self.inventory.address, True, {"from": self.random_person} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + # Set ERC721 token as equippable in slot with max amount of 1 + self.inventory.mark_item_as_equippable_in_slot( + slot, 721, self.item_nft.address, 0, 1, {"from": self.admin} + ) + + self.assertEqual( + self.item_nft.owner_of(item_token_id), self.random_person.address + ) + + with self.assertRaises(VirtualMachineError): + self.inventory.equip( + subject_token_id, + slot, + 721, + self.item_nft.address, + item_token_id, + 1, + {"from": self.player}, + ) + + self.assertEqual( + self.item_nft.owner_of(item_token_id), self.random_person.address + ) + + equipped_item = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item, (0, ZERO_ADDRESS, 0, 0)) + + def test_player_can_equip_erc1155_items_onto_their_subject_tokens(self): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + self.terminus.create_pool_v1(MAX_UINT, True, True, self.owner_tx_config) + item_pool_id = self.terminus.total_pools() + self.terminus.mint( + self.player.address, item_pool_id, 100, "", self.owner_tx_config + ) + self.terminus.set_approval_for_all( + self.inventory.address, True, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + # Set ERC1155 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 1155, self.terminus.address, item_pool_id, 10, {"from": self.admin} + ) + + player_balance_0 = self.terminus.balance_of(self.player.address, item_pool_id) + inventory_balance_0 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + tx_receipt = self.inventory.equip( + subject_token_id, + slot, + 1155, + self.terminus.address, + item_pool_id, + 10, + {"from": self.player}, + ) + + player_balance_1 = self.terminus.balance_of(self.player.address, item_pool_id) + inventory_balance_1 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + self.assertEqual(player_balance_1, player_balance_0 - 10) + self.assertEqual(inventory_balance_1, inventory_balance_0 + 10) + + equipped_item = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item, (1155, self.terminus.address, item_pool_id, 10)) + + item_equipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_EQUIPPED_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(item_equipped_events), 1) + + self.assertEqual( + item_equipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_equipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_equipped_events[0]["args"]["itemType"], + 1155, + ) + self.assertEqual( + item_equipped_events[0]["args"]["itemAddress"], + self.terminus.address, + ) + self.assertEqual( + item_equipped_events[0]["args"]["itemTokenId"], + item_pool_id, + ) + self.assertEqual( + item_equipped_events[0]["args"]["amount"], + 10, + ) + self.assertEqual( + item_equipped_events[0]["args"]["equippedBy"], + self.player.address, + ) + + def test_player_cannot_equip_too_many_erc1155_items_onto_their_subject_tokens(self): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + self.terminus.create_pool_v1(MAX_UINT, True, True, self.owner_tx_config) + item_pool_id = self.terminus.total_pools() + self.terminus.mint( + self.player.address, item_pool_id, 100, "", self.owner_tx_config + ) + self.terminus.set_approval_for_all( + self.inventory.address, True, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + # Set ERC1155 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 1155, self.terminus.address, item_pool_id, 10, {"from": self.admin} + ) + + player_balance_0 = self.terminus.balance_of(self.player.address, item_pool_id) + inventory_balance_0 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + with self.assertRaises(VirtualMachineError): + self.inventory.equip( + subject_token_id, + slot, + 1155, + self.terminus.address, + item_pool_id, + 11, + {"from": self.player}, + ) + + player_balance_1 = self.terminus.balance_of(self.player.address, item_pool_id) + inventory_balance_1 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + self.assertEqual(player_balance_1, player_balance_0) + self.assertEqual(inventory_balance_1, inventory_balance_0) + + equipped_item = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item, (0, ZERO_ADDRESS, 0, 0)) + + def test_player_can_unequip_all_erc20_items_in_slot_on_their_subject_tokens(self): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + self.payment_token.mint(self.player.address, 1000, {"from": self.owner}) + self.payment_token.approve( + self.inventory.address, MAX_UINT, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + self.assertFalse(self.inventory.slot_is_persistent(slot)) + + # Set ERC20 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 20, self.payment_token.address, 0, 10, {"from": self.admin} + ) + + player_balance_0 = self.payment_token.balance_of(self.player.address) + inventory_balance_0 = self.payment_token.balance_of(self.inventory.address) + + equipped_item_0 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_0, (0, ZERO_ADDRESS, 0, 0)) + + self.inventory.equip( + subject_token_id, + slot, + 20, + self.payment_token.address, + 0, + 2, + {"from": self.player}, + ) + + player_balance_1 = self.payment_token.balance_of(self.player.address) + inventory_balance_1 = self.payment_token.balance_of(self.inventory.address) + + self.assertEqual(player_balance_1, player_balance_0 - 2) + self.assertEqual(inventory_balance_1, inventory_balance_0 + 2) + + equipped_item_1 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_1, (20, self.payment_token.address, 0, 2)) + + tx_receipt = self.inventory.unequip( + subject_token_id, slot, True, 0, {"from": self.player} + ) + + player_balance_2 = self.payment_token.balance_of(self.player.address) + inventory_balance_2 = self.payment_token.balance_of(self.inventory.address) + + self.assertEqual(player_balance_2, player_balance_0) + self.assertEqual(inventory_balance_2, inventory_balance_0) + + equipped_item_2 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_2, (0, ZERO_ADDRESS, 0, 0)) + + item_unequipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_UNEQUIPPED_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(item_unequipped_events), 1) + + self.assertEqual( + item_unequipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_unequipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_unequipped_events[0]["args"]["itemType"], + 20, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemAddress"], + self.payment_token.address, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemTokenId"], + 0, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["amount"], + 2, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["unequippedBy"], + self.player.address, + ) + + def test_player_can_unequip_some_but_not_all_erc20_items_in_slot_on_their_subject_tokens( + self, + ): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + self.payment_token.mint(self.player.address, 1000, {"from": self.owner}) + self.payment_token.approve( + self.inventory.address, MAX_UINT, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + self.assertFalse(self.inventory.slot_is_persistent(slot)) + + # Set ERC20 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 20, self.payment_token.address, 0, 10, {"from": self.admin} + ) + + player_balance_0 = self.payment_token.balance_of(self.player.address) + inventory_balance_0 = self.payment_token.balance_of(self.inventory.address) + + equipped_item_0 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_0, (0, ZERO_ADDRESS, 0, 0)) + + self.inventory.equip( + subject_token_id, + slot, + 20, + self.payment_token.address, + 0, + 2, + {"from": self.player}, + ) + + player_balance_1 = self.payment_token.balance_of(self.player.address) + inventory_balance_1 = self.payment_token.balance_of(self.inventory.address) + + self.assertEqual(player_balance_1, player_balance_0 - 2) + self.assertEqual(inventory_balance_1, inventory_balance_0 + 2) + + equipped_item_1 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_1, (20, self.payment_token.address, 0, 2)) + + tx_receipt = self.inventory.unequip( + subject_token_id, slot, False, 1, {"from": self.player} + ) + + player_balance_2 = self.payment_token.balance_of(self.player.address) + inventory_balance_2 = self.payment_token.balance_of(self.inventory.address) + + self.assertEqual(player_balance_2, player_balance_1 + 1) + self.assertEqual(inventory_balance_2, inventory_balance_1 - 1) + + equipped_item_2 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_2, (20, self.payment_token.address, 0, 1)) + + item_unequipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_UNEQUIPPED_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(item_unequipped_events), 1) + + self.assertEqual( + item_unequipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_unequipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_unequipped_events[0]["args"]["itemType"], + 20, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemAddress"], + self.payment_token.address, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemTokenId"], + 0, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["amount"], + 1, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["unequippedBy"], + self.player.address, + ) + + def test_player_can_unequip_all_erc721_items_in_slot_on_their_subject_tokens(self): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + item_token_id = self.item_nft.total_supply() + self.item_nft.mint(self.player.address, item_token_id, {"from": self.owner}) + self.item_nft.set_approval_for_all( + self.inventory.address, True, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + self.assertFalse(self.inventory.slot_is_persistent(slot)) + + # Set ERC721 token as equippable in slot + self.inventory.mark_item_as_equippable_in_slot( + slot, 721, self.item_nft.address, 0, 1, {"from": self.admin} + ) + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, 721, self.item_nft.address, 0 + ), + 1, + ) + + self.assertEqual(self.item_nft.owner_of(item_token_id), self.player.address) + + equipped_item_0 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_0, (0, ZERO_ADDRESS, 0, 0)) + + self.inventory.equip( + subject_token_id, + slot, + 721, + self.item_nft.address, + item_token_id, + 1, + {"from": self.player}, + ) + + self.assertEqual(self.item_nft.owner_of(item_token_id), self.inventory.address) + + equipped_item_1 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual( + equipped_item_1, (721, self.item_nft.address, item_token_id, 1) + ) + + tx_receipt = self.inventory.unequip( + subject_token_id, slot, True, 0, {"from": self.player} + ) + + self.assertEqual(self.item_nft.owner_of(item_token_id), self.player.address) + + equipped_item_2 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_2, (0, ZERO_ADDRESS, 0, 0)) + + item_unequipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_UNEQUIPPED_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(item_unequipped_events), 1) + + self.assertEqual( + item_unequipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_unequipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_unequipped_events[0]["args"]["itemType"], + 721, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemAddress"], + self.item_nft.address, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemTokenId"], + item_token_id, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["amount"], + 1, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["unequippedBy"], + self.player.address, + ) + + def test_player_can_unequip_single_erc721_item_in_slot_on_their_subject_tokens_by_specifying_amount( + self, + ): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + item_token_id = self.item_nft.total_supply() + self.item_nft.mint(self.player.address, item_token_id, {"from": self.owner}) + self.item_nft.set_approval_for_all( + self.inventory.address, True, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + self.assertFalse(self.inventory.slot_is_persistent(slot)) + + # Set ERC721 token as equippable in slot + self.inventory.mark_item_as_equippable_in_slot( + slot, 721, self.item_nft.address, 0, 1, {"from": self.admin} + ) + self.assertEqual( + self.inventory.max_amount_of_item_in_slot( + slot, 721, self.item_nft.address, 0 + ), + 1, + ) + + self.assertEqual(self.item_nft.owner_of(item_token_id), self.player.address) + + equipped_item_0 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_0, (0, ZERO_ADDRESS, 0, 0)) + + self.inventory.equip( + subject_token_id, + slot, + 721, + self.item_nft.address, + item_token_id, + 1, + {"from": self.player}, + ) + + self.assertEqual(self.item_nft.owner_of(item_token_id), self.inventory.address) + + equipped_item_1 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual( + equipped_item_1, (721, self.item_nft.address, item_token_id, 1) + ) + + tx_receipt = self.inventory.unequip( + subject_token_id, slot, False, 1, {"from": self.player} + ) + + self.assertEqual(self.item_nft.owner_of(item_token_id), self.player.address) + + equipped_item_2 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_2, (0, ZERO_ADDRESS, 0, 0)) + + item_unequipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_UNEQUIPPED_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(item_unequipped_events), 1) + + self.assertEqual( + item_unequipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_unequipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_unequipped_events[0]["args"]["itemType"], + 721, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemAddress"], + self.item_nft.address, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemTokenId"], + item_token_id, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["amount"], + 1, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["unequippedBy"], + self.player.address, + ) + + def test_player_can_unequip_all_erc1155_items_in_slot_on_their_subject_tokens(self): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + self.terminus.create_pool_v1(MAX_UINT, True, True, self.owner_tx_config) + item_pool_id = self.terminus.total_pools() + self.terminus.mint( + self.player.address, item_pool_id, 100, "", self.owner_tx_config + ) + self.terminus.set_approval_for_all( + self.inventory.address, True, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + # Set ERC1155 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 1155, self.terminus.address, item_pool_id, 10, {"from": self.admin} + ) + + player_balance_0 = self.terminus.balance_of(self.player.address, item_pool_id) + inventory_balance_0 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + equipped_item_0 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_0, (0, ZERO_ADDRESS, 0, 0)) + + self.inventory.equip( + subject_token_id, + slot, + 1155, + self.terminus.address, + item_pool_id, + 9, + {"from": self.player}, + ) + + player_balance_1 = self.terminus.balance_of(self.player.address, item_pool_id) + inventory_balance_1 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + self.assertEqual(player_balance_1, player_balance_0 - 9) + self.assertEqual(inventory_balance_1, inventory_balance_0 + 9) + + equipped_item_1 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual( + equipped_item_1, (1155, self.terminus.address, item_pool_id, 9) + ) + + tx_receipt = self.inventory.unequip( + subject_token_id, slot, True, 0, {"from": self.player} + ) + + player_balance_2 = self.terminus.balance_of(self.player.address, item_pool_id) + inventory_balance_2 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + self.assertEqual(player_balance_2, player_balance_0) + self.assertEqual(inventory_balance_2, inventory_balance_0) + + equipped_item_2 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_2, (0, ZERO_ADDRESS, 0, 0)) + + item_unequipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_UNEQUIPPED_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(item_unequipped_events), 1) + + self.assertEqual( + item_unequipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_unequipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_unequipped_events[0]["args"]["itemType"], + 1155, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemAddress"], + self.terminus.address, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemTokenId"], + item_pool_id, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["amount"], + 9, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["unequippedBy"], + self.player.address, + ) + + def test_player_can_unequip_some_but_not_all_erc1155_items_in_slot_on_their_subject_tokens( + self, + ): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + self.terminus.create_pool_v1(MAX_UINT, True, True, self.owner_tx_config) + item_pool_id = self.terminus.total_pools() + self.terminus.mint( + self.player.address, item_pool_id, 100, "", self.owner_tx_config + ) + self.terminus.set_approval_for_all( + self.inventory.address, True, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + # Set ERC1155 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 1155, self.terminus.address, item_pool_id, 10, {"from": self.admin} + ) + + player_balance_0 = self.terminus.balance_of(self.player.address, item_pool_id) + inventory_balance_0 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + equipped_item_0 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_0, (0, ZERO_ADDRESS, 0, 0)) + + self.inventory.equip( + subject_token_id, + slot, + 1155, + self.terminus.address, + item_pool_id, + 9, + {"from": self.player}, + ) + + player_balance_1 = self.terminus.balance_of(self.player.address, item_pool_id) + inventory_balance_1 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + self.assertEqual(player_balance_1, player_balance_0 - 9) + self.assertEqual(inventory_balance_1, inventory_balance_0 + 9) + + equipped_item_1 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual( + equipped_item_1, (1155, self.terminus.address, item_pool_id, 9) + ) + + tx_receipt = self.inventory.unequip( + subject_token_id, slot, False, 5, {"from": self.player} + ) + + player_balance_2 = self.terminus.balance_of(self.player.address, item_pool_id) + inventory_balance_2 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + self.assertEqual(player_balance_2, player_balance_1 + 5) + self.assertEqual(inventory_balance_2, inventory_balance_1 - 5) + + equipped_item_2 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual( + equipped_item_2, (1155, self.terminus.address, item_pool_id, 4) + ) + + item_unequipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_UNEQUIPPED_ABI, + from_block=tx_receipt.block_number, + to_block=tx_receipt.block_number, + ) + self.assertEqual(len(item_unequipped_events), 1) + + self.assertEqual( + item_unequipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_unequipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_unequipped_events[0]["args"]["itemType"], + 1155, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemAddress"], + self.terminus.address, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemTokenId"], + item_pool_id, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["amount"], + 5, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["unequippedBy"], + self.player.address, + ) + + def test_player_can_equip_an_item_and_then_replace_it_onto_their_subject_tokens_20_then_1155( + self, + ): + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + self.payment_token.mint(self.player.address, 1000, {"from": self.owner}) + self.payment_token.approve( + self.inventory.address, MAX_UINT, {"from": self.player} + ) + + self.terminus.create_pool_v1(MAX_UINT, True, True, self.owner_tx_config) + item_pool_id = self.terminus.total_pools() + self.terminus.mint( + self.player.address, item_pool_id, 100, "", self.owner_tx_config + ) + self.terminus.set_approval_for_all( + self.inventory.address, True, {"from": self.player} + ) + + # Create inventory slot + persistent = False + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + + # Set ERC20 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 20, self.payment_token.address, 0, 10, {"from": self.admin} + ) + + # Set ERC1155 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 1155, self.terminus.address, item_pool_id, 10, {"from": self.admin} + ) + + player_erc20_balance_0 = self.payment_token.balance_of(self.player.address) + inventory_erc20_balance_0 = self.payment_token.balance_of( + self.inventory.address + ) + + tx_receipt_0 = self.inventory.equip( + subject_token_id, + slot, + 20, + self.payment_token.address, + 0, + 2, + {"from": self.player}, + ) + + player_erc20_balance_1 = self.payment_token.balance_of(self.player.address) + inventory_erc20_balance_1 = self.payment_token.balance_of( + self.inventory.address + ) + + self.assertEqual(player_erc20_balance_1, player_erc20_balance_0 - 2) + self.assertEqual(inventory_erc20_balance_1, inventory_erc20_balance_0 + 2) + + equipped_item = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item, (20, self.payment_token.address, 0, 2)) + + player_erc1155_balance_1 = self.terminus.balance_of( + self.player.address, item_pool_id + ) + inventory_erc1155_balance_1 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + tx_receipt_1 = self.inventory.equip( + subject_token_id, + slot, + 1155, + self.terminus.address, + item_pool_id, + 9, + {"from": self.player}, + ) + + player_erc20_balance_2 = self.payment_token.balance_of(self.player.address) + inventory_erc20_balance_2 = self.payment_token.balance_of( + self.inventory.address + ) + + self.assertEqual(player_erc20_balance_2, player_erc20_balance_0) + self.assertEqual(inventory_erc20_balance_2, inventory_erc20_balance_0) + + equipped_item = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item, (1155, self.terminus.address, item_pool_id, 9)) + + player_erc1155_balance_2 = self.terminus.balance_of( + self.player.address, item_pool_id + ) + inventory_erc1155_balance_2 = self.terminus.balance_of( + self.inventory.address, item_pool_id + ) + + self.assertEqual(player_erc1155_balance_2, player_erc1155_balance_1 - 9) + self.assertEqual(inventory_erc1155_balance_2, inventory_erc1155_balance_1 + 9) + + item_equipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_EQUIPPED_ABI, + from_block=tx_receipt_0.block_number, + to_block=tx_receipt_1.block_number, + ) + self.assertEqual(len(item_equipped_events), 2) + + self.assertEqual( + item_equipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_equipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_equipped_events[0]["args"]["itemType"], + 20, + ) + self.assertEqual( + item_equipped_events[0]["args"]["itemAddress"], + self.payment_token.address, + ) + self.assertEqual( + item_equipped_events[0]["args"]["itemTokenId"], + 0, + ) + self.assertEqual( + item_equipped_events[0]["args"]["amount"], + 2, + ) + self.assertEqual( + item_equipped_events[0]["args"]["equippedBy"], + self.player.address, + ) + + self.assertEqual( + item_equipped_events[1]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_equipped_events[1]["args"]["slot"], slot) + self.assertEqual( + item_equipped_events[1]["args"]["itemType"], + 1155, + ) + self.assertEqual( + item_equipped_events[1]["args"]["itemAddress"], + self.terminus.address, + ) + self.assertEqual( + item_equipped_events[1]["args"]["itemTokenId"], + item_pool_id, + ) + self.assertEqual( + item_equipped_events[1]["args"]["amount"], + 9, + ) + self.assertEqual( + item_equipped_events[1]["args"]["equippedBy"], + self.player.address, + ) + + item_unequipped_events = _fetch_events_chunk( + web3_client, + inventory_events.ITEM_UNEQUIPPED_ABI, + from_block=tx_receipt_1.block_number, + to_block=tx_receipt_1.block_number, + ) + self.assertEqual(len(item_unequipped_events), 1) + self.assertEqual( + item_unequipped_events[0]["args"]["subjectTokenId"], subject_token_id + ) + self.assertEqual(item_unequipped_events[0]["args"]["slot"], slot) + self.assertEqual( + item_unequipped_events[0]["args"]["itemType"], + 20, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemAddress"], + self.payment_token.address, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["itemTokenId"], + 0, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["amount"], + 2, + ) + self.assertEqual( + item_unequipped_events[0]["args"]["unequippedBy"], + self.player.address, + ) + + def test_player_cannot_unequip_erc20_tokens_from_persistent_slot(self): + """ + Checks that, once a player has equipped eligible ERC20 tokens into a persistent slot, they + cannot be unequipped without a change in persistence. + """ + # Mint tokens to player and set approvals + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + self.payment_token.mint(self.player.address, 1000, {"from": self.owner}) + self.payment_token.approve( + self.inventory.address, MAX_UINT, {"from": self.player} + ) + + # Create inventory slot + persistent = True + self.inventory.create_slot( + persistent, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + self.assertTrue(self.inventory.slot_is_persistent(slot)) + + # Set ERC20 token as equippable in slot with max amount of 10 + self.inventory.mark_item_as_equippable_in_slot( + slot, 20, self.payment_token.address, 0, 10, {"from": self.admin} + ) + + player_balance_0 = self.payment_token.balance_of(self.player.address) + inventory_balance_0 = self.payment_token.balance_of(self.inventory.address) + + equipped_item_0 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_0, (0, ZERO_ADDRESS, 0, 0)) + + self.inventory.equip( + subject_token_id, + slot, + 20, + self.payment_token.address, + 0, + 2, + {"from": self.player}, + ) + + player_balance_1 = self.payment_token.balance_of(self.player.address) + inventory_balance_1 = self.payment_token.balance_of(self.inventory.address) + + self.assertEqual(player_balance_1, player_balance_0 - 2) + self.assertEqual(inventory_balance_1, inventory_balance_0 + 2) + + equipped_item_1 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_1, (20, self.payment_token.address, 0, 2)) + + with self.assertRaises(VirtualMachineError): + self.inventory.unequip( + subject_token_id, slot, False, 1, {"from": self.player} + ) + + player_balance_2 = self.payment_token.balance_of(self.player.address) + inventory_balance_2 = self.payment_token.balance_of(self.inventory.address) + + self.assertEqual(player_balance_2, player_balance_1) + self.assertEqual(inventory_balance_2, inventory_balance_1) + + equipped_item_2 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_2, (20, self.payment_token.address, 0, 2)) + + """ + TODO(zomglings): There is currently a bug in the contract which prevents players from increasing + the amount of ERC20 tokens in a persistent slot (even if the total amount would be below the + maximum allowable amount for that token in that slot). Once this bug has been fixed, this test + should be extended with the following code (and should be renamed): + + self.inventory.equip( + subject_token_id, + slot, + 20, + self.payment_token.address, + 0, + 3, + {"from": self.player}, + ) + + player_balance_3 = self.payment_token.balance_of(self.player.address) + inventory_balance_3 = self.payment_token.balance_of(self.inventory.address) + + self.assertEqual(player_balance_3, player_balance_2 - 3) + self.assertEqual(inventory_balance_3, inventory_balance_2 + 3) + + equipped_item_3 = self.inventory.get_equipped_item(subject_token_id, slot) + self.assertEqual(equipped_item_3, (20, self.payment_token.address, 0, 5)) + """ diff --git a/cli/enginecli/test_lootbox.py b/cli/web3cli/test_lootbox.py similarity index 100% rename from cli/enginecli/test_lootbox.py rename to cli/web3cli/test_lootbox.py diff --git a/cli/enginecli/test_random_lootbox.py b/cli/web3cli/test_random_lootbox.py similarity index 100% rename from cli/enginecli/test_random_lootbox.py rename to cli/web3cli/test_random_lootbox.py diff --git a/cli/enginecli/test_reentrancy_guard.py b/cli/web3cli/test_reentrancy_guard.py similarity index 100% rename from cli/enginecli/test_reentrancy_guard.py rename to cli/web3cli/test_reentrancy_guard.py diff --git a/cli/enginecli/version.py b/cli/web3cli/version.py similarity index 100% rename from cli/enginecli/version.py rename to cli/web3cli/version.py diff --git a/cli/enginecli/version.txt b/cli/web3cli/version.txt similarity index 100% rename from cli/enginecli/version.txt rename to cli/web3cli/version.txt diff --git a/contracts/diamond/interfaces/IInventory.sol b/contracts/diamond/interfaces/IInventory.sol deleted file mode 100644 index f58fd534..00000000 --- a/contracts/diamond/interfaces/IInventory.sol +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "../libraries/LibInventory.sol"; - - -interface IInventory { - - event AdministratorDesignated( - address indexed adminTerminusAddress, - uint256 indexed adminTerminusPoolId - ); - - event ContractAddressDesignated(address indexed contractAddress); - - event SlotCreated(address indexed creator, uint256 indexed slot, bool unequippable, uint256 indexed slotType); - - event NewSlotTypeAdded(address indexed creator, uint256 indexed slotType, string slotTypeName); - - event ItemMarkedAsEquippableInSlot( - uint256 indexed slot, - uint256 indexed itemType, - address indexed itemAddress, - uint256 itemPoolId, - uint256 maxAmount - ); - - event BackpackAdded(address indexed creator, uint256 indexed toSubjectTokenId, uint256 indexed slotQuantity); - - event NewSlotURI(uint256 indexed slotId); - - event SlotTypeAdded(address indexed creator, uint256 indexed slotId, uint256 indexed slotType); - - event ItemEquipped( - uint256 indexed subjectTokenId, - uint256 indexed slot, - uint256 itemType, - address indexed itemAddress, - uint256 itemTokenId, - uint256 amount, - address equippedBy - ); - - event ItemUnequipped( - uint256 indexed subjectTokenId, - uint256 indexed slot, - uint256 itemType, - address indexed itemAddress, - uint256 itemTokenId, - uint256 amount, - address unequippedBy - ); - - function init( - address adminTerminusAddress, - uint256 adminTerminusPoolId, - address subjectAddress - ) external; - - function adminTerminusInfo() external view returns (address, uint256); - - function subject() external view returns (address); - - function createSlot(bool unequippable, uint256 slotType, string memory slotURI) - external returns (uint256); - - function numSlots() external view returns (uint256); - - function slotIsUnequippable(uint256 slotId) external view returns (bool); - - function markItemAsEquippableInSlot( - uint256 slot, - uint256 itemType, - address itemAddress, - uint256 itemPoolId, - uint256 maxAmount - ) external; - - function maxAmountOfItemInSlot( - uint256 slot, - uint256 itemType, - address itemAddress, - uint256 itemPoolId - ) external view returns (uint256); - - function equip( - uint256 subjectTokenId, - uint256 slot, - uint256 itemType, - address itemAddress, - uint256 itemTokenId, - uint256 amount - ) external; - - function unequip( - uint256 subjectTokenId, - uint256 slot, - bool unequipAll, - uint256 amount - ) external; - - function getEquippedItem(uint256 subjectTokenId, uint256 slot) - external - view - returns (LibInventory.EquippedItem memory item); - - function getSlotById(uint256 slotId) - external - view - returns (LibInventory.Slot memory slots); - - function getSubjectTokenSlots(uint256 subjectTokenId) - external - view - returns(LibInventory.Slot[] memory slot); - - function addBackpackToSubject( - uint256 slotQty, - uint256 toSubjectTokenId, - uint256 slotType, - string memory slotURI - ) external; - - function getSlotURI(uint256 slotId) external view returns (string memory); - - function createSlotType(uint256 slotType, string memory slotTypeName) external; - - function assignSlotType(uint256 slot, uint256 slotType) external; - - function getSlotType(uint256 slotType) external view returns(string memory slotTypeName); - - function setSlotUnequippable(bool unquippable, uint256 slotId) external; - - function getAllEquippedItems(uint256 subjectTokenId, uint256[] memory slots) - external - view - returns (LibInventory.EquippedItem[] memory equippedItems); - - function equipBatch( - uint256 subjectTokenId, - uint256[] memory slots, - LibInventory.EquippedItem[] memory items - ) external; -} \ No newline at end of file diff --git a/contracts/diamond/libraries/LibInventory.sol b/contracts/diamond/libraries/LibInventory.sol deleted file mode 100644 index 0ec6fc42..00000000 --- a/contracts/diamond/libraries/LibInventory.sol +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -/** -LibInventory defines the storage structure used by the Inventory contract as a facet for an EIP-2535 Diamond -proxy. - */ -library LibInventory { - bytes32 constant STORAGE_POSITION = - keccak256("g7dao.eth.storage.Inventory"); - - uint256 constant ERC20_ITEM_TYPE = 20; - uint256 constant ERC721_ITEM_TYPE = 721; - uint256 constant ERC1155_ITEM_TYPE = 1155; - - struct Slot { - string SlotURI; - uint256 SlotType; - bool SlotIsUnequippable; - uint256 SlotId; - } - - // EquippedItem represents an item equipped in a specific inventory slot for a specific ERC721 token. - struct EquippedItem { - uint256 ItemType; - address ItemAddress; - uint256 ItemTokenId; - uint256 Amount; - } - - struct InventoryStorage { - address AdminTerminusAddress; - uint256 AdminTerminusPoolId; - address ContractERC721Address; - uint256 NumSlots; - - // SlotId => slot, useful to get the rest of the slot data. - mapping(uint256 => Slot) SlotData; - - - // SlotType => "slot type name" - mapping(uint256 => string) SlotTypes; - - - // Slot => item type => item address => item pool ID => maximum equippable - // For ERC20 and ERC721 tokens, item pool ID is assumed to be 0. No data will be stored under positive - // item pool IDs. - // - // NOTE: It is possible for the same contract to implement multiple of these ERCs (e.g. ERC20 and ERC721), - // so this data structure actually makes sense. - mapping(uint256 => mapping(uint256 => mapping(address => mapping(uint256 => uint256)))) SlotEligibleItems; - - // Subject contract address => subject token ID => slot => EquippedItem - // Item type and Pool ID on EquippedItem have the same constraints as they do elsewhere (e.g. in SlotEligibleItems). - // - // NOTE: We have added the subject contract address as the first mapping key as a defense against - // future modifications which may allow administrators to modify the subject contract address. - // If such a modification were made, it could make it possible for a bad actor administrator - // to change the address of the subject token to the address to an ERC721 contract they control - // and drain all items from every subject token's inventory. - // If this contract is deployed as a Diamond proxy, the owner of the Diamond can pretty much - // do whatever they want in any case, but adding the subject contract address as a key protects - // users of non-Diamond deployments even under small variants of the current implementation. - // It also offers *some* protection to users of Diamond deployments of the Inventory. - // ERC721 Contract Address => - // subjectTokenId => - // slotId => - // EquippedItem struct - mapping(address => mapping(uint256 => mapping(uint256 => EquippedItem))) EquippedItems; - - // Subject contract address => subject token ID => Slot[] - mapping(address => mapping(uint256 => Slot[])) SubjectSlots; - - // Subject contract address => subject token ID => slotNum - mapping(address => mapping(uint256 => uint256)) SubjectNumSlots; - - // Subject contract address => subject token ID => slotId => bool - mapping(address => mapping(uint256 => mapping(uint256 => bool))) IsSubjectTokenBlackListedForSlot; - - - } - - function inventoryStorage() - internal - pure - returns (InventoryStorage storage istore) - { - bytes32 position = STORAGE_POSITION; - assembly { - istore.slot := position - } - } -} \ No newline at end of file diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol new file mode 100644 index 00000000..e8e6a9fc --- /dev/null +++ b/contracts/inventory/IInventory.sol @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +struct Slot { + string SlotURI; + bool SlotIsPersistent; +} + +// EquippedItem represents an item equipped in a specific inventory slot for a specific ERC721 token. +struct EquippedItem { + uint256 ItemType; + address ItemAddress; + uint256 ItemTokenId; + uint256 Amount; +} + +// Interface ID: 6e34096c +// +// Calculated by solface: https://github.com/moonstream-to/solface +// +// To recalculate from root directory of this repo: +// $ jq .abi build/contracts/IInventory.json | solface -name IInventory -annotations | grep "Interface ID:" +// +// Note: Change path to build/contracts/IInventory.json depending on where you are relative to the repo root. +interface IInventory { + // This event should be emitted when the subject ERC721 contract address is set (or changes) on the + // Inventory contract. + event NewSubjectAddress(address indexed contractAddress); + + event SlotCreated(address indexed creator, uint256 slot); + + event ItemMarkedAsEquippableInSlot( + uint256 indexed slot, + uint256 indexed itemType, + address indexed itemAddress, + uint256 itemPoolId, + uint256 maxAmount + ); + + event NewSlotURI(uint256 indexed slotId); + + event NewSlotPersistence(uint256 indexed slotId, bool persistent); + + event ItemEquipped( + uint256 indexed subjectTokenId, + uint256 indexed slot, + uint256 itemType, + address indexed itemAddress, + uint256 itemTokenId, + uint256 amount, + address equippedBy + ); + + event ItemUnequipped( + uint256 indexed subjectTokenId, + uint256 indexed slot, + uint256 itemType, + address indexed itemAddress, + uint256 itemTokenId, + uint256 amount, + address unequippedBy + ); + + function adminTerminusInfo() external view returns (address, uint256); + + function subject() external view returns (address); + + // Constraint: Admin + // Emits: SlotCreated, NewSlotURI, NewSlotPersistence + function createSlot( + bool persistent, + string memory slotURI + ) external returns (uint256); + + function numSlots() external view returns (uint256); + + function getSlotById( + uint256 slotId + ) external view returns (Slot memory slots); + + function getSlotURI(uint256 slotId) external view returns (string memory); + + function slotIsPersistent(uint256 slotId) external view returns (bool); + + // Constraint: Admin + // Emits: NewSlotURI + function setSlotURI(string memory newSlotURI, uint slotId) external; + + // Constraint: Admin + // Emits: NewSlotPersistence + function setSlotPersistent(uint256 slotId, bool persistent) external; + + // Constraint: Admin + // Emits: ItemMarkedAsEquippableInSlot + function markItemAsEquippableInSlot( + uint256 slot, + uint256 itemType, + address itemAddress, + uint256 itemPoolId, + uint256 maxAmount + ) external; + + function maxAmountOfItemInSlot( + uint256 slot, + uint256 itemType, + address itemAddress, + uint256 itemPoolId + ) external view returns (uint256); + + // Constraint: Non-reentrant. + // Emits: ItemEquipped + // Optionally emits: ItemUnequipped (if the current item in that slot is being replaced) + function equip( + uint256 subjectTokenId, + uint256 slot, + uint256 itemType, + address itemAddress, + uint256 itemTokenId, + uint256 amount + ) external; + + // Constraint: Non-reentrant. + // Emits: ItemUnequipped + function unequip( + uint256 subjectTokenId, + uint256 slot, + bool unequipAll, + uint256 amount + ) external; + + function getEquippedItem( + uint256 subjectTokenId, + uint256 slot + ) external view returns (EquippedItem memory item); +} diff --git a/contracts/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol similarity index 66% rename from contracts/InventoryFacet.sol rename to contracts/inventory/InventoryFacet.sol index a6f6b276..39c1ae15 100644 --- a/contracts/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -1,22 +1,78 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 /** - * Authors: Omar Garcia, Moonstream DAO (engineering@moonstream.to) - * GitHub: https://github.com/G7DAO/contracts + * Authors: Moonstream Engineering (engineering@moonstream.to) + * GitHub: https://github.com/moonstream-to/web3 */ -pragma solidity ^0.8.17; +pragma solidity ^0.8.0; import {TerminusPermissions} from "@moonstream/contracts/terminus/TerminusPermissions.sol"; -import {DiamondReentrancyGuard} from "./diamond/security/DiamondReentrancyGuard.sol"; import "@openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol"; import "@openzeppelin-contracts/contracts/token/ERC721/utils/ERC721Holder.sol"; import "@openzeppelin-contracts/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol"; -import "./diamond/libraries/LibDiamond.sol"; -import "./diamond/libraries/LibInventory.sol"; -import "./diamond/interfaces/IInventory.sol"; +import "../diamond/libraries/LibDiamond.sol"; +import {DiamondReentrancyGuard} from "../diamond/security/DiamondReentrancyGuard.sol"; +import {Slot, EquippedItem, IInventory} from "./IInventory.sol"; + +/** +LibInventory defines the storage structure used by the Inventory contract as a facet for an EIP-2535 Diamond +proxy. + */ +library LibInventory { + bytes32 constant STORAGE_POSITION = + keccak256("moonstreamdao.eth.storage.Inventory"); + + uint256 constant ERC20_ITEM_TYPE = 20; + uint256 constant ERC721_ITEM_TYPE = 721; + uint256 constant ERC1155_ITEM_TYPE = 1155; + + struct InventoryStorage { + address AdminTerminusAddress; + uint256 AdminTerminusPoolId; + address ContractERC721Address; + uint256 NumSlots; + // SlotId => slot data (URI, persistence) + mapping(uint256 => Slot) SlotData; + // Slot => item type => item address => item pool ID => maximum equippable + // For ERC20 and ERC721 tokens, item pool ID is assumed to be 0. No data will be stored under positive + // item pool IDs. + // + // NOTE: It is possible for the same contract to implement multiple of these ERCs (e.g. ERC20 and ERC721), + // so this data structure actually makes sense. + mapping(uint256 => mapping(uint256 => mapping(address => mapping(uint256 => uint256)))) SlotEligibleItems; + // Subject contract address => subject token ID => slot => EquippedItem + // Item type and Pool ID on EquippedItem have the same constraints as they do elsewhere (e.g. in SlotEligibleItems). + // + // NOTE: We have added the subject contract address as the first mapping key as a defense against + // future modifications which may allow administrators to modify the subject contract address. + // If such a modification were made, it could make it possible for a bad actor administrator + // to change the address of the subject token to the address to an ERC721 contract they control + // and drain all items from every subject token's inventory. + // If this contract is deployed as a Diamond proxy, the owner of the Diamond can pretty much + // do whatever they want in any case, but adding the subject contract address as a key protects + // users of non-Diamond deployments even under small variants of the current implementation. + // It also offers *some* protection to users of Diamond deployments of the Inventory. + // ERC721 Contract Address => + // subjectTokenId => + // slotId => + // EquippedItem struct + mapping(address => mapping(uint256 => mapping(uint256 => EquippedItem))) EquippedItems; + } + + function inventoryStorage() + internal + pure + returns (InventoryStorage storage istore) + { + bytes32 position = STORAGE_POSITION; + assembly { + istore.slot := position + } + } +} /** InventoryFacet is a smart contract that can either be used standalone or as part of an EIP-2535 Diamond @@ -33,10 +89,10 @@ Admin flow: - [x] Define tokens as equippable in inventory slots Player flow: -- [] Equip ERC20 tokens in eligible inventory slots -- [] Equip ERC721 tokens in eligible inventory slots -- [] Equip ERC1155 tokens in eligible inventory slots -- [ ] Unequip items from unequippable slots +- [x] Equip ERC20 tokens in eligible inventory slots +- [x] Equip ERC721 tokens in eligible inventory slots +- [x] Equip ERC1155 tokens in eligible inventory slots +- [x] Unequip items from persistent slots Batch endpoints: - [ ] Marking items as equippable @@ -50,6 +106,11 @@ contract InventoryFacet is TerminusPermissions, DiamondReentrancyGuard { + event AdministratorDesignated( + address indexed adminTerminusAddress, + uint256 indexed adminTerminusPoolId + ); + modifier onlyAdmin() { LibInventory.InventoryStorage storage istore = LibInventory .inventoryStorage(); @@ -64,27 +125,6 @@ contract InventoryFacet is _; } - modifier requireValidItemType(uint256 itemType) { - require( - itemType == LibInventory.ERC20_ITEM_TYPE || - itemType == LibInventory.ERC721_ITEM_TYPE || - itemType == LibInventory.ERC1155_ITEM_TYPE, - "InventoryFacet.requireValidItemType: Invalid item type" - ); - _; - } - - modifier onlyContractSubjectOwner(uint256 subjectTokenId) { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - IERC721 subjectContract = IERC721(istore.ContractERC721Address); - require( - msg.sender == subjectContract.ownerOf(subjectTokenId), - "InventoryFacet.getSubjectTokenSlots: Message sender is not owner of subject token" - ); - _; - } - /** An Inventory must be initialized with: 1. adminTerminusAddress: The address for the Terminus contract which hosts the Administrator badge. @@ -104,7 +144,7 @@ contract InventoryFacet is istore.ContractERC721Address = contractAddress; emit AdministratorDesignated(adminTerminusAddress, adminTerminusPoolId); - emit ContractAddressDesignated(contractAddress); + emit NewSubjectAddress(contractAddress); } function adminTerminusInfo() external view returns (address, uint256) { @@ -118,8 +158,7 @@ contract InventoryFacet is } function createSlot( - bool unequippable, - uint256 slotType, + bool persistent, string memory slotURI ) external onlyAdmin returns (uint256) { LibInventory.InventoryStorage storage istore = LibInventory @@ -129,113 +168,24 @@ contract InventoryFacet is istore.NumSlots += 1; uint256 newSlot = istore.NumSlots; // save the slot type! - istore.SlotData[newSlot] = LibInventory.Slot({ - SlotType: slotType, + istore.SlotData[newSlot] = Slot({ SlotURI: slotURI, - SlotIsUnequippable: unequippable, - SlotId: newSlot + SlotIsPersistent: persistent }); - emit SlotCreated(msg.sender, newSlot, unequippable, slotType); + emit SlotCreated(msg.sender, newSlot); + emit NewSlotURI(newSlot); + emit NewSlotPersistence(newSlot, persistent); return newSlot; } - function createSlotType( - uint256 slotType, - string memory slotTypeName - ) external onlyAdmin { - require( - bytes(slotTypeName).length > 0, - "InventoryFacet.setSlotType: Slot type name must be non-empty" - ); - require( - slotType > 0, - "InventoryFacet.setSlotType: Slot type must be greater than 0" - ); - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - istore.SlotTypes[slotType] = slotTypeName; - emit NewSlotTypeAdded(msg.sender, slotType, slotTypeName); - } - - function assignSlotType(uint256 slot, uint256 slotType) external onlyAdmin { - require( - slotType > 0, - "InventoryFacet.addSlotType: SlotType must be greater than 0" - ); - - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - istore.SlotData[slot].SlotType = slotType; - emit SlotTypeAdded(msg.sender, slot, slotType); - } - - function getSlotType( - uint256 slotType - ) external view returns (string memory slotTypeName) { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - return istore.SlotTypes[slotType]; - } - - // TODO: @ogarciarevett change this to use a external backpack NFT - function addBackpackToSubject( - uint256 slotQty, - uint256 toSubjectTokenId, - uint256 slotType, - string memory slotURI - ) external onlyAdmin { - require( - slotQty > 0, - "InventoryFacet.addBackpackToSubject: Slot quantity must be greater than 0" - ); - - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - uint256 previousSlotNumSubject = istore - .SubjectSlots[istore.ContractERC721Address][toSubjectTokenId].length; - - for (uint256 i = 0; i < slotQty; i++) { - istore - .SubjectSlots[istore.ContractERC721Address][toSubjectTokenId].push( - LibInventory.Slot({ - SlotType: slotType, - SlotURI: slotURI, - SlotIsUnequippable: false, - SlotId: previousSlotNumSubject + i == - previousSlotNumSubject - ? previousSlotNumSubject + 1 - : previousSlotNumSubject + i - }) - ); - } - - emit BackpackAdded(msg.sender, toSubjectTokenId, slotQty); - } - - function getSubjectTokenSlots( - uint256 subjectTokenId - ) - external - view - onlyContractSubjectOwner(subjectTokenId) - returns (LibInventory.Slot[] memory slots) - { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - return - istore.SubjectSlots[istore.ContractERC721Address][subjectTokenId]; - } - - // COUNTER function numSlots() external view returns (uint256) { return LibInventory.inventoryStorage().NumSlots; } function getSlotById( uint256 slotId - ) external view returns (LibInventory.Slot memory slot) { + ) external view returns (Slot memory slot) { return LibInventory.inventoryStorage().SlotData[slotId]; } @@ -246,34 +196,36 @@ contract InventoryFacet is return istore.SlotData[slotId].SlotURI; } - function setSlotUri( + function setSlotURI( string memory newSlotURI, uint slotId ) external onlyAdmin { LibInventory.InventoryStorage storage istore = LibInventory .inventoryStorage(); - LibInventory.Slot memory slot = istore.SlotData[slotId]; + Slot memory slot = istore.SlotData[slotId]; slot.SlotURI = newSlotURI; istore.SlotData[slotId] = slot; emit NewSlotURI(slotId); } - function slotIsUnequippable(uint256 slotId) external view returns (bool) { + function slotIsPersistent(uint256 slotId) external view returns (bool) { return - LibInventory.inventoryStorage().SlotData[slotId].SlotIsUnequippable; + LibInventory.inventoryStorage().SlotData[slotId].SlotIsPersistent; } - function setSlotUnequippable( - bool unquippable, - uint256 slotId + function setSlotPersistent( + uint256 slotId, + bool persistent ) external onlyAdmin { LibInventory.InventoryStorage storage istore = LibInventory .inventoryStorage(); - LibInventory.Slot memory slot = istore.SlotData[slotId]; - slot.SlotIsUnequippable = unquippable; + Slot memory slot = istore.SlotData[slotId]; + slot.SlotIsPersistent = persistent; istore.SlotData[slotId] = slot; + + emit NewSlotPersistence(slotId, persistent); } function markItemAsEquippableInSlot( @@ -282,7 +234,14 @@ contract InventoryFacet is address itemAddress, uint256 itemPoolId, uint256 maxAmount - ) external onlyAdmin requireValidItemType(itemType) { + ) external onlyAdmin { + require( + itemType == LibInventory.ERC20_ITEM_TYPE || + itemType == LibInventory.ERC721_ITEM_TYPE || + itemType == LibInventory.ERC1155_ITEM_TYPE, + "InventoryFacet.markItemAsEquippableInSlot: Invalid item type" + ); + LibInventory.InventoryStorage storage istore = LibInventory .inventoryStorage(); @@ -345,11 +304,11 @@ contract InventoryFacet is .inventoryStorage(); require( - istore.SlotData[slot].SlotIsUnequippable, - "InventoryFacet._unequip: That slot is not unequippable" + !istore.SlotData[slot].SlotIsPersistent, + "InventoryFacet._unequip: That slot is persistent. You cannot unequip items from it." ); - LibInventory.EquippedItem storage existingItem = istore.EquippedItems[ + EquippedItem storage existingItem = istore.EquippedItems[ istore.ContractERC721Address ][subjectTokenId][slot]; @@ -412,14 +371,20 @@ contract InventoryFacet is address itemAddress, uint256 itemTokenId, uint256 amount - ) external requireValidItemType(itemType) diamondNonReentrant { + ) external diamondNonReentrant { + require( + itemType == LibInventory.ERC20_ITEM_TYPE || + itemType == LibInventory.ERC721_ITEM_TYPE || + itemType == LibInventory.ERC1155_ITEM_TYPE, + "InventoryFacet.equip: Invalid item type" + ); + require( itemType == LibInventory.ERC721_ITEM_TYPE || itemType == LibInventory.ERC1155_ITEM_TYPE || itemTokenId == 0, "InventoryFacet.equip: itemTokenId can only be non-zero for ERC721 or ERC1155 items" ); - require( itemType == LibInventory.ERC20_ITEM_TYPE || itemType == LibInventory.ERC1155_ITEM_TYPE || @@ -440,6 +405,10 @@ contract InventoryFacet is // increasing the amount of an existing token in the given slot. To increase gas-efficiency, // we could add more complex logic here to handle that situation by only equipping the difference // between the existing amount of the token and the target amount. + // TODO(zomglings): The current implementation makes it so that players cannot increase the + // number of tokens of a given type that are equipped into a persistent slot. I would consider + // this a bug. For more details, see comment at bottom of the following test: + // web3cli.test_inventory.TestPlayerFlow.test_player_cannot_unequip_erc20_tokens_from_persistent_slot_but_can_increase_amount if ( istore .EquippedItems[istore.ContractERC721Address][subjectTokenId][slot] @@ -497,6 +466,15 @@ contract InventoryFacet is ); } + istore.EquippedItems[istore.ContractERC721Address][subjectTokenId][ + slot + ] = EquippedItem({ + ItemType: itemType, + ItemAddress: itemAddress, + ItemTokenId: itemTokenId, + Amount: amount + }); + emit ItemEquipped( subjectTokenId, slot, @@ -506,15 +484,6 @@ contract InventoryFacet is amount, msg.sender ); - - istore.EquippedItems[istore.ContractERC721Address][subjectTokenId][ - slot - ] = LibInventory.EquippedItem({ - ItemType: itemType, - ItemAddress: itemAddress, - ItemTokenId: itemTokenId, - Amount: amount - }); } function unequip( @@ -538,7 +507,7 @@ contract InventoryFacet is function getEquippedItem( uint256 subjectTokenId, uint256 slot - ) external view returns (LibInventory.EquippedItem memory item) { + ) external view returns (EquippedItem memory item) { LibInventory.InventoryStorage storage istore = LibInventory .inventoryStorage(); @@ -547,64 +516,10 @@ contract InventoryFacet is "InventoryFacet.getEquippedItem: Slot does not exist" ); - LibInventory.EquippedItem memory equippedItem = istore.EquippedItems[ + EquippedItem memory equippedItem = istore.EquippedItems[ istore.ContractERC721Address ][subjectTokenId][slot]; return equippedItem; } - - function getAllEquippedItems( - uint256 subjectTokenId, - uint256[] memory slots - ) external view returns (LibInventory.EquippedItem[] memory equippedItems) { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - LibInventory.EquippedItem[] - memory items = new LibInventory.EquippedItem[](slots.length); - - for (uint256 i = 0; i < slots.length; i++) { - require( - slots[i] <= this.numSlots(), - "InventoryFacet.getEquippedItem: Slot does not exist" - ); - LibInventory.EquippedItem memory equippedItem = istore - .EquippedItems[istore.ContractERC721Address][subjectTokenId][ - slots[i] - ]; - items[i] = equippedItem; - } - - return items; - } - - function equipBatch( - uint256 subjectTokenId, - uint256[] memory slots, - LibInventory.EquippedItem[] memory items - ) external diamondNonReentrant { - require( - items.length > 0, - "InventoryFacet.batchEquip: Must equip at least one item" - ); - require( - slots.length == items.length, - "InventoryFacet.batchEquip: Must provide a slot for each item" - ); - for (uint256 i = 0; i < items.length; i++) { - require( - slots[i] <= this.numSlots(), - "InventoryFacet.batchEquip: Slot does not exist" - ); - this.equip( - subjectTokenId, - slots[i], - items[i].ItemType, - items[i].ItemAddress, - items[i].ItemTokenId, - items[i].Amount - ); - } - } }