From 3d9b86ed8afff6df661aa69eaf5e85bce33ba03e Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Wed, 26 Jul 2023 21:02:45 -0700 Subject: [PATCH 01/30] Updated .gitignore --- .gitignore | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 From 63000ad27959223c513bf24737f8604ec821fc11 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Wed, 26 Jul 2023 21:05:16 -0700 Subject: [PATCH 02/30] Removed old IInventory and LibInventory These were just there to generate the Python scripts, which will need to be updated to support the new Inventory anyway. --- contracts/diamond/interfaces/IInventory.sol | 144 ------------------- contracts/diamond/libraries/LibInventory.sol | 93 ------------ 2 files changed, 237 deletions(-) delete mode 100644 contracts/diamond/interfaces/IInventory.sol delete mode 100644 contracts/diamond/libraries/LibInventory.sol 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 From 09c5e0feb8e8007a4efdfbaf2450acdf89645c43 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Wed, 26 Jul 2023 21:06:13 -0700 Subject: [PATCH 03/30] Removed old `contracts/InventorFacet.sol` That file was only there to generate the CLI before. CLI will need to be updated, as will the setup-inventory script. --- contracts/InventoryFacet.sol | 610 ----------------------------------- 1 file changed, 610 deletions(-) delete mode 100644 contracts/InventoryFacet.sol diff --git a/contracts/InventoryFacet.sol b/contracts/InventoryFacet.sol deleted file mode 100644 index a6f6b276..00000000 --- a/contracts/InventoryFacet.sol +++ /dev/null @@ -1,610 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * Authors: Omar Garcia, Moonstream DAO (engineering@moonstream.to) - * GitHub: https://github.com/G7DAO/contracts - */ - -pragma solidity ^0.8.17; - -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"; - -/** -InventoryFacet is a smart contract that can either be used standalone or as part of an EIP-2535 Diamond -proxy contract. - -It implements an inventory system which can be layered onto any ERC721 contract. - -For more details, please refer to the design document: -https://docs.google.com/document/d/1Oa9I9b7t46_ngYp-Pady5XKEDW8M2NE9rI0GBRACZBI/edit?usp=sharing - -Admin flow: -- [x] Create inventory slots -- [x] Specify whether inventory slots are equippable or not on slot creation -- [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 - -Batch endpoints: -- [ ] Marking items as equippable -- [ ] Equipping items -- [ ] Unequipping items - */ -contract InventoryFacet is - IInventory, - ERC721Holder, - ERC1155Holder, - TerminusPermissions, - DiamondReentrancyGuard -{ - modifier onlyAdmin() { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - require( - _holdsPoolToken( - istore.AdminTerminusAddress, - istore.AdminTerminusPoolId, - 1 - ), - "InventoryFacet.onlyAdmin: The address is not an authorized administrator" - ); - _; - } - - 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. - 2. adminTerminusPoolId: The pool ID for the Administrator badge on that Terminus contract. - 3. contractAddress: The address of the ERC721 contract that the Inventory refers to. - */ - function init( - address adminTerminusAddress, - uint256 adminTerminusPoolId, - address contractAddress - ) external { - LibDiamond.enforceIsContractOwner(); - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - istore.AdminTerminusAddress = adminTerminusAddress; - istore.AdminTerminusPoolId = adminTerminusPoolId; - istore.ContractERC721Address = contractAddress; - - emit AdministratorDesignated(adminTerminusAddress, adminTerminusPoolId); - emit ContractAddressDesignated(contractAddress); - } - - function adminTerminusInfo() external view returns (address, uint256) { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - return (istore.AdminTerminusAddress, istore.AdminTerminusPoolId); - } - - function subject() external view returns (address) { - return LibInventory.inventoryStorage().ContractERC721Address; - } - - function createSlot( - bool unequippable, - uint256 slotType, - string memory slotURI - ) external onlyAdmin returns (uint256) { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - // Slots are 1-indexed! - istore.NumSlots += 1; - uint256 newSlot = istore.NumSlots; - // save the slot type! - istore.SlotData[newSlot] = LibInventory.Slot({ - SlotType: slotType, - SlotURI: slotURI, - SlotIsUnequippable: unequippable, - SlotId: newSlot - }); - - emit SlotCreated(msg.sender, newSlot, unequippable, slotType); - 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) { - return LibInventory.inventoryStorage().SlotData[slotId]; - } - - function getSlotURI(uint256 slotId) external view returns (string memory) { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - return istore.SlotData[slotId].SlotURI; - } - - function setSlotUri( - string memory newSlotURI, - uint slotId - ) external onlyAdmin { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - LibInventory.Slot memory slot = istore.SlotData[slotId]; - slot.SlotURI = newSlotURI; - istore.SlotData[slotId] = slot; - emit NewSlotURI(slotId); - } - - function slotIsUnequippable(uint256 slotId) external view returns (bool) { - return - LibInventory.inventoryStorage().SlotData[slotId].SlotIsUnequippable; - } - - function setSlotUnequippable( - bool unquippable, - uint256 slotId - ) external onlyAdmin { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - LibInventory.Slot memory slot = istore.SlotData[slotId]; - slot.SlotIsUnequippable = unquippable; - istore.SlotData[slotId] = slot; - } - - function markItemAsEquippableInSlot( - uint256 slot, - uint256 itemType, - address itemAddress, - uint256 itemPoolId, - uint256 maxAmount - ) external onlyAdmin requireValidItemType(itemType) { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - require( - itemType == LibInventory.ERC1155_ITEM_TYPE || itemPoolId == 0, - "InventoryFacet.markItemAsEquippableInSlot: Pool ID can only be non-zero for items from ERC1155 contracts" - ); - require( - itemType != LibInventory.ERC721_ITEM_TYPE || maxAmount <= 1, - "InventoryFacet.markItemAsEquippableInSlot: maxAmount should be at most 1 for items from ERC721 contracts" - ); - - // NOTE: We do not perform any check on the previously registered maxAmount for the item. - // This gives administrators some flexibility in marking items as no longer eligible for slots. - // But any player who has already equipped items in a slot before a change in maxAmount will - // not be subject to the new limitation. This is something administrators will have to factor - // into their game design. - istore.SlotEligibleItems[slot][itemType][itemAddress][ - itemPoolId - ] = maxAmount; - - emit ItemMarkedAsEquippableInSlot( - slot, - itemType, - itemAddress, - itemPoolId, - maxAmount - ); - } - - function maxAmountOfItemInSlot( - uint256 slot, - uint256 itemType, - address itemAddress, - uint256 itemPoolId - ) external view returns (uint256) { - return - LibInventory.inventoryStorage().SlotEligibleItems[slot][itemType][ - itemAddress - ][itemPoolId]; - } - - function _unequip( - uint256 subjectTokenId, - uint256 slot, - bool unequipAll, - uint256 amount - ) internal { - require( - !unequipAll || amount == 0, - "InventoryFacet._unequip: Set amount to 0 if you are unequipping all instances of the item in that slot" - ); - - require( - unequipAll || amount > 0, - "InventoryFacet._unequip: Since you are not unequipping all instances of the item in that slot, you must specify how many instances you want to unequip" - ); - - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - require( - istore.SlotData[slot].SlotIsUnequippable, - "InventoryFacet._unequip: That slot is not unequippable" - ); - - LibInventory.EquippedItem storage existingItem = istore.EquippedItems[ - istore.ContractERC721Address - ][subjectTokenId][slot]; - - if (unequipAll) { - amount = existingItem.Amount; - } - - require( - amount <= existingItem.Amount, - "InventoryFacet._unequip: Attempting to unequip too many items from the slot" - ); - - if (existingItem.ItemType == 20) { - IERC20 erc20Contract = IERC20(existingItem.ItemAddress); - bool transferSuccess = erc20Contract.transfer(msg.sender, amount); - require( - transferSuccess, - "InventoryFacet._unequip: Error unequipping ERC20 item - transfer was unsuccessful" - ); - } else if (existingItem.ItemType == 721 && amount > 0) { - IERC721 erc721Contract = IERC721(existingItem.ItemAddress); - erc721Contract.safeTransferFrom( - address(this), - msg.sender, - existingItem.ItemTokenId - ); - } else if (existingItem.ItemType == 1155) { - IERC1155 erc1155Contract = IERC1155(existingItem.ItemAddress); - erc1155Contract.safeTransferFrom( - address(this), - msg.sender, - existingItem.ItemTokenId, - amount, - "" - ); - } - - emit ItemUnequipped( - subjectTokenId, - slot, - existingItem.ItemType, - existingItem.ItemAddress, - existingItem.ItemTokenId, - amount, - msg.sender - ); - - existingItem.Amount -= amount; - if (existingItem.Amount == 0) { - delete istore.EquippedItems[istore.ContractERC721Address][ - subjectTokenId - ][slot]; - } - } - - function equip( - uint256 subjectTokenId, - uint256 slot, - uint256 itemType, - address itemAddress, - uint256 itemTokenId, - uint256 amount - ) external requireValidItemType(itemType) diamondNonReentrant { - 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 || - amount == 1, - "InventoryFacet.equip: amount can be other value than 1 only for ERC20 and ERC1155 items" - ); - - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - IERC721 subjectContract = IERC721(istore.ContractERC721Address); - require( - msg.sender == subjectContract.ownerOf(subjectTokenId), - "InventoryFacet.equip: Message sender is not owner of subject token" - ); - - // TODO(zomglings): Although this does the job, it is not gas-efficient if the caller 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. - if ( - istore - .EquippedItems[istore.ContractERC721Address][subjectTokenId][slot] - .ItemType != 0 - ) { - _unequip(subjectTokenId, slot, true, 0); - } - - require( - // Note the if statement when accessing the itemPoolId key in the SlotEligibleItems mapping. - // That field is only relevant for ERC1155 tokens. For ERC20 and ERC721 tokens, the capacity - // is set under the 0 key in that position. - // Using itemTokenId as the key in that position would incorrectly yield a value of 0 for - // ERC721 tokens. - istore.SlotEligibleItems[slot][itemType][itemAddress][ - itemType == 1155 ? itemTokenId : 0 - ] >= amount, - "InventoryFacet.equip: You can not equip those many instances of that item into the given slot" - ); - - if (itemType == LibInventory.ERC20_ITEM_TYPE) { - IERC20 erc20Contract = IERC20(itemAddress); - bool erc20TransferSuccess = erc20Contract.transferFrom( - msg.sender, - address(this), - amount - ); - require( - erc20TransferSuccess, - "InventoryFacet.equip: Error equipping ERC20 item - transfer was unsuccessful" - ); - } else if (itemType == LibInventory.ERC721_ITEM_TYPE) { - IERC721 erc721Contract = IERC721(itemAddress); - require( - msg.sender == erc721Contract.ownerOf(itemTokenId), - "InventoryFacet.equip: Message sender cannot equip an item that they do not own" - ); - erc721Contract.safeTransferFrom( - msg.sender, - address(this), - itemTokenId - ); - } else if (itemType == LibInventory.ERC1155_ITEM_TYPE) { - IERC1155 erc1155Contract = IERC1155(itemAddress); - require( - erc1155Contract.balanceOf(msg.sender, itemTokenId) >= amount, - "InventoryFacet.equip: Message sender does not own enough of that item to equip" - ); - erc1155Contract.safeTransferFrom( - msg.sender, - address(this), - itemTokenId, - amount, - "" - ); - } - - emit ItemEquipped( - subjectTokenId, - slot, - itemType, - itemAddress, - itemTokenId, - amount, - msg.sender - ); - - istore.EquippedItems[istore.ContractERC721Address][subjectTokenId][ - slot - ] = LibInventory.EquippedItem({ - ItemType: itemType, - ItemAddress: itemAddress, - ItemTokenId: itemTokenId, - Amount: amount - }); - } - - function unequip( - uint256 subjectTokenId, - uint256 slot, - bool unequipAll, - uint256 amount - ) external diamondNonReentrant { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - IERC721 subjectContract = IERC721(istore.ContractERC721Address); - require( - msg.sender == subjectContract.ownerOf(subjectTokenId), - "InventoryFacet.equip: Message sender is not owner of subject token" - ); - - _unequip(subjectTokenId, slot, unequipAll, amount); - } - - function getEquippedItem( - uint256 subjectTokenId, - uint256 slot - ) external view returns (LibInventory.EquippedItem memory item) { - LibInventory.InventoryStorage storage istore = LibInventory - .inventoryStorage(); - - require( - slot <= this.numSlots(), - "InventoryFacet.getEquippedItem: Slot does not exist" - ); - - LibInventory.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 - ); - } - } -} From b5ccb1c0111a70d21eaf44c87ac2acd942d741a4 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Wed, 26 Jul 2023 21:09:21 -0700 Subject: [PATCH 04/30] Renamed `enginecli` -> `web3cli` --- cli/setup.py | 6 +++--- cli/{enginecli => web3cli}/ClaimProxy.py | 0 cli/{enginecli => web3cli}/CraftingFacet.py | 0 cli/{enginecli => web3cli}/Diamond.py | 0 cli/{enginecli => web3cli}/DiamondCutFacet.py | 0 cli/{enginecli => web3cli}/DiamondLoupeFacet.py | 0 cli/{enginecli => web3cli}/Dropper.py | 0 cli/{enginecli => web3cli}/DropperFacet.py | 0 cli/{enginecli => web3cli}/ERC1155CompatibleClaimProxy.py | 0 cli/{enginecli => web3cli}/ERC721CompatibleClaimProxy.py | 0 cli/{enginecli => web3cli}/ExploitContract.py | 0 cli/{enginecli => web3cli}/GOFPFacet.py | 0 cli/{enginecli => web3cli}/ITerminus.py | 0 cli/{enginecli => web3cli}/InventoryFacet.py | 0 cli/{enginecli => web3cli}/Lootbox.py | 0 cli/{enginecli => web3cli}/MockERC1155.py | 0 cli/{enginecli => web3cli}/MockERC721.py | 0 cli/{enginecli => web3cli}/MockErc20.py | 0 cli/{enginecli => web3cli}/MockTerminus.py | 0 cli/{enginecli => web3cli}/OwnershipFacet.py | 0 cli/{enginecli => web3cli}/ReentrancyExploitable.py | 0 cli/{enginecli => web3cli}/__init__.py | 0 cli/{enginecli => web3cli}/abi.py | 0 cli/{enginecli => web3cli}/cli.py | 2 +- cli/{enginecli => web3cli}/core.py | 0 cli/{enginecli => web3cli}/drop.py | 0 cli/{enginecli => web3cli}/flows.py | 2 +- cli/{enginecli => web3cli}/gas_profiler.py | 0 cli/{enginecli => web3cli}/setup_drop.py | 0 cli/{enginecli => web3cli}/test_crafting.py | 0 cli/{enginecli => web3cli}/test_dropper.py | 0 cli/{enginecli => web3cli}/test_gofp.py | 0 cli/{enginecli => web3cli}/test_lootbox.py | 0 cli/{enginecli => web3cli}/test_random_lootbox.py | 0 cli/{enginecli => web3cli}/test_reentrancy_guard.py | 0 cli/{enginecli => web3cli}/version.py | 0 cli/{enginecli => web3cli}/version.txt | 0 37 files changed, 5 insertions(+), 5 deletions(-) rename cli/{enginecli => web3cli}/ClaimProxy.py (100%) rename cli/{enginecli => web3cli}/CraftingFacet.py (100%) rename cli/{enginecli => web3cli}/Diamond.py (100%) rename cli/{enginecli => web3cli}/DiamondCutFacet.py (100%) rename cli/{enginecli => web3cli}/DiamondLoupeFacet.py (100%) rename cli/{enginecli => web3cli}/Dropper.py (100%) rename cli/{enginecli => web3cli}/DropperFacet.py (100%) rename cli/{enginecli => web3cli}/ERC1155CompatibleClaimProxy.py (100%) rename cli/{enginecli => web3cli}/ERC721CompatibleClaimProxy.py (100%) rename cli/{enginecli => web3cli}/ExploitContract.py (100%) rename cli/{enginecli => web3cli}/GOFPFacet.py (100%) rename cli/{enginecli => web3cli}/ITerminus.py (100%) rename cli/{enginecli => web3cli}/InventoryFacet.py (100%) rename cli/{enginecli => web3cli}/Lootbox.py (100%) rename cli/{enginecli => web3cli}/MockERC1155.py (100%) rename cli/{enginecli => web3cli}/MockERC721.py (100%) rename cli/{enginecli => web3cli}/MockErc20.py (100%) rename cli/{enginecli => web3cli}/MockTerminus.py (100%) rename cli/{enginecli => web3cli}/OwnershipFacet.py (100%) rename cli/{enginecli => web3cli}/ReentrancyExploitable.py (100%) rename cli/{enginecli => web3cli}/__init__.py (100%) rename cli/{enginecli => web3cli}/abi.py (100%) rename cli/{enginecli => web3cli}/cli.py (98%) rename cli/{enginecli => web3cli}/core.py (100%) rename cli/{enginecli => web3cli}/drop.py (100%) rename cli/{enginecli => web3cli}/flows.py (99%) rename cli/{enginecli => web3cli}/gas_profiler.py (100%) rename cli/{enginecli => web3cli}/setup_drop.py (100%) rename cli/{enginecli => web3cli}/test_crafting.py (100%) rename cli/{enginecli => web3cli}/test_dropper.py (100%) rename cli/{enginecli => web3cli}/test_gofp.py (100%) rename cli/{enginecli => web3cli}/test_lootbox.py (100%) rename cli/{enginecli => web3cli}/test_random_lootbox.py (100%) rename cli/{enginecli => web3cli}/test_reentrancy_guard.py (100%) rename cli/{enginecli => web3cli}/version.py (100%) rename cli/{enginecli => web3cli}/version.txt (100%) 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/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 100% rename from cli/enginecli/InventoryFacet.py rename to cli/web3cli/InventoryFacet.py 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 100% rename from cli/enginecli/core.py rename to cli/web3cli/core.py 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/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/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 From 2664149369f9ab560e356d9af191a98193cea2c4 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Thu, 27 Jul 2023 05:53:01 -0700 Subject: [PATCH 05/30] Moved over Inventory code from G7DAO/contracts And organized it to be flatter (file structure). --- contracts/inventory/IInventory.sol | 167 +++++++ contracts/inventory/InventoryFacet.sol | 618 +++++++++++++++++++++++++ 2 files changed, 785 insertions(+) create mode 100644 contracts/inventory/IInventory.sol create mode 100644 contracts/inventory/InventoryFacet.sol diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol new file mode 100644 index 00000000..a5da36e5 --- /dev/null +++ b/contracts/inventory/IInventory.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +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; +} + +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 (EquippedItem memory item); + + function getSlotById( + uint256 slotId + ) external view returns (Slot memory slots); + + function getSubjectTokenSlots( + uint256 subjectTokenId + ) external view returns (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 addSlotType(uint256 slot, uint256 slotType) external; + + function getSlotType( + uint256 slotType + ) external view returns (string memory slotTypeName); + + function setSlotUnequippable(bool unquippable, uint256 slotId) external; +} diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol new file mode 100644 index 00000000..607d7bd3 --- /dev/null +++ b/contracts/inventory/InventoryFacet.sol @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: Apache-2.0 + +/** + * Authors: Moonstream Engineering (engineering@moonstream.to) + * GitHub: https://github.com/moonstream-to/web3 + */ + +pragma solidity ^0.8.17; + +import {TerminusPermissions} from "@moonstream/contracts/terminus/TerminusPermissions.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 {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("g7dao.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, 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 + } + } +} + +/** +InventoryFacet is a smart contract that can either be used standalone or as part of an EIP-2535 Diamond +proxy contract. + +It implements an inventory system which can be layered onto any ERC721 contract. + +For more details, please refer to the design document: +https://docs.google.com/document/d/1Oa9I9b7t46_ngYp-Pady5XKEDW8M2NE9rI0GBRACZBI/edit?usp=sharing + +Admin flow: +- [x] Create inventory slots +- [x] Specify whether inventory slots are equippable or not on slot creation +- [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 + +Batch endpoints: +- [ ] Marking items as equippable +- [ ] Equipping items +- [ ] Unequipping items + */ +contract InventoryFacet is + IInventory, + ERC721Holder, + ERC1155Holder, + TerminusPermissions, + DiamondReentrancyGuard +{ + modifier onlyAdmin() { + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + require( + _holdsPoolToken( + istore.AdminTerminusAddress, + istore.AdminTerminusPoolId, + 1 + ), + "InventoryFacet.onlyAdmin: The address is not an authorized administrator" + ); + _; + } + + 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. + 2. adminTerminusPoolId: The pool ID for the Administrator badge on that Terminus contract. + 3. contractAddress: The address of the ERC721 contract that the Inventory refers to. + */ + function init( + address adminTerminusAddress, + uint256 adminTerminusPoolId, + address contractAddress + ) external { + LibDiamond.enforceIsContractOwner(); + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + istore.AdminTerminusAddress = adminTerminusAddress; + istore.AdminTerminusPoolId = adminTerminusPoolId; + istore.ContractERC721Address = contractAddress; + + emit AdministratorDesignated(adminTerminusAddress, adminTerminusPoolId); + emit ContractAddressDesignated(contractAddress); + } + + function adminTerminusInfo() external view returns (address, uint256) { + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + return (istore.AdminTerminusAddress, istore.AdminTerminusPoolId); + } + + function subject() external view returns (address) { + return LibInventory.inventoryStorage().ContractERC721Address; + } + + function createSlot( + bool unequippable, + uint256 slotType, + string memory slotURI + ) external onlyAdmin returns (uint256) { + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + + // Slots are 1-indexed! + istore.NumSlots += 1; + uint256 newSlot = istore.NumSlots; + // save the slot type! + istore.SlotData[newSlot] = Slot({ + SlotType: slotType, + SlotURI: slotURI, + SlotIsUnequippable: unequippable, + SlotId: newSlot + }); + + emit SlotCreated(msg.sender, newSlot, unequippable, slotType); + 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 addSlotType(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]; + } + + 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( + 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 (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 (Slot memory slot) { + return LibInventory.inventoryStorage().SlotData[slotId]; + } + + function getSlotURI(uint256 slotId) external view returns (string memory) { + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + + return istore.SlotData[slotId].SlotURI; + } + + function setSlotUri( + string memory newSlotURI, + uint slotId + ) external onlyAdmin { + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + + Slot memory slot = istore.SlotData[slotId]; + slot.SlotURI = newSlotURI; + istore.SlotData[slotId] = slot; + emit NewSlotURI(slotId); + } + + function slotIsUnequippable(uint256 slotId) external view returns (bool) { + return + LibInventory.inventoryStorage().SlotData[slotId].SlotIsUnequippable; + } + + function setSlotUnequippable( + bool unquippable, + uint256 slotId + ) external onlyAdmin { + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + + Slot memory slot = istore.SlotData[slotId]; + slot.SlotIsUnequippable = unquippable; + istore.SlotData[slotId] = slot; + } + + function markItemAsEquippableInSlot( + uint256 slot, + uint256 itemType, + address itemAddress, + uint256 itemPoolId, + uint256 maxAmount + ) external onlyAdmin requireValidItemType(itemType) { + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + + require( + itemType == LibInventory.ERC1155_ITEM_TYPE || itemPoolId == 0, + "InventoryFacet.markItemAsEquippableInSlot: Pool ID can only be non-zero for items from ERC1155 contracts" + ); + require( + itemType != LibInventory.ERC721_ITEM_TYPE || maxAmount <= 1, + "InventoryFacet.markItemAsEquippableInSlot: maxAmount should be at most 1 for items from ERC721 contracts" + ); + + // NOTE: We do not perform any check on the previously registered maxAmount for the item. + // This gives administrators some flexibility in marking items as no longer eligible for slots. + // But any player who has already equipped items in a slot before a change in maxAmount will + // not be subject to the new limitation. This is something administrators will have to factor + // into their game design. + istore.SlotEligibleItems[slot][itemType][itemAddress][ + itemPoolId + ] = maxAmount; + + emit ItemMarkedAsEquippableInSlot( + slot, + itemType, + itemAddress, + itemPoolId, + maxAmount + ); + } + + function maxAmountOfItemInSlot( + uint256 slot, + uint256 itemType, + address itemAddress, + uint256 itemPoolId + ) external view returns (uint256) { + return + LibInventory.inventoryStorage().SlotEligibleItems[slot][itemType][ + itemAddress + ][itemPoolId]; + } + + function _unequip( + uint256 subjectTokenId, + uint256 slot, + bool unequipAll, + uint256 amount + ) internal { + require( + !unequipAll || amount == 0, + "InventoryFacet._unequip: Set amount to 0 if you are unequipping all instances of the item in that slot" + ); + + require( + unequipAll || amount > 0, + "InventoryFacet._unequip: Since you are not unequipping all instances of the item in that slot, you must specify how many instances you want to unequip" + ); + + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + + require( + istore.SlotData[slot].SlotIsUnequippable, + "InventoryFacet._unequip: That slot is not unequippable" + ); + + EquippedItem storage existingItem = istore.EquippedItems[ + istore.ContractERC721Address + ][subjectTokenId][slot]; + + if (unequipAll) { + amount = existingItem.Amount; + } + + require( + amount <= existingItem.Amount, + "InventoryFacet._unequip: Attempting to unequip too many items from the slot" + ); + + if (existingItem.ItemType == 20) { + IERC20 erc20Contract = IERC20(existingItem.ItemAddress); + bool transferSuccess = erc20Contract.transfer(msg.sender, amount); + require( + transferSuccess, + "InventoryFacet._unequip: Error unequipping ERC20 item - transfer was unsuccessful" + ); + } else if (existingItem.ItemType == 721 && amount > 0) { + IERC721 erc721Contract = IERC721(existingItem.ItemAddress); + erc721Contract.safeTransferFrom( + address(this), + msg.sender, + existingItem.ItemTokenId + ); + } else if (existingItem.ItemType == 1155) { + IERC1155 erc1155Contract = IERC1155(existingItem.ItemAddress); + erc1155Contract.safeTransferFrom( + address(this), + msg.sender, + existingItem.ItemTokenId, + amount, + "" + ); + } + + emit ItemUnequipped( + subjectTokenId, + slot, + existingItem.ItemType, + existingItem.ItemAddress, + existingItem.ItemTokenId, + amount, + msg.sender + ); + + existingItem.Amount -= amount; + if (existingItem.Amount == 0) { + delete istore.EquippedItems[istore.ContractERC721Address][ + subjectTokenId + ][slot]; + } + } + + function equip( + uint256 subjectTokenId, + uint256 slot, + uint256 itemType, + address itemAddress, + uint256 itemTokenId, + uint256 amount + ) external requireValidItemType(itemType) diamondNonReentrant { + 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 || + amount == 1, + "InventoryFacet.equip: amount can be other value than 1 only for ERC20 and ERC1155 items" + ); + + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + + IERC721 subjectContract = IERC721(istore.ContractERC721Address); + require( + msg.sender == subjectContract.ownerOf(subjectTokenId), + "InventoryFacet.equip: Message sender is not owner of subject token" + ); + + // TODO(zomglings): Although this does the job, it is not gas-efficient if the caller 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. + if ( + istore + .EquippedItems[istore.ContractERC721Address][subjectTokenId][slot] + .ItemType != 0 + ) { + _unequip(subjectTokenId, slot, true, 0); + } + + require( + // Note the if statement when accessing the itemPoolId key in the SlotEligibleItems mapping. + // That field is only relevant for ERC1155 tokens. For ERC20 and ERC721 tokens, the capacity + // is set under the 0 key in that position. + // Using itemTokenId as the key in that position would incorrectly yield a value of 0 for + // ERC721 tokens. + istore.SlotEligibleItems[slot][itemType][itemAddress][ + itemType == 1155 ? itemTokenId : 0 + ] >= amount, + "InventoryFacet.equip: You can not equip those many instances of that item into the given slot" + ); + + if (itemType == LibInventory.ERC20_ITEM_TYPE) { + IERC20 erc20Contract = IERC20(itemAddress); + bool erc20TransferSuccess = erc20Contract.transferFrom( + msg.sender, + address(this), + amount + ); + require( + erc20TransferSuccess, + "InventoryFacet.equip: Error equipping ERC20 item - transfer was unsuccessful" + ); + } else if (itemType == LibInventory.ERC721_ITEM_TYPE) { + IERC721 erc721Contract = IERC721(itemAddress); + require( + msg.sender == erc721Contract.ownerOf(itemTokenId), + "InventoryFacet.equip: Message sender cannot equip an item that they do not own" + ); + erc721Contract.safeTransferFrom( + msg.sender, + address(this), + itemTokenId + ); + } else if (itemType == LibInventory.ERC1155_ITEM_TYPE) { + IERC1155 erc1155Contract = IERC1155(itemAddress); + require( + erc1155Contract.balanceOf(msg.sender, itemTokenId) >= amount, + "InventoryFacet.equip: Message sender does not own enough of that item to equip" + ); + erc1155Contract.safeTransferFrom( + msg.sender, + address(this), + itemTokenId, + amount, + "" + ); + } + + emit ItemEquipped( + subjectTokenId, + slot, + itemType, + itemAddress, + itemTokenId, + amount, + msg.sender + ); + + istore.EquippedItems[istore.ContractERC721Address][subjectTokenId][ + slot + ] = EquippedItem({ + ItemType: itemType, + ItemAddress: itemAddress, + ItemTokenId: itemTokenId, + Amount: amount + }); + } + + function unequip( + uint256 subjectTokenId, + uint256 slot, + bool unequipAll, + uint256 amount + ) external diamondNonReentrant { + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + + IERC721 subjectContract = IERC721(istore.ContractERC721Address); + require( + msg.sender == subjectContract.ownerOf(subjectTokenId), + "InventoryFacet.equip: Message sender is not owner of subject token" + ); + + _unequip(subjectTokenId, slot, unequipAll, amount); + } + + function getEquippedItem( + uint256 subjectTokenId, + uint256 slot + ) external view returns (EquippedItem memory item) { + LibInventory.InventoryStorage storage istore = LibInventory + .inventoryStorage(); + + require( + slot <= this.numSlots(), + "InventoryFacet.getEquippedItem: Slot does not exist" + ); + + EquippedItem memory equippedItem = istore.EquippedItems[ + istore.ContractERC721Address + ][subjectTokenId][slot]; + + return equippedItem; + } +} From dee709f845c037fdce2552119bd8a1082dafc234 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Thu, 27 Jul 2023 16:24:44 -0700 Subject: [PATCH 06/30] Updated InventoryFacet comment --- contracts/inventory/InventoryFacet.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index 607d7bd3..3b5c2ad6 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -97,10 +97,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 unequippable slots Batch endpoints: - [ ] Marking items as equippable From 902204521120d6b53896600cab0b35a53ea51222 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Thu, 27 Jul 2023 16:50:47 -0700 Subject: [PATCH 07/30] Updated core.py to contain inventory_gogogo and related functionality As a part of this, I also added: 1. The ability to specify existing facets for diamond_gogogo and for inventory_gogogo. This lays the foundation to eventually resolve https://github.com/moonstream-to/web3/issues/315 2. A fix for https://github.com/moonstream-to/web3/issues/323 --- cli/web3cli/core.py | 352 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 279 insertions(+), 73 deletions(-) diff --git a/cli/web3cli/core.py b/cli/web3cli/core.py index 660cb83f..324b4542 100644 --- a/cli/web3cli/core.py +++ b/cli/web3cli/core.py @@ -23,6 +23,7 @@ ReentrancyExploitable, CraftingFacet, GOFPFacet, + InventoryFacet, ) FACETS: Dict[str, Any] = { @@ -33,6 +34,7 @@ "ReentrancyExploitable": ReentrancyExploitable, "CraftingFacet": CraftingFacet, "GOFPFacet": GOFPFacet, + "InventoryFacet": InventoryFacet, } FACET_INIT_CALLDATA: Dict[str, str] = { @@ -42,6 +44,9 @@ "GOFPFacet": lambda address, *args: GOFPFacet.GOFPFacet( address ).contract.init.encode_input(*args), + "InventoryFacet": lambda address, *args: InventoryFacet.InventoryFacet( + address + ).contract.init.encode_input(*args), } DIAMOND_FACET_PRECEDENCE: List[str] = [ @@ -58,8 +63,9 @@ class EngineFeatures(Enum): - DROPPER = "dropper" - GOFP = "GardenOfForkingPaths" + DROPPER = "DropperFacet" + GOFP = "GOFPFacet" + INVENTORY = "InventoryFacet" def feature_from_facet_name(facet_name: str) -> Optional[EngineFeatures]: @@ -72,11 +78,13 @@ def feature_from_facet_name(facet_name: str) -> Optional[EngineFeatures]: FEATURE_FACETS: Dict[EngineFeatures, List[str]] = { EngineFeatures.DROPPER: ["DropperFacet"], EngineFeatures.GOFP: ["GOFPFacet"], + EngineFeatures.INVENTORY: ["InventoryFacet"], } FEATURE_IGNORES: Dict[EngineFeatures, List[str]] = { EngineFeatures.DROPPER: {"methods": ["init"], "selectors": []}, EngineFeatures.GOFP: {"methods": ["init"], "selectors": []}, + EngineFeatures.INVENTORY: {"methods": ["init"], "selectors": []}, } FACET_ACTIONS: Dict[str, int] = {"add": 0, "replace": 1, "remove": 2} @@ -188,7 +196,10 @@ def facet_cut( diamond = DiamondCutFacet.DiamondCutFacet(diamond_address) calldata = b"" - if FACET_INIT_CALLDATA.get(facet_name) is not None: + if ( + initializer_address != ZERO_ADDRESS + and FACET_INIT_CALLDATA.get(facet_name) is not None + ): if initializer_args is None: initializer_args = [] calldata = FACET_INIT_CALLDATA[facet_name]( @@ -200,40 +211,14 @@ def facet_cut( return transaction -def crafting_gogogo( - owner_address: str, transaction_config: Dict[str, Any] -) -> Dict[str, Any]: - result = diamond_gogogo(owner_address, transaction_config) - - try: - crafting_facet = CraftingFacet.CraftingFacet(None) - crafting_facet.deploy(transaction_config) - except Exception as e: - print(e) - result["error"] = f"Failed to deploy CraftingFacet: {e}" - return result - - result["CraftingFacet"] = crafting_facet.address - - try: - facet_cut( - result["Diamond"], - "CraftingFacet", - crafting_facet.address, - "add", - transaction_config, - ) - except Exception as e: - print(e) - result["error"] = f"Failed to diamondCut cut CraftingFacet: {e}" - return result - - result["attached"].append("CraftingFacet") - return result - - def diamond_gogogo( - owner_address: str, transaction_config: Dict[str, Any] + owner_address: str, + transaction_config: Dict[str, Any], + diamond_cut_address: Optional[str] = None, + diamond_address: Optional[str] = None, + diamond_loupe_address: Optional[str] = None, + ownership_address: Optional[str] = None, + verify_contracts: Optional[bool] = False, ) -> Dict[str, Any]: """ Deploy diamond along with all its basic facets and attach those facets to the diamond. @@ -241,42 +226,61 @@ def diamond_gogogo( Returns addresses of all the deployed contracts with the contract names as keys. """ result: Dict[str, Any] = {"contracts": {}, "attached": []} - - try: - diamond_cut_facet = DiamondCutFacet.DiamondCutFacet(None) - diamond_cut_facet.deploy(transaction_config) - except Exception as e: - print(e) - result["error"] = "Failed to deploy DiamondCutFacet" - return result - result["contracts"]["DiamondCutFacet"] = diamond_cut_facet.address - - try: - diamond = Diamond.Diamond(None) - diamond.deploy(owner_address, diamond_cut_facet.address, transaction_config) - except Exception as e: - print(e) - result["error"] = "Failed to deploy Diamond" - return result - result["contracts"]["Diamond"] = diamond.address - - try: - diamond_loupe_facet = DiamondLoupeFacet.DiamondLoupeFacet(None) - diamond_loupe_facet.deploy(transaction_config) - except Exception as e: - print(e) - result["error"] = "Failed to deploy DiamondLoupeFacet" - return result - result["contracts"]["DiamondLoupeFacet"] = diamond_loupe_facet.address - - try: - ownership_facet = OwnershipFacet.OwnershipFacet(None) - ownership_facet.deploy(transaction_config) - except Exception as e: - print(e) - result["error"] = "Failed to deploy OwnershipFacet" - return result - result["contracts"]["OwnershipFacet"] = ownership_facet.address + if verify_contracts: + result["verified"] = [] + result["verification_errors"] = [] + + if diamond_cut_address is None: + try: + diamond_cut_facet = DiamondCutFacet.DiamondCutFacet(None) + diamond_cut_facet.deploy(transaction_config) + except Exception as e: + print(e) + result["error"] = "Failed to deploy DiamondCutFacet" + return result + result["contracts"]["DiamondCutFacet"] = diamond_cut_facet.address + else: + result["contracts"]["DiamondCutFacet"] = diamond_cut_address + diamond_cut_facet = DiamondCutFacet.DiamondCutFacet(diamond_cut_address) + + if diamond_address is None: + try: + diamond = Diamond.Diamond(None) + diamond.deploy(owner_address, diamond_cut_facet.address, transaction_config) + except Exception as e: + print(e) + result["error"] = "Failed to deploy Diamond" + return result + result["contracts"]["Diamond"] = diamond.address + else: + result["contracts"]["Diamond"] = diamond_address + diamond = Diamond.Diamond(diamond_address) + + if diamond_loupe_address is None: + try: + diamond_loupe_facet = DiamondLoupeFacet.DiamondLoupeFacet(None) + diamond_loupe_facet.deploy(transaction_config) + except Exception as e: + print(e) + result["error"] = "Failed to deploy DiamondLoupeFacet" + return result + result["contracts"]["DiamondLoupeFacet"] = diamond_loupe_facet.address + else: + result["contracts"]["DiamondLoupeFacet"] = diamond_loupe_address + diamond_loupe_facet = DiamondLoupeFacet.DiamondLoupeFacet(diamond_loupe_address) + + if ownership_address is None: + try: + ownership_facet = OwnershipFacet.OwnershipFacet(None) + ownership_facet.deploy(transaction_config) + except Exception as e: + print(e) + result["error"] = "Failed to deploy OwnershipFacet" + return result + result["contracts"]["OwnershipFacet"] = ownership_facet.address + else: + result["contracts"]["OwnershipFacet"] = ownership_address + ownership_facet = OwnershipFacet.OwnershipFacet(ownership_address) try: facet_cut( @@ -306,6 +310,62 @@ def diamond_gogogo( return result result["attached"].append("OwnershipFacet") + if verify_contracts: + try: + diamond_cut_facet.verify_contract() + result["verified"].append("DiamondCutFacet") + except Exception as e: + result["verification_errors"].append(repr(e)) + + try: + diamond.verify_contract() + result["verified"].append("Diamond") + except Exception as e: + result["verification_errors"].append(repr(e)) + + try: + diamond_loupe_facet.verify_contract() + result["verified"].append("DiamondLoupeFacet") + except Exception as e: + result["verification_errors"].append(repr(e)) + + try: + ownership_facet.verify_contract() + except Exception as e: + result["verification_errors"].append(repr(e)) + + return result + + +def crafting_gogogo( + owner_address: str, transaction_config: Dict[str, Any] +) -> Dict[str, Any]: + result = diamond_gogogo(owner_address, transaction_config) + + try: + crafting_facet = CraftingFacet.CraftingFacet(None) + crafting_facet.deploy(transaction_config) + except Exception as e: + print(e) + result["error"] = f"Failed to deploy CraftingFacet: {e}" + return result + + result["CraftingFacet"] = crafting_facet.address + + try: + facet_cut( + result["Diamond"], + "CraftingFacet", + crafting_facet.address, + "add", + transaction_config, + ) + except Exception as e: + print(e) + result["error"] = f"Failed to diamondCut cut CraftingFacet: {e}" + return result + + result["attached"].append("CraftingFacet") return result @@ -385,7 +445,6 @@ def create_lootboxes_from_config( ) for lootbox in config: # This is for validating data - lootbox_items = [] for item in lootbox["items"]: if item["rewardType"] == 20: @@ -569,6 +628,67 @@ def gofp_gogogo( return deployment_info +def inventory_gogogo( + admin_terminus_address: str, + admin_terminus_pool_id: int, + subject_erc721_address: str, + transaction_config: Dict[str, Any], + diamond_cut_address: Optional[str] = None, + diamond_address: Optional[str] = None, + diamond_loupe_address: Optional[str] = None, + ownership_address: Optional[str] = None, + inventory_facet_address: Optional[str] = None, + verify_contracts: Optional[bool] = False, +) -> Dict[str, Any]: + """ + Deploys an EIP2535 Diamond contract and an InventoryFacet and mounts the InventoryFacet onto the Diamond contract. + + Returns the addresses and attachments. + """ + deployment_info = diamond_gogogo( + owner_address=transaction_config["from"].address, + transaction_config=transaction_config, + diamond_cut_address=diamond_cut_address, + diamond_address=diamond_address, + diamond_loupe_address=diamond_loupe_address, + ownership_address=ownership_address, + verify_contracts=verify_contracts, + ) + + if inventory_facet_address is None: + inventory_facet = InventoryFacet.InventoryFacet(None) + inventory_facet.deploy(transaction_config=transaction_config) + else: + inventory_facet = InventoryFacet.InventoryFacet(inventory_facet_address) + + deployment_info["contracts"]["InventoryFacet"] = inventory_facet.address + + if verify_contracts: + try: + inventory_facet.verify_contract() + deployment_info["verified"].append("InventoryFacet") + except Exception as e: + deployment_info["verification_errors"].append(repr(e)) + + facet_cut( + deployment_info["contracts"]["Diamond"], + "InventoryFacet", + inventory_facet.address, + "add", + transaction_config, + initializer_address=inventory_facet.address, + feature=EngineFeatures.INVENTORY, + initializer_args=[ + admin_terminus_address, + admin_terminus_pool_id, + subject_erc721_address, + ], + ) + deployment_info["attached"].append("InventoryFacet") + + return deployment_info + + def handle_facet_cut(args: argparse.Namespace) -> None: network.connect(args.network) diamond_address = args.address @@ -635,6 +755,27 @@ def handle_gofp_gogogo(args: argparse.Namespace) -> None: json.dump(result, sys.stdout, indent=4) +def handle_inventory_gogogo(args: argparse.Namespace) -> None: + network.connect(args.network) + transaction_config = InventoryFacet.get_transaction_config(args) + result = inventory_gogogo( + admin_terminus_address=args.admin_terminus_address, + admin_terminus_pool_id=args.admin_terminus_pool_id, + subject_erc721_address=args.subject_erc721_address, + transaction_config=transaction_config, + diamond_cut_address=args.diamond_cut_address, + diamond_address=args.diamond_address, + diamond_loupe_address=args.diamond_loupe_address, + ownership_address=args.ownership_address, + inventory_facet_address=args.inventory_facet_address, + verify_contracts=args.verify_contracts, + ) + if args.outfile is not None: + with args.outfile: + json.dump(result, args.outfile) + json.dump(result, sys.stdout, indent=4) + + def handle_crafting_gogogo(args: argparse.Namespace) -> None: network.connect(args.network) @@ -774,6 +915,71 @@ def generate_cli(): ) gofp_gogogo_parser.set_defaults(func=handle_gofp_gogogo) + inventory_gogogo_parser = subcommands.add_parser( + "inventory-gogogo", + description="Deploy Inventory diamond contract", + ) + Diamond.add_default_arguments(inventory_gogogo_parser, transact=True) + inventory_gogogo_parser.add_argument( + "--verify-contracts", + action="store_true", + help="Verify contracts", + ) + inventory_gogogo_parser.add_argument( + "--admin-terminus-address", + required=True, + help="Address of Terminus contract defining access control for this GardenOfForkingPaths contract", + ) + inventory_gogogo_parser.add_argument( + "--admin-terminus-pool-id", + required=True, + type=int, + help="Pool ID of Terminus pool for administrators of this GardenOfForkingPaths contract", + ) + inventory_gogogo_parser.add_argument( + "--subject-erc721-address", + required=True, + help="Address of ERC721 contract that the Inventory modifies", + ) + inventory_gogogo_parser.add_argument( + "--diamond-cut-address", + required=False, + default=None, + help="Address to deployed DiamondCutFacet. If provided, this command skips deployment of a new DiamondCutFacet.", + ) + inventory_gogogo_parser.add_argument( + "--diamond-address", + required=False, + default=None, + help="Address to deployed Diamond contract. If provided, this command skips deployment of a new Diamond contract and simply mounts the required facets onto the existing Diamond contract. Assumes that there is no collision of selectors.", + ) + inventory_gogogo_parser.add_argument( + "--diamond-loupe-address", + required=False, + default=None, + help="Address to deployed DiamondLoupeFacet. If provided, this command skips deployment of a new DiamondLoupeFacet. It mounts the existing DiamondLoupeFacet onto the Diamond.", + ) + inventory_gogogo_parser.add_argument( + "--ownership-address", + required=False, + default=None, + help="Address to deployed OwnershipFacet. If provided, this command skips deployment of a new OwnershipFacet. It mounts the existing OwnershipFacet onto the Diamond.", + ) + inventory_gogogo_parser.add_argument( + "--inventory-facet-address", + required=False, + default=None, + help="Address to deployed InventoryFacet. If provided, this command skips deployment of a new InventoryFacet. It mounts the existing InventoryFacet onto the Diamond.", + ) + inventory_gogogo_parser.add_argument( + "-o", + "--outfile", + type=argparse.FileType("w"), + default=None, + help="(Optional) file to write deployed addresses to", + ) + inventory_gogogo_parser.set_defaults(func=handle_inventory_gogogo) + lootbox_gogogo_parser = subcommands.add_parser( "lootbox-gogogo", help="Deploys Lootbox contract", From d155ed3c332fd3428b225ec6663670231e9fa857 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Thu, 27 Jul 2023 16:57:57 -0700 Subject: [PATCH 08/30] Ported over InventoryFacet... from https://github.com/G7DAO/contracts And I got the tests working. --- cli/web3cli/inventory_events.py | 188 +++ cli/web3cli/test_inventory.py | 2045 +++++++++++++++++++++++++++++++ 2 files changed, 2233 insertions(+) create mode 100644 cli/web3cli/inventory_events.py create mode 100644 cli/web3cli/test_inventory.py diff --git a/cli/web3cli/inventory_events.py b/cli/web3cli/inventory_events.py new file mode 100644 index 00000000..f42af424 --- /dev/null +++ b/cli/web3cli/inventory_events.py @@ -0,0 +1,188 @@ +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", +} + +CONTRACT_ADDRESS_DESIGNATED_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "contractAddress", + "type": "address", + } + ], + "name": "ContractAddressDesignated", + "type": "event", +} + +SLOT_CREATED_ABI = { + "anonymous": False, + "inputs": [ + { + "indexed": True, + "internalType": "address", + "name": "creator", + "type": "address", + }, + { + "indexed": True, + "internalType": "uint256", + "name": "slot", + "type": "uint256", + }, + { + "indexed": False, + "internalType": "bool", + "name": "unequippable", + "type": "bool", + }, + { + "indexed": True, + "internalType": "uint256", + "name": "slotType", + "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", +} diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py new file mode 100644 index 00000000..5d600e88 --- /dev/null +++ b/cli/web3cli/test_inventory.py @@ -0,0 +1,2045 @@ +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 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) + cls.deployed_contracts = inventory_gogogo( + cls.terminus.address, + cls.admin_terminus_pool_id, + cls.nft.address, + cls.owner_tx_config, + ) + cls.postdeployment_block = len(chain) + cls.inventory = InventoryFacet.InventoryFacet( + cls.deployed_contracts["contracts"]["Diamond"] + ) + + +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.CONTRACT_ADDRESS_DESIGNATED_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_nonunequippable_slot(self): + unequippable = False + + num_slots_0 = self.inventory.num_slots() + tx_receipt = self.inventory.create_slot( + unequippable, + slot_type=1, + 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_unequippable(num_slots_1), unequippable) + + 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, + ) + self.assertEqual( + inventory_slot_created_events[0]["args"]["unequippable"], + unequippable, + ) + + def test_admin_can_create_unequippable_slot(self): + unequippable = True + + num_slots_0 = self.inventory.num_slots() + tx_receipt = self.inventory.create_slot( + unequippable, + slot_type=1, + 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_unequippable(num_slots_1), unequippable) + + 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, + ) + self.assertEqual( + inventory_slot_created_events[0]["args"]["unequippable"], + unequippable, + ) + + def test_admin_can_add_backpacks_to_subject_token(self): + unequippable = False + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + # player has 0 slots in their inventory + self.inventory.create_slot( + unequippable, + slot_type=1, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + num_slots_1 = self.inventory.num_slots() + + # all the players has 1 slot in their inventory + self.assertEqual(num_slots_1, 1) + + # admin adds 10 more slots to the subject token + self.inventory.add_backpack_to_subject( + 10, + subject_token_id, + 0, + "some_fancy_slot_uri", + transaction_config={"from": self.admin}, + ) + + # all the players still having only 1 slot in their inventory + self.assertEqual(num_slots_1, 1) + + def test_admin_can_set_slot_uri(self): + unequippable = False + + self.inventory.create_slot( + unequippable, + slot_type=1, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + num_slots_1 = self.inventory.num_slots() + + # set the slot uri + 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") + + def test_nonadmin_cannot_create_slot(self): + unequippable = False + + num_slots_0 = self.inventory.num_slots() + with self.assertRaises(VirtualMachineError): + self.inventory.create_slot( + unequippable, + slot_type=1, + 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_noadmin_cannot_set_slot_uri(self): + unequippable = False + self.inventory.create_slot( + unequippable, + slot_type=1, + 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_get_subject_slots(self): + unequippable = False + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + # player has 0 slots in their inventory + self.inventory.create_slot( + unequippable, + slot_type=1, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + num_slots_0 = self.inventory.num_slots() + + # admin adds 10 more slots to the subject token + self.inventory.add_backpack_to_subject( + 10, + subject_token_id, + 0, + "some_fancy_slot_uri", + transaction_config={"from": self.admin}, + ) + + # set the slot uri + with self.assertRaises(VirtualMachineError): + self.inventory.get_subject_token_slots(subject_token_id) + + num_slots_1 = self.inventory.num_slots() + + self.assertEqual(num_slots_1, num_slots_0) + + def test_noadmin_cannot_add_backpack_to_subject(self): + unequippable = False + subject_token_id = self.nft.total_supply() + self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) + + # player has 0 slots in their inventory + self.inventory.create_slot( + unequippable, + slot_type=1, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + num_slots_0 = self.inventory.num_slots() + + # set the slot uri + with self.assertRaises(VirtualMachineError): + # admin adds 10 more slots to the subject token + self.inventory.add_backpack_to_subject( + 10, + subject_token_id, + 0, + "some_fancy_slot_uri", + 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, + ): + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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): + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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): + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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, + ): + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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): + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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): + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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, + ): + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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.payment_token.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, + ): + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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.payment_token.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, + ): + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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-unequippable slot. + unequippable = False + self.inventory.create_slot( + unequippable, + slot_type=1, + 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): + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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, + ) + + +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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + self.assertTrue(self.inventory.slot_is_unequippable(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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + self.assertTrue(self.inventory.slot_is_unequippable(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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + self.assertTrue(self.inventory.slot_is_unequippable(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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + slot_uri="random_uri", + transaction_config={"from": self.admin}, + ) + slot = self.inventory.num_slots() + self.assertTrue(self.inventory.slot_is_unequippable(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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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 + unequippable = True + self.inventory.create_slot( + unequippable, + slot_type=1, + 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, + ) From ae699e263be213bc78a456c5c359b1ced51dcb04 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Thu, 27 Jul 2023 17:00:17 -0700 Subject: [PATCH 09/30] Inventory Diamond storage slot... `g7dao` -> `moonstreamdao` --- contracts/inventory/InventoryFacet.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index 3b5c2ad6..f364e608 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -23,7 +23,7 @@ proxy. */ library LibInventory { bytes32 constant STORAGE_POSITION = - keccak256("g7dao.eth.storage.Inventory"); + keccak256("moonstreamdao.eth.storage.Inventory"); uint256 constant ERC20_ITEM_TYPE = 20; uint256 constant ERC721_ITEM_TYPE = 721; From fb0323ca7004cead2690d34d77965e6296633963 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Thu, 27 Jul 2023 17:04:23 -0700 Subject: [PATCH 10/30] onlyContractSubjectOwner -> onlySubjectTokenOwner --- contracts/inventory/InventoryFacet.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index f364e608..9600feee 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -138,7 +138,7 @@ contract InventoryFacet is _; } - modifier onlyContractSubjectOwner(uint256 subjectTokenId) { + modifier onlySubjectTokenOwner(uint256 subjectTokenId) { LibInventory.InventoryStorage storage istore = LibInventory .inventoryStorage(); IERC721 subjectContract = IERC721(istore.ContractERC721Address); @@ -282,7 +282,7 @@ contract InventoryFacet is ) external view - onlyContractSubjectOwner(subjectTokenId) + onlySubjectTokenOwner(subjectTokenId) returns (Slot[] memory slots) { LibInventory.InventoryStorage storage istore = LibInventory From 5041eadbb2e845c5cb1c1486ed78732ededea1bd Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Thu, 27 Jul 2023 17:09:20 -0700 Subject: [PATCH 11/30] Removed requireValidItemType modifier It complicates scope and was only being used twice. --- contracts/inventory/InventoryFacet.sol | 28 +++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index 9600feee..b57eb14b 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -128,16 +128,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 onlySubjectTokenOwner(uint256 subjectTokenId) { LibInventory.InventoryStorage storage istore = LibInventory .inventoryStorage(); @@ -345,7 +335,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(); @@ -475,7 +472,14 @@ 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 || From 56bfa77955ba28d6ac49e9190cb95830c64b4780 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Thu, 27 Jul 2023 17:15:30 -0700 Subject: [PATCH 12/30] Removed all functionality related to subject slots --- cli/web3cli/test_inventory.py | 89 -------------------------- contracts/inventory/IInventory.sol | 11 ---- contracts/inventory/InventoryFacet.sol | 67 ------------------- 3 files changed, 167 deletions(-) diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index 5d600e88..19be2999 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -181,35 +181,6 @@ def test_admin_can_create_unequippable_slot(self): unequippable, ) - def test_admin_can_add_backpacks_to_subject_token(self): - unequippable = False - subject_token_id = self.nft.total_supply() - self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) - - # player has 0 slots in their inventory - self.inventory.create_slot( - unequippable, - slot_type=1, - slot_uri="random_uri", - transaction_config={"from": self.admin}, - ) - num_slots_1 = self.inventory.num_slots() - - # all the players has 1 slot in their inventory - self.assertEqual(num_slots_1, 1) - - # admin adds 10 more slots to the subject token - self.inventory.add_backpack_to_subject( - 10, - subject_token_id, - 0, - "some_fancy_slot_uri", - transaction_config={"from": self.admin}, - ) - - # all the players still having only 1 slot in their inventory - self.assertEqual(num_slots_1, 1) - def test_admin_can_set_slot_uri(self): unequippable = False @@ -270,66 +241,6 @@ def test_noadmin_cannot_set_slot_uri(self): self.assertEqual(num_slots_1, num_slots_0) - def test_admin_cannot_get_subject_slots(self): - unequippable = False - subject_token_id = self.nft.total_supply() - self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) - - # player has 0 slots in their inventory - self.inventory.create_slot( - unequippable, - slot_type=1, - slot_uri="random_uri", - transaction_config={"from": self.admin}, - ) - num_slots_0 = self.inventory.num_slots() - - # admin adds 10 more slots to the subject token - self.inventory.add_backpack_to_subject( - 10, - subject_token_id, - 0, - "some_fancy_slot_uri", - transaction_config={"from": self.admin}, - ) - - # set the slot uri - with self.assertRaises(VirtualMachineError): - self.inventory.get_subject_token_slots(subject_token_id) - - num_slots_1 = self.inventory.num_slots() - - self.assertEqual(num_slots_1, num_slots_0) - - def test_noadmin_cannot_add_backpack_to_subject(self): - unequippable = False - subject_token_id = self.nft.total_supply() - self.nft.mint(self.player.address, subject_token_id, {"from": self.owner}) - - # player has 0 slots in their inventory - self.inventory.create_slot( - unequippable, - slot_type=1, - slot_uri="random_uri", - transaction_config={"from": self.admin}, - ) - num_slots_0 = self.inventory.num_slots() - - # set the slot uri - with self.assertRaises(VirtualMachineError): - # admin adds 10 more slots to the subject token - self.inventory.add_backpack_to_subject( - 10, - subject_token_id, - 0, - "some_fancy_slot_uri", - 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, ): diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol index a5da36e5..7ea22852 100644 --- a/contracts/inventory/IInventory.sol +++ b/contracts/inventory/IInventory.sol @@ -139,17 +139,6 @@ interface IInventory { uint256 slotId ) external view returns (Slot memory slots); - function getSubjectTokenSlots( - uint256 subjectTokenId - ) external view returns (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( diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index b57eb14b..319e1bb4 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -62,12 +62,6 @@ library LibInventory { // 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() @@ -128,17 +122,6 @@ contract InventoryFacet is _; } - modifier onlySubjectTokenOwner(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. @@ -232,56 +215,6 @@ contract InventoryFacet is return istore.SlotTypes[slotType]; } - 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( - 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 - onlySubjectTokenOwner(subjectTokenId) - returns (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; } From bfe4910a6e075775a152e798c55bc130f61c03fd Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 20:27:40 -0700 Subject: [PATCH 13/30] `pragma solidity ^0.8.0;` And made comments more accurate --- contracts/inventory/IInventory.sol | 2 +- contracts/inventory/InventoryFacet.sol | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol index a5da36e5..2c9675a4 100644 --- a/contracts/inventory/IInventory.sol +++ b/contracts/inventory/IInventory.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; +pragma solidity ^0.8.0; struct Slot { string SlotURI; diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index 607d7bd3..c89eb011 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -5,7 +5,7 @@ * 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 "@openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol"; @@ -97,10 +97,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 unequippable slots Batch endpoints: - [ ] Marking items as equippable From 7bc0d195ac2a38723423cd34df6eeb134a491f78 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 20:38:09 -0700 Subject: [PATCH 14/30] Removed slot types The slot types are implicitly defined by the items that can be equipped in those slots. Having an explicit notion of slot type with no conceivable way to enforce it creates two sources of truth about what a slot does - it's type and the set of items equippable in that slot. This makes the system more complex and difficult to work with, with no additional benefit. --- cli/web3cli/IInventory.py | 651 +++++++++++++++++++++++++ cli/web3cli/InventoryFacet.py | 229 +-------- cli/web3cli/inventory_events.py | 6 - cli/web3cli/test_inventory.py | 30 -- contracts/inventory/IInventory.sol | 34 +- contracts/inventory/InventoryFacet.sol | 44 +- 6 files changed, 655 insertions(+), 339 deletions(-) create mode 100644 cli/web3cli/IInventory.py diff --git a/cli/web3cli/IInventory.py b/cli/web3cli/IInventory.py new file mode 100644 index 00000000..a399974c --- /dev/null +++ b/cli/web3cli/IInventory.py @@ -0,0 +1,651 @@ +# 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, unequippable: bool, slot_uri: str, transaction_config) -> Any: + self.assert_contract_is_instantiated() + return self.contract.createSlot(unequippable, 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 init( + self, + admin_terminus_address: ChecksumAddress, + admin_terminus_pool_id: int, + subject_address: ChecksumAddress, + transaction_config, + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.init( + admin_terminus_address, + admin_terminus_pool_id, + subject_address, + transaction_config, + ) + + 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_unequippable( + self, unquippable: bool, slot_id: int, transaction_config + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.setSlotUnequippable( + unquippable, slot_id, transaction_config + ) + + def slot_is_unequippable( + self, slot_id: int, block_number: Optional[Union[str, int]] = "latest" + ) -> Any: + self.assert_contract_is_instantiated() + return self.contract.slotIsUnequippable.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( + unequippable=args.unequippable, + 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_init(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + transaction_config = get_transaction_config(args) + result = contract.init( + admin_terminus_address=args.admin_terminus_address, + admin_terminus_pool_id=args.admin_terminus_pool_id, + subject_address=args.subject_address, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +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_unequippable(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + transaction_config = get_transaction_config(args) + result = contract.set_slot_unequippable( + unquippable=args.unquippable, + slot_id=args.slot_id, + transaction_config=transaction_config, + ) + print(result) + if args.verbose: + print(result.info()) + + +def handle_slot_is_unequippable(args: argparse.Namespace) -> None: + network.connect(args.network) + contract = IInventory(args.address) + result = contract.slot_is_unequippable( + 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( + "--unequippable", 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) + + init_parser = subcommands.add_parser("init") + add_default_arguments(init_parser, True) + init_parser.add_argument( + "--admin-terminus-address", required=True, help="Type: address" + ) + init_parser.add_argument( + "--admin-terminus-pool-id", required=True, help="Type: uint256", type=int + ) + init_parser.add_argument("--subject-address", required=True, help="Type: address") + init_parser.set_defaults(func=handle_init) + + 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_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( + "--slot-id", required=True, help="Type: uint256", type=int + ) + set_slot_unequippable_parser.set_defaults(func=handle_set_slot_unequippable) + + 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-id", required=True, help="Type: uint256", type=int + ) + slot_is_unequippable_parser.set_defaults(func=handle_slot_is_unequippable) + + 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/web3cli/InventoryFacet.py b/cli/web3cli/InventoryFacet.py index b36d64a7..052f9cf5 100644 --- a/cli/web3cli/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, unequippable: 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(unequippable, 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, @@ -429,22 +367,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 +374,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, slot_uri=args.slot_uri, transaction_config=transaction_config, ) @@ -479,20 +388,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 +406,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 +426,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 +433,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) @@ -766,59 +617,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 - ) 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 +647,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 +664,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 +671,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( diff --git a/cli/web3cli/inventory_events.py b/cli/web3cli/inventory_events.py index f42af424..e4209e31 100644 --- a/cli/web3cli/inventory_events.py +++ b/cli/web3cli/inventory_events.py @@ -53,12 +53,6 @@ "name": "unequippable", "type": "bool", }, - { - "indexed": True, - "internalType": "uint256", - "name": "slotType", - "type": "uint256", - }, ], "name": "SlotCreated", "type": "event", diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index 19be2999..61d7df75 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -115,7 +115,6 @@ def test_admin_can_create_nonunequippable_slot(self): num_slots_0 = self.inventory.num_slots() tx_receipt = self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -151,7 +150,6 @@ def test_admin_can_create_unequippable_slot(self): num_slots_0 = self.inventory.num_slots() tx_receipt = self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -186,7 +184,6 @@ def test_admin_can_set_slot_uri(self): self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -211,7 +208,6 @@ def test_nonadmin_cannot_create_slot(self): with self.assertRaises(VirtualMachineError): self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.player}, ) @@ -223,7 +219,6 @@ def test_noadmin_cannot_set_slot_uri(self): unequippable = False self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -247,7 +242,6 @@ def test_admin_cannot_mark_contracts_with_invalid_type_as_eligible_for_slots( unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -276,7 +270,6 @@ def test_admin_can_mark_erc20_tokens_as_eligible_for_slots(self): unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -333,7 +326,6 @@ def test_nonadmin_cannot_mark_erc20_tokens_as_eligible_for_slots(self): unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -364,7 +356,6 @@ def test_admin_cannot_mark_erc20_tokens_as_eligible_for_slots_if_pool_id_is_nonz unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -393,7 +384,6 @@ def test_admin_can_mark_erc721_tokens_as_eligible_for_slots(self): unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -450,7 +440,6 @@ def test_nonadmin_cannot_mark_erc721_tokens_as_eligible_for_slots(self): unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -481,7 +470,6 @@ def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_if_pool_id_is_non unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -512,7 +500,6 @@ def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_with_max_amount_g unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -543,7 +530,6 @@ def test_admin_can_mark_erc721_tokens_as_eligible_for_slots_with_max_amount_1_th unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -618,7 +604,6 @@ def test_admin_can_mark_erc1155_tokens_as_eligible_for_slots(self): unequippable = False self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -677,7 +662,6 @@ def test_nonadmin_cannot_mark_erc1155_tokens_as_eligible_for_slots(self): unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -719,7 +703,6 @@ def test_player_can_equip_erc20_items_onto_their_subject_tokens(self): unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -798,7 +781,6 @@ def test_player_cannot_equip_too_many_erc20_items_onto_their_subject_tokens(self unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -847,7 +829,6 @@ def test_player_can_equip_erc721_items_onto_their_subject_tokens(self): unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -927,7 +908,6 @@ def test_player_cannot_equip_erc721_items_they_own_onto_subject_tokens_they_do_n unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -973,7 +953,6 @@ def test_player_cannot_equip_erc721_items_which_they_do_not_own(self): unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1024,7 +1003,6 @@ def test_player_can_equip_erc1155_items_onto_their_subject_tokens(self): unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1112,7 +1090,6 @@ def test_player_cannot_equip_too_many_erc1155_items_onto_their_subject_tokens(se unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1163,7 +1140,6 @@ def test_player_can_unequip_all_erc20_items_in_slot_on_their_subject_tokens(self unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1261,7 +1237,6 @@ def test_player_can_unequip_some_but_not_all_erc20_items_in_slot_on_their_subjec unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1359,7 +1334,6 @@ def test_player_can_unequip_all_erc721_items_in_slot_on_their_subject_tokens(sel unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1458,7 +1432,6 @@ def test_player_can_unequip_single_erc721_item_in_slot_on_their_subject_tokens_b unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1558,7 +1531,6 @@ def test_player_can_unequip_all_erc1155_items_in_slot_on_their_subject_tokens(se unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1668,7 +1640,6 @@ def test_player_can_unequip_some_but_not_all_erc1155_items_in_slot_on_their_subj unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1785,7 +1756,6 @@ def test_player_can_equip_an_item_and_then_replace_it_onto_their_subject_tokens_ unequippable = True self.inventory.create_slot( unequippable, - slot_type=1, slot_uri="random_uri", transaction_config={"from": self.admin}, ) diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol index 4a28d0bc..c33e23fb 100644 --- a/contracts/inventory/IInventory.sol +++ b/contracts/inventory/IInventory.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.0; struct Slot { string SlotURI; - uint256 SlotType; bool SlotIsUnequippable; uint256 SlotId; } @@ -27,14 +26,7 @@ interface IInventory { event SlotCreated( address indexed creator, uint256 indexed slot, - bool unequippable, - uint256 indexed slotType - ); - - event NewSlotTypeAdded( - address indexed creator, - uint256 indexed slotType, - string slotTypeName + bool unequippable ); event ItemMarkedAsEquippableInSlot( @@ -45,20 +37,8 @@ interface IInventory { 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, @@ -91,7 +71,6 @@ interface IInventory { function createSlot( bool unequippable, - uint256 slotType, string memory slotURI ) external returns (uint256); @@ -141,16 +120,5 @@ interface IInventory { function getSlotURI(uint256 slotId) external view returns (string memory); - function createSlotType( - uint256 slotType, - string memory slotTypeName - ) external; - - function addSlotType(uint256 slot, uint256 slotType) external; - - function getSlotType( - uint256 slotType - ) external view returns (string memory slotTypeName); - function setSlotUnequippable(bool unquippable, uint256 slotId) external; } diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index a4f6116d..4afccb92 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -36,8 +36,6 @@ library LibInventory { 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. @@ -156,7 +154,6 @@ contract InventoryFacet is function createSlot( bool unequippable, - uint256 slotType, string memory slotURI ) external onlyAdmin returns (uint256) { LibInventory.InventoryStorage storage istore = LibInventory @@ -167,54 +164,15 @@ contract InventoryFacet is uint256 newSlot = istore.NumSlots; // save the slot type! istore.SlotData[newSlot] = Slot({ - SlotType: slotType, SlotURI: slotURI, SlotIsUnequippable: unequippable, SlotId: newSlot }); - emit SlotCreated(msg.sender, newSlot, unequippable, slotType); + emit SlotCreated(msg.sender, newSlot, unequippable); 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 addSlotType(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]; - } - function numSlots() external view returns (uint256) { return LibInventory.inventoryStorage().NumSlots; } From decb6626d4d1d9a0f8db82ef8b9be6e0bea1ca8d Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 20:59:24 -0700 Subject: [PATCH 15/30] Nonunequippable -> persistent This required a negation in contract `_unequip` logic. I also changed the signature of `setSlotPersistent`. There are currently no tests for `setSlotPersistent`. TODO(zomglings): Write them! --- cli/web3cli/IInventory.py | 93 +++++----------- cli/web3cli/InventoryFacet.py | 52 +++++---- cli/web3cli/inventory_events.py | 2 +- cli/web3cli/test_inventory.py | 146 ++++++++++++------------- contracts/inventory/IInventory.sol | 21 ++-- contracts/inventory/InventoryFacet.sol | 24 ++-- 6 files changed, 146 insertions(+), 192 deletions(-) diff --git a/cli/web3cli/IInventory.py b/cli/web3cli/IInventory.py index a399974c..87e0c3e2 100644 --- a/cli/web3cli/IInventory.py +++ b/cli/web3cli/IInventory.py @@ -102,9 +102,9 @@ def admin_terminus_info( self.assert_contract_is_instantiated() return self.contract.adminTerminusInfo.call(block_identifier=block_number) - def create_slot(self, unequippable: bool, slot_uri: str, transaction_config) -> Any: + def create_slot(self, persistent: bool, slot_uri: str, transaction_config) -> Any: self.assert_contract_is_instantiated() - return self.contract.createSlot(unequippable, slot_uri, transaction_config) + return self.contract.createSlot(persistent, slot_uri, transaction_config) def equip( self, @@ -150,21 +150,6 @@ def get_slot_uri( self.assert_contract_is_instantiated() return self.contract.getSlotURI.call(slot_id, block_identifier=block_number) - def init( - self, - admin_terminus_address: ChecksumAddress, - admin_terminus_pool_id: int, - subject_address: ChecksumAddress, - transaction_config, - ) -> Any: - self.assert_contract_is_instantiated() - return self.contract.init( - admin_terminus_address, - admin_terminus_pool_id, - subject_address, - transaction_config, - ) - def mark_item_as_equippable_in_slot( self, slot: int, @@ -196,19 +181,17 @@ 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_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 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 ) @@ -326,7 +309,7 @@ def handle_create_slot(args: argparse.Namespace) -> None: contract = IInventory(args.address) transaction_config = get_transaction_config(args) result = contract.create_slot( - unequippable=args.unequippable, + persistent=args.persistent, slot_uri=args.slot_uri, transaction_config=transaction_config, ) @@ -380,21 +363,6 @@ def handle_get_slot_uri(args: argparse.Namespace) -> None: print(result) -def handle_init(args: argparse.Namespace) -> None: - network.connect(args.network) - contract = IInventory(args.address) - transaction_config = get_transaction_config(args) - result = contract.init( - admin_terminus_address=args.admin_terminus_address, - admin_terminus_pool_id=args.admin_terminus_pool_id, - subject_address=args.subject_address, - transaction_config=transaction_config, - ) - print(result) - if args.verbose: - print(result.info()) - - def handle_mark_item_as_equippable_in_slot(args: argparse.Namespace) -> None: network.connect(args.network) contract = IInventory(args.address) @@ -432,13 +400,13 @@ def handle_num_slots(args: argparse.Namespace) -> None: print(result) -def handle_set_slot_unequippable(args: argparse.Namespace) -> None: +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_unequippable( - unquippable=args.unquippable, + result = contract.set_slot_persistent( slot_id=args.slot_id, + persistent=args.persistent, transaction_config=transaction_config, ) print(result) @@ -446,10 +414,10 @@ def handle_set_slot_unequippable(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 = IInventory(args.address) - result = contract.slot_is_unequippable( + result = contract.slot_is_persistent( slot_id=args.slot_id, block_number=args.block_number ) print(result) @@ -498,7 +466,7 @@ def generate_cli() -> argparse.ArgumentParser: 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 + "--persistent", required=True, help="Type: bool", type=boolean_argument_type ) create_slot_parser.add_argument( "--slot-uri", required=True, help="Type: string", type=str @@ -545,17 +513,6 @@ def generate_cli() -> argparse.ArgumentParser: ) get_slot_uri_parser.set_defaults(func=handle_get_slot_uri) - init_parser = subcommands.add_parser("init") - add_default_arguments(init_parser, True) - init_parser.add_argument( - "--admin-terminus-address", required=True, help="Type: address" - ) - init_parser.add_argument( - "--admin-terminus-pool-id", required=True, help="Type: uint256", type=int - ) - init_parser.add_argument("--subject-address", required=True, help="Type: address") - init_parser.set_defaults(func=handle_init) - mark_item_as_equippable_in_slot_parser = subcommands.add_parser( "mark-item-as-equippable-in-slot" ) @@ -603,22 +560,22 @@ def generate_cli() -> argparse.ArgumentParser: add_default_arguments(num_slots_parser, False) num_slots_parser.set_defaults(func=handle_num_slots) - 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) - 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/web3cli/InventoryFacet.py b/cli/web3cli/InventoryFacet.py index 052f9cf5..eb10898d 100644 --- a/cli/web3cli/InventoryFacet.py +++ b/cli/web3cli/InventoryFacet.py @@ -102,9 +102,9 @@ def admin_terminus_info( self.assert_contract_is_instantiated() return self.contract.adminTerminusInfo.call(block_identifier=block_number) - def create_slot(self, unequippable: bool, slot_uri: str, transaction_config) -> Any: + def create_slot(self, persistent: bool, slot_uri: str, transaction_config) -> Any: self.assert_contract_is_instantiated() - return self.contract.createSlot(unequippable, slot_uri, transaction_config) + return self.contract.createSlot(persistent, slot_uri, transaction_config) def equip( self, @@ -237,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) - 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 ) @@ -379,7 +377,7 @@ def handle_create_slot(args: argparse.Namespace) -> None: contract = InventoryFacet(args.address) transaction_config = get_transaction_config(args) result = contract.create_slot( - unequippable=args.unequippable, + persistent=args.persistent, slot_uri=args.slot_uri, transaction_config=transaction_config, ) @@ -535,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) @@ -563,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) @@ -624,7 +622,7 @@ def generate_cli() -> argparse.ArgumentParser: 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 + "--persistent", required=True, help="Type: bool", type=boolean_argument_type ) create_slot_parser.add_argument( "--slot-uri", required=True, help="Type: string", type=str @@ -785,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) @@ -805,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/web3cli/inventory_events.py b/cli/web3cli/inventory_events.py index e4209e31..22025d98 100644 --- a/cli/web3cli/inventory_events.py +++ b/cli/web3cli/inventory_events.py @@ -50,7 +50,7 @@ { "indexed": False, "internalType": "bool", - "name": "unequippable", + "name": "persistent", "type": "bool", }, ], diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index 61d7df75..13b2dd9f 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -109,19 +109,19 @@ def test_contract_address_designated_event(self): class TestAdminFlow(InventoryTestCase): - def test_admin_can_create_nonunequippable_slot(self): - unequippable = False + def test_admin_can_create_persistent_slot(self): + persistent = True num_slots_0 = self.inventory.num_slots() tx_receipt = self.inventory.create_slot( - unequippable, + 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_unequippable(num_slots_1), unequippable) + self.assertEqual(self.inventory.slot_is_persistent(num_slots_1), persistent) inventory_slot_created_events = _fetch_events_chunk( web3_client, @@ -140,23 +140,23 @@ def test_admin_can_create_nonunequippable_slot(self): num_slots_1, ) self.assertEqual( - inventory_slot_created_events[0]["args"]["unequippable"], - unequippable, + inventory_slot_created_events[0]["args"]["persistent"], + persistent, ) - def test_admin_can_create_unequippable_slot(self): - unequippable = True + def test_admin_can_create_impersistent_slot(self): + persistent = False num_slots_0 = self.inventory.num_slots() tx_receipt = self.inventory.create_slot( - unequippable, + 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_unequippable(num_slots_1), unequippable) + self.assertEqual(self.inventory.slot_is_persistent(num_slots_1), persistent) inventory_slot_created_events = _fetch_events_chunk( web3_client, @@ -175,15 +175,15 @@ def test_admin_can_create_unequippable_slot(self): num_slots_1, ) self.assertEqual( - inventory_slot_created_events[0]["args"]["unequippable"], - unequippable, + inventory_slot_created_events[0]["args"]["persistent"], + persistent, ) def test_admin_can_set_slot_uri(self): - unequippable = False + persistent = True self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -202,12 +202,12 @@ def test_admin_can_set_slot_uri(self): self.assertEqual(new_slot_uri, "some_fancy_slot_uri") def test_nonadmin_cannot_create_slot(self): - unequippable = False + persistent = True num_slots_0 = self.inventory.num_slots() with self.assertRaises(VirtualMachineError): self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.player}, ) @@ -216,9 +216,9 @@ def test_nonadmin_cannot_create_slot(self): self.assertEqual(num_slots_1, num_slots_0) def test_noadmin_cannot_set_slot_uri(self): - unequippable = False + persistent = True self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -239,9 +239,9 @@ def test_noadmin_cannot_set_slot_uri(self): def test_admin_cannot_mark_contracts_with_invalid_type_as_eligible_for_slots( self, ): - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -267,9 +267,9 @@ def test_admin_cannot_mark_contracts_with_invalid_type_as_eligible_for_slots( ) def test_admin_can_mark_erc20_tokens_as_eligible_for_slots(self): - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -323,9 +323,9 @@ def test_admin_can_mark_erc20_tokens_as_eligible_for_slots(self): ) def test_nonadmin_cannot_mark_erc20_tokens_as_eligible_for_slots(self): - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -353,9 +353,9 @@ def test_nonadmin_cannot_mark_erc20_tokens_as_eligible_for_slots(self): def test_admin_cannot_mark_erc20_tokens_as_eligible_for_slots_if_pool_id_is_nonzero( self, ): - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -381,9 +381,9 @@ def test_admin_cannot_mark_erc20_tokens_as_eligible_for_slots_if_pool_id_is_nonz ) def test_admin_can_mark_erc721_tokens_as_eligible_for_slots(self): - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -437,9 +437,9 @@ def test_admin_can_mark_erc721_tokens_as_eligible_for_slots(self): ) def test_nonadmin_cannot_mark_erc721_tokens_as_eligible_for_slots(self): - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -467,9 +467,9 @@ def test_nonadmin_cannot_mark_erc721_tokens_as_eligible_for_slots(self): def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_if_pool_id_is_nonzero( self, ): - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -497,9 +497,9 @@ def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_if_pool_id_is_non def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_with_max_amount_greater_than_1( self, ): - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -527,9 +527,9 @@ def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_with_max_amount_g def test_admin_can_mark_erc721_tokens_as_eligible_for_slots_with_max_amount_1_then_0( self, ): - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -600,10 +600,10 @@ def test_admin_can_mark_erc721_tokens_as_eligible_for_slots_with_max_amount_1_th ) def test_admin_can_mark_erc1155_tokens_as_eligible_for_slots(self): - # Testing with non-unequippable slot. - unequippable = False + # Testing with non-persistent slot. + persistent = True self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -659,9 +659,9 @@ def test_admin_can_mark_erc1155_tokens_as_eligible_for_slots(self): ) def test_nonadmin_cannot_mark_erc1155_tokens_as_eligible_for_slots(self): - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -700,9 +700,9 @@ def test_player_can_equip_erc20_items_onto_their_subject_tokens(self): ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -778,9 +778,9 @@ def test_player_cannot_equip_too_many_erc20_items_onto_their_subject_tokens(self ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -826,9 +826,9 @@ def test_player_can_equip_erc721_items_onto_their_subject_tokens(self): ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -905,9 +905,9 @@ def test_player_cannot_equip_erc721_items_they_own_onto_subject_tokens_they_do_n ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -950,9 +950,9 @@ def test_player_cannot_equip_erc721_items_which_they_do_not_own(self): ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1000,9 +1000,9 @@ def test_player_can_equip_erc1155_items_onto_their_subject_tokens(self): ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1087,9 +1087,9 @@ def test_player_cannot_equip_too_many_erc1155_items_onto_their_subject_tokens(se ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1137,14 +1137,14 @@ def test_player_can_unequip_all_erc20_items_in_slot_on_their_subject_tokens(self ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) slot = self.inventory.num_slots() - self.assertTrue(self.inventory.slot_is_unequippable(slot)) + 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( @@ -1234,14 +1234,14 @@ def test_player_can_unequip_some_but_not_all_erc20_items_in_slot_on_their_subjec ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) slot = self.inventory.num_slots() - self.assertTrue(self.inventory.slot_is_unequippable(slot)) + 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( @@ -1331,14 +1331,14 @@ def test_player_can_unequip_all_erc721_items_in_slot_on_their_subject_tokens(sel ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) slot = self.inventory.num_slots() - self.assertTrue(self.inventory.slot_is_unequippable(slot)) + self.assertFalse(self.inventory.slot_is_persistent(slot)) # Set ERC721 token as equippable in slot self.inventory.mark_item_as_equippable_in_slot( @@ -1429,14 +1429,14 @@ def test_player_can_unequip_single_erc721_item_in_slot_on_their_subject_tokens_b ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) slot = self.inventory.num_slots() - self.assertTrue(self.inventory.slot_is_unequippable(slot)) + self.assertFalse(self.inventory.slot_is_persistent(slot)) # Set ERC721 token as equippable in slot self.inventory.mark_item_as_equippable_in_slot( @@ -1528,9 +1528,9 @@ def test_player_can_unequip_all_erc1155_items_in_slot_on_their_subject_tokens(se ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1637,9 +1637,9 @@ def test_player_can_unequip_some_but_not_all_erc1155_items_in_slot_on_their_subj ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) @@ -1753,9 +1753,9 @@ def test_player_can_equip_an_item_and_then_replace_it_onto_their_subject_tokens_ ) # Create inventory slot - unequippable = True + persistent = False self.inventory.create_slot( - unequippable, + persistent, slot_uri="random_uri", transaction_config={"from": self.admin}, ) diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol index c33e23fb..e86978b3 100644 --- a/contracts/inventory/IInventory.sol +++ b/contracts/inventory/IInventory.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; struct Slot { string SlotURI; - bool SlotIsUnequippable; + bool SlotIsPersistent; uint256 SlotId; } @@ -26,7 +26,7 @@ interface IInventory { event SlotCreated( address indexed creator, uint256 indexed slot, - bool unequippable + bool persistent ); event ItemMarkedAsEquippableInSlot( @@ -59,25 +59,21 @@ interface IInventory { address unequippedBy ); - function init( - address adminTerminusAddress, - uint256 adminTerminusPoolId, - address subjectAddress - ) external; - function adminTerminusInfo() external view returns (address, uint256); function subject() external view returns (address); + // Cosntraint: Admin function createSlot( - bool unequippable, + bool persistent, string memory slotURI ) external returns (uint256); function numSlots() external view returns (uint256); - function slotIsUnequippable(uint256 slotId) external view returns (bool); + function slotIsPersistent(uint256 slotId) external view returns (bool); + // Cosntraint: Admin function markItemAsEquippableInSlot( uint256 slot, uint256 itemType, @@ -93,6 +89,7 @@ interface IInventory { uint256 itemPoolId ) external view returns (uint256); + // Constraint: Non-reentrant. function equip( uint256 subjectTokenId, uint256 slot, @@ -102,6 +99,7 @@ interface IInventory { uint256 amount ) external; + // Constraint: Non-reentrant. function unequip( uint256 subjectTokenId, uint256 slot, @@ -120,5 +118,6 @@ interface IInventory { function getSlotURI(uint256 slotId) external view returns (string memory); - function setSlotUnequippable(bool unquippable, uint256 slotId) external; + // Cosntraint: Admin + function setSlotPersistent(uint256 slotId, bool persistent) external; } diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index 4afccb92..fee551dc 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -92,7 +92,7 @@ Player flow: - [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 unequippable slots +- [x] Unequip items from persistent slots Batch endpoints: - [ ] Marking items as equippable @@ -153,7 +153,7 @@ contract InventoryFacet is } function createSlot( - bool unequippable, + bool persistent, string memory slotURI ) external onlyAdmin returns (uint256) { LibInventory.InventoryStorage storage istore = LibInventory @@ -165,11 +165,11 @@ contract InventoryFacet is // save the slot type! istore.SlotData[newSlot] = Slot({ SlotURI: slotURI, - SlotIsUnequippable: unequippable, + SlotIsPersistent: persistent, SlotId: newSlot }); - emit SlotCreated(msg.sender, newSlot, unequippable); + emit SlotCreated(msg.sender, newSlot, persistent); return newSlot; } @@ -203,20 +203,20 @@ contract InventoryFacet is 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(); Slot memory slot = istore.SlotData[slotId]; - slot.SlotIsUnequippable = unquippable; + slot.SlotIsPersistent = persistent; istore.SlotData[slotId] = slot; } @@ -296,8 +296,8 @@ 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." ); EquippedItem storage existingItem = istore.EquippedItems[ From 23aeaad64de9b8b250ea0694645ae6834b54356c Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 21:03:30 -0700 Subject: [PATCH 16/30] Removed "SlotID" field from `Slot` struct Unnecessary. --- contracts/inventory/IInventory.sol | 1 - contracts/inventory/InventoryFacet.sol | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol index e86978b3..45dcf235 100644 --- a/contracts/inventory/IInventory.sol +++ b/contracts/inventory/IInventory.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.0; struct Slot { string SlotURI; bool SlotIsPersistent; - uint256 SlotId; } // EquippedItem represents an item equipped in a specific inventory slot for a specific ERC721 token. diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index fee551dc..1e1e7041 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -34,7 +34,7 @@ library LibInventory { uint256 AdminTerminusPoolId; address ContractERC721Address; uint256 NumSlots; - // SlotId => slot, useful to get the rest of the slot data. + // 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 @@ -165,8 +165,7 @@ contract InventoryFacet is // save the slot type! istore.SlotData[newSlot] = Slot({ SlotURI: slotURI, - SlotIsPersistent: persistent, - SlotId: newSlot + SlotIsPersistent: persistent }); emit SlotCreated(msg.sender, newSlot, persistent); From 7ef0128940b6b280569923461c4759fc1c03c361 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 21:10:48 -0700 Subject: [PATCH 17/30] Changed indexing on `SlotCreated` events No need to index the slot itself - that will only be emitted once. We are now indexing the persistence. --- cli/web3cli/inventory_events.py | 4 ++-- contracts/inventory/IInventory.sol | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cli/web3cli/inventory_events.py b/cli/web3cli/inventory_events.py index 22025d98..0ca764d1 100644 --- a/cli/web3cli/inventory_events.py +++ b/cli/web3cli/inventory_events.py @@ -42,13 +42,13 @@ "type": "address", }, { - "indexed": True, + "indexed": False, "internalType": "uint256", "name": "slot", "type": "uint256", }, { - "indexed": False, + "indexed": True, "internalType": "bool", "name": "persistent", "type": "bool", diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol index 45dcf235..dba906ad 100644 --- a/contracts/inventory/IInventory.sol +++ b/contracts/inventory/IInventory.sol @@ -14,6 +14,14 @@ struct EquippedItem { uint256 Amount; } +// Interface ID: baca2357 +// +// 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 { event AdministratorDesignated( address indexed adminTerminusAddress, @@ -24,8 +32,8 @@ interface IInventory { event SlotCreated( address indexed creator, - uint256 indexed slot, - bool persistent + uint256 slot, + bool indexed persistent ); event ItemMarkedAsEquippableInSlot( From 11323bcf907c9d1153de59895e4f631481167a02 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 22:38:50 -0700 Subject: [PATCH 18/30] Added `NewSlotPersistence` event Also, unindexed `persistent` from `SlotCreated` event. --- cli/web3cli/IInventory.py | 28 +++++++++++++++ cli/web3cli/InventoryFacet.py | 2 +- cli/web3cli/inventory_events.py | 2 +- contracts/inventory/IInventory.sol | 47 ++++++++++++++------------ contracts/inventory/InventoryFacet.sol | 25 ++++++++------ 5 files changed, 70 insertions(+), 34 deletions(-) diff --git a/cli/web3cli/IInventory.py b/cli/web3cli/IInventory.py index 87e0c3e2..efdbd70e 100644 --- a/cli/web3cli/IInventory.py +++ b/cli/web3cli/IInventory.py @@ -187,6 +187,10 @@ def set_slot_persistent( 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: @@ -414,6 +418,20 @@ def handle_set_slot_persistent(args: argparse.Namespace) -> None: 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) @@ -570,6 +588,16 @@ def generate_cli() -> argparse.ArgumentParser: ) 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( diff --git a/cli/web3cli/InventoryFacet.py b/cli/web3cli/InventoryFacet.py index eb10898d..a50be9b4 100644 --- a/cli/web3cli/InventoryFacet.py +++ b/cli/web3cli/InventoryFacet.py @@ -245,7 +245,7 @@ def set_slot_persistent( 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_persistent( self, slot_id: int, block_number: Optional[Union[str, int]] = "latest" diff --git a/cli/web3cli/inventory_events.py b/cli/web3cli/inventory_events.py index 0ca764d1..7420c3b4 100644 --- a/cli/web3cli/inventory_events.py +++ b/cli/web3cli/inventory_events.py @@ -48,7 +48,7 @@ "type": "uint256", }, { - "indexed": True, + "indexed": False, "internalType": "bool", "name": "persistent", "type": "bool", diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol index dba906ad..10342c80 100644 --- a/contracts/inventory/IInventory.sol +++ b/contracts/inventory/IInventory.sol @@ -14,7 +14,7 @@ struct EquippedItem { uint256 Amount; } -// Interface ID: baca2357 +// Interface ID: 6e34096c // // Calculated by solface: https://github.com/moonstream-to/solface // @@ -23,18 +23,9 @@ struct EquippedItem { // // Note: Change path to build/contracts/IInventory.json depending on where you are relative to the repo root. interface IInventory { - event AdministratorDesignated( - address indexed adminTerminusAddress, - uint256 indexed adminTerminusPoolId - ); - event ContractAddressDesignated(address indexed contractAddress); - event SlotCreated( - address indexed creator, - uint256 slot, - bool indexed persistent - ); + event SlotCreated(address indexed creator, uint256 slot, bool persistent); event ItemMarkedAsEquippableInSlot( uint256 indexed slot, @@ -46,6 +37,8 @@ interface IInventory { event NewSlotURI(uint256 indexed slotId); + event NewSlotPersistence(uint256 indexed slotId, bool persistent); + event ItemEquipped( uint256 indexed subjectTokenId, uint256 indexed slot, @@ -70,7 +63,8 @@ interface IInventory { function subject() external view returns (address); - // Cosntraint: Admin + // Constraint: Admin + // Emits: SlotCreated, NewSlotURI, NewSlotPersistence function createSlot( bool persistent, string memory slotURI @@ -78,9 +72,24 @@ interface IInventory { 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); - // Cosntraint: Admin + // 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, @@ -97,6 +106,8 @@ interface IInventory { ) 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, @@ -107,6 +118,7 @@ interface IInventory { ) external; // Constraint: Non-reentrant. + // Emits: ItemUnequipped function unequip( uint256 subjectTokenId, uint256 slot, @@ -118,13 +130,4 @@ interface IInventory { uint256 subjectTokenId, uint256 slot ) external view returns (EquippedItem memory item); - - function getSlotById( - uint256 slotId - ) external view returns (Slot memory slots); - - function getSlotURI(uint256 slotId) external view returns (string memory); - - // Cosntraint: Admin - function setSlotPersistent(uint256 slotId, bool persistent) external; } diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index 1e1e7041..67b33ca2 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -106,6 +106,11 @@ contract InventoryFacet is TerminusPermissions, DiamondReentrancyGuard { + event AdministratorDesignated( + address indexed adminTerminusAddress, + uint256 indexed adminTerminusPoolId + ); + modifier onlyAdmin() { LibInventory.InventoryStorage storage istore = LibInventory .inventoryStorage(); @@ -189,7 +194,7 @@ contract InventoryFacet is return istore.SlotData[slotId].SlotURI; } - function setSlotUri( + function setSlotURI( string memory newSlotURI, uint slotId ) external onlyAdmin { @@ -453,6 +458,15 @@ contract InventoryFacet is ); } + istore.EquippedItems[istore.ContractERC721Address][subjectTokenId][ + slot + ] = EquippedItem({ + ItemType: itemType, + ItemAddress: itemAddress, + ItemTokenId: itemTokenId, + Amount: amount + }); + emit ItemEquipped( subjectTokenId, slot, @@ -462,15 +476,6 @@ contract InventoryFacet is amount, msg.sender ); - - istore.EquippedItems[istore.ContractERC721Address][subjectTokenId][ - slot - ] = EquippedItem({ - ItemType: itemType, - ItemAddress: itemAddress, - ItemTokenId: itemTokenId, - Amount: amount - }); } function unequip( From a693c866a8101db2649416edcf6e85b1b456de03 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 22:46:31 -0700 Subject: [PATCH 19/30] `createSlot` fires `NewSlotURI`, `NewSlotPersistence` Removed `persistent` argument from the `SlotCreated` event - it is duplicated in the `NewSlotPersistence` emission. --- cli/web3cli/inventory_events.py | 6 ------ cli/web3cli/test_inventory.py | 10 ++-------- contracts/inventory/IInventory.sol | 4 +++- contracts/inventory/InventoryFacet.sol | 4 +++- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/cli/web3cli/inventory_events.py b/cli/web3cli/inventory_events.py index 7420c3b4..34d249ad 100644 --- a/cli/web3cli/inventory_events.py +++ b/cli/web3cli/inventory_events.py @@ -47,12 +47,6 @@ "name": "slot", "type": "uint256", }, - { - "indexed": False, - "internalType": "bool", - "name": "persistent", - "type": "bool", - }, ], "name": "SlotCreated", "type": "event", diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index 13b2dd9f..75adf2a5 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -139,10 +139,7 @@ def test_admin_can_create_persistent_slot(self): inventory_slot_created_events[0]["args"]["slot"], num_slots_1, ) - self.assertEqual( - inventory_slot_created_events[0]["args"]["persistent"], - persistent, - ) + # TODO(zomglings): Add tests for NewSlotURI, NewSlotPersistence def test_admin_can_create_impersistent_slot(self): persistent = False @@ -174,10 +171,7 @@ def test_admin_can_create_impersistent_slot(self): inventory_slot_created_events[0]["args"]["slot"], num_slots_1, ) - self.assertEqual( - inventory_slot_created_events[0]["args"]["persistent"], - persistent, - ) + # TODO(zomglings): Add tests for NewSlotURI, NewSlotPersistence def test_admin_can_set_slot_uri(self): persistent = True diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol index 10342c80..80aa1311 100644 --- a/contracts/inventory/IInventory.sol +++ b/contracts/inventory/IInventory.sol @@ -23,9 +23,11 @@ struct EquippedItem { // // 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 ContractAddressDesignated(address indexed contractAddress); - event SlotCreated(address indexed creator, uint256 slot, bool persistent); + event SlotCreated(address indexed creator, uint256 slot); event ItemMarkedAsEquippableInSlot( uint256 indexed slot, diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index 67b33ca2..d9f8b600 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -173,7 +173,9 @@ contract InventoryFacet is SlotIsPersistent: persistent }); - emit SlotCreated(msg.sender, newSlot, persistent); + emit SlotCreated(msg.sender, newSlot); + emit NewSlotURI(newSlot); + emit NewSlotPersistence(newSlot, persistent); return newSlot; } From c331b01fc5500e5dc416b66e8e8a87b5f70e1038 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 22:48:53 -0700 Subject: [PATCH 20/30] IInventory - license --- contracts/inventory/IInventory.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol index 80aa1311..5f74d5f5 100644 --- a/contracts/inventory/IInventory.sol +++ b/contracts/inventory/IInventory.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; struct Slot { From 9046a23edc0de43af35361a06be7bbb2640bd0b6 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 22:54:46 -0700 Subject: [PATCH 21/30] `ContractAddressDesignated` -> `NewSubjectAddress` --- cli/web3cli/inventory_events.py | 4 ++-- cli/web3cli/test_inventory.py | 2 +- contracts/inventory/IInventory.sol | 2 +- contracts/inventory/InventoryFacet.sol | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/web3cli/inventory_events.py b/cli/web3cli/inventory_events.py index 34d249ad..b89f3fda 100644 --- a/cli/web3cli/inventory_events.py +++ b/cli/web3cli/inventory_events.py @@ -18,7 +18,7 @@ "type": "event", } -CONTRACT_ADDRESS_DESIGNATED_ABI = { +NEW_SUBJECT_ADDRESS_ABI = { "anonymous": False, "inputs": [ { @@ -28,7 +28,7 @@ "type": "address", } ], - "name": "ContractAddressDesignated", + "name": "NewSubjectAddress", "type": "event", } diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index 75adf2a5..270d83bb 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -96,7 +96,7 @@ def test_subject_erc721_address(self): def test_contract_address_designated_event(self): contract_address_designated_events = _fetch_events_chunk( web3_client, - inventory_events.CONTRACT_ADDRESS_DESIGNATED_ABI, + inventory_events.NEW_SUBJECT_ADDRESS_ABI, self.predeployment_block, self.postdeployment_block, ) diff --git a/contracts/inventory/IInventory.sol b/contracts/inventory/IInventory.sol index 5f74d5f5..e8e6a9fc 100644 --- a/contracts/inventory/IInventory.sol +++ b/contracts/inventory/IInventory.sol @@ -25,7 +25,7 @@ struct EquippedItem { interface IInventory { // This event should be emitted when the subject ERC721 contract address is set (or changes) on the // Inventory contract. - event ContractAddressDesignated(address indexed contractAddress); + event NewSubjectAddress(address indexed contractAddress); event SlotCreated(address indexed creator, uint256 slot); diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index d9f8b600..82818c86 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -144,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) { From 0c134ffbf8e2d0061095f6dbbb340ee5aa1ab994 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 23:02:47 -0700 Subject: [PATCH 22/30] Added tests for NewSlotURI and NewSlotPersistence... ... emission from `createSlot`. --- cli/web3cli/inventory_events.py | 34 ++++++++++++++++++++++++ cli/web3cli/test_inventory.py | 46 ++++++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/cli/web3cli/inventory_events.py b/cli/web3cli/inventory_events.py index b89f3fda..ab31fc63 100644 --- a/cli/web3cli/inventory_events.py +++ b/cli/web3cli/inventory_events.py @@ -174,3 +174,37 @@ "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/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index 270d83bb..550a1ec8 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -129,7 +129,6 @@ def test_admin_can_create_persistent_slot(self): 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"], @@ -139,7 +138,27 @@ def test_admin_can_create_persistent_slot(self): inventory_slot_created_events[0]["args"]["slot"], num_slots_1, ) - # TODO(zomglings): Add tests for NewSlotURI, NewSlotPersistence + + 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 @@ -161,7 +180,6 @@ def test_admin_can_create_impersistent_slot(self): 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"], @@ -171,7 +189,27 @@ def test_admin_can_create_impersistent_slot(self): inventory_slot_created_events[0]["args"]["slot"], num_slots_1, ) - # TODO(zomglings): Add tests for NewSlotURI, NewSlotPersistence + + 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 From b35520481f9822d00c765ba576027c7653325c42 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 23:14:05 -0700 Subject: [PATCH 23/30] Factored Inventory deployment into separate... class method in `InventoryTestCase`. This will make it easier to test other Inventory implementations in the future. --- cli/web3cli/test_inventory.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index 550a1ec8..df2494f4 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -12,6 +12,19 @@ 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: @@ -54,16 +67,9 @@ def setUpClass(cls) -> None: ) cls.predeployment_block = len(chain) - cls.deployed_contracts = inventory_gogogo( - cls.terminus.address, - cls.admin_terminus_pool_id, - cls.nft.address, - cls.owner_tx_config, - ) + inventory_address = cls.deploy_inventory() + cls.inventory = InventoryFacet.InventoryFacet(inventory_address) cls.postdeployment_block = len(chain) - cls.inventory = InventoryFacet.InventoryFacet( - cls.deployed_contracts["contracts"]["Diamond"] - ) class InventorySetupTests(InventoryTestCase): @@ -247,7 +253,7 @@ def test_nonadmin_cannot_create_slot(self): self.assertEqual(num_slots_1, num_slots_0) - def test_noadmin_cannot_set_slot_uri(self): + def test_nonadmin_cannot_set_slot_uri(self): persistent = True self.inventory.create_slot( persistent, From fb0b3d29338b7be16c21effc648e927c9663d8d5 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 23:28:46 -0700 Subject: [PATCH 24/30] Tests for `setSlotPersistent` Also added `NewSlotPersistence` event emission from that method. --- cli/web3cli/test_inventory.py | 110 ++++++++++++++++++++++++- contracts/inventory/InventoryFacet.sol | 2 + 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index df2494f4..5b4df9a0 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -228,7 +228,7 @@ def test_admin_can_set_slot_uri(self): num_slots_1 = self.inventory.num_slots() # set the slot uri - self.inventory.set_slot_uri( + tx_receipt = self.inventory.set_slot_uri( "some_fancy_slot_uri", num_slots_1, transaction_config={"from": self.admin}, @@ -239,6 +239,15 @@ def test_admin_can_set_slot_uri(self): # 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 @@ -726,6 +735,105 @@ def test_nonadmin_cannot_mark_erc1155_tokens_as_eligible_for_slots(self): 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): diff --git a/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index 82818c86..d4e5d29e 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -224,6 +224,8 @@ contract InventoryFacet is Slot memory slot = istore.SlotData[slotId]; slot.SlotIsPersistent = persistent; istore.SlotData[slotId] = slot; + + emit NewSlotPersistence(slotId, persistent); } function markItemAsEquippableInSlot( From fafb329ec6823f82f4b563e3d81e5872ab383914 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Sun, 30 Jul 2023 23:56:07 -0700 Subject: [PATCH 25/30] Added test for unequipping from persistent slot This is only one of the tests we need to write for this behavior --- .github/workflows/chainlink.yml | 70 +++++++++---------- .github/workflows/crafting.yml | 76 ++++++++++----------- .github/workflows/dropper.yml | 72 ++++++++++---------- .github/workflows/gofp.yml | 76 ++++++++++----------- .github/workflows/inventory.yml | 41 ++++++++++++ .github/workflows/lootbox.yml | 84 +++++++++++------------ .github/workflows/reentrancy-guard.yml | 76 ++++++++++----------- .github/workflows/sdk.release.yml | 44 ++++++------ cli/web3cli/test_inventory.py | 93 ++++++++++++++++++++++++++ contracts/inventory/InventoryFacet.sol | 4 ++ 10 files changed, 387 insertions(+), 249 deletions(-) create mode 100644 .github/workflows/inventory.yml diff --git a/.github/workflows/chainlink.yml b/.github/workflows/chainlink.yml index fc95f0c9..d0ac4c58 100644 --- a/.github/workflows/chainlink.yml +++ b/.github/workflows/chainlink.yml @@ -1,39 +1,39 @@ 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/ + 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..6c0c371a 100644 --- a/.github/workflows/crafting.yml +++ b/.github/workflows/crafting.yml @@ -1,42 +1,42 @@ 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/ + 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..d5542d5c 100644 --- a/.github/workflows/dropper.yml +++ b/.github/workflows/dropper.yml @@ -1,40 +1,40 @@ 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/ + 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..55d7c0dd 100644 --- a/.github/workflows/gofp.yml +++ b/.github/workflows/gofp.yml @@ -1,42 +1,42 @@ 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/ + 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..f4f36808 --- /dev/null +++ b/.github/workflows/inventory.yml @@ -0,0 +1,41 @@ +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/ + 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..6afa5a0c 100644 --- a/.github/workflows/lootbox.yml +++ b/.github/workflows/lootbox.yml @@ -1,46 +1,46 @@ 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/ + 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..a0f9fdcd 100644 --- a/.github/workflows/reentrancy-guard.yml +++ b/.github/workflows/reentrancy-guard.yml @@ -1,42 +1,42 @@ 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/ + 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/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index 5b4df9a0..e988a8b9 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -2070,3 +2070,96 @@ def test_player_can_equip_an_item_and_then_replace_it_onto_their_subject_tokens_ item_unequipped_events[0]["args"]["unequippedBy"], self.player.address, ) + + def test_player_cannot_unequip_erc20_tokens_from_persistent_slot_but_can_increase_amount(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: + + 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/contracts/inventory/InventoryFacet.sol b/contracts/inventory/InventoryFacet.sol index d4e5d29e..39c1ae15 100644 --- a/contracts/inventory/InventoryFacet.sol +++ b/contracts/inventory/InventoryFacet.sol @@ -405,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] From 2a1eff23e411f97436c84ac2e7ca5588ae98faa5 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Mon, 31 Jul 2023 00:01:14 -0700 Subject: [PATCH 26/30] BROWNIE_LIB=1 --- .github/workflows/chainlink.yml | 2 ++ .github/workflows/crafting.yml | 2 ++ .github/workflows/dropper.yml | 2 ++ .github/workflows/gofp.yml | 2 ++ .github/workflows/inventory.yml | 2 ++ .github/workflows/lootbox.yml | 2 ++ .github/workflows/reentrancy-guard.yml | 2 ++ 7 files changed, 14 insertions(+) diff --git a/.github/workflows/chainlink.yml b/.github/workflows/chainlink.yml index d0ac4c58..fc7f8161 100644 --- a/.github/workflows/chainlink.yml +++ b/.github/workflows/chainlink.yml @@ -32,6 +32,8 @@ jobs: pip install black moonworm - name: Install dependencies for CLI working-directory: cli/ + env: + BROWNIE_LIB: 1 run: | pip install -e . - name: Run tests diff --git a/.github/workflows/crafting.yml b/.github/workflows/crafting.yml index 6c0c371a..b239006e 100644 --- a/.github/workflows/crafting.yml +++ b/.github/workflows/crafting.yml @@ -35,6 +35,8 @@ jobs: pip install black moonworm - name: Install dependencies for CLI working-directory: cli/ + env: + BROWNIE_LIB: 1 run: | pip install -e . - name: Run tests diff --git a/.github/workflows/dropper.yml b/.github/workflows/dropper.yml index d5542d5c..90f4a312 100644 --- a/.github/workflows/dropper.yml +++ b/.github/workflows/dropper.yml @@ -33,6 +33,8 @@ jobs: pip install black moonworm - name: Install dependencies for CLI working-directory: cli/ + env: + BROWNIE_LIB: 1 run: | pip install -e . - name: Run tests diff --git a/.github/workflows/gofp.yml b/.github/workflows/gofp.yml index 55d7c0dd..7d981bd4 100644 --- a/.github/workflows/gofp.yml +++ b/.github/workflows/gofp.yml @@ -35,6 +35,8 @@ jobs: pip install black moonworm - name: Install dependencies for CLI working-directory: cli/ + env: + BROWNIE_LIB: 1 run: | pip install -e . - name: Run tests diff --git a/.github/workflows/inventory.yml b/.github/workflows/inventory.yml index f4f36808..55dcf0eb 100644 --- a/.github/workflows/inventory.yml +++ b/.github/workflows/inventory.yml @@ -34,6 +34,8 @@ jobs: pip install black moonworm - name: Install dependencies for CLI working-directory: cli/ + env: + BROWNIE_LIB: 1 run: | pip install -e . - name: Run tests diff --git a/.github/workflows/lootbox.yml b/.github/workflows/lootbox.yml index 6afa5a0c..feaea0a4 100644 --- a/.github/workflows/lootbox.yml +++ b/.github/workflows/lootbox.yml @@ -36,6 +36,8 @@ jobs: pip install black moonworm - name: Install dependencies for CLI working-directory: cli/ + env: + BROWNIE_LIB: 1 run: | pip install -e . - name: Run lootbox tests diff --git a/.github/workflows/reentrancy-guard.yml b/.github/workflows/reentrancy-guard.yml index a0f9fdcd..b8c3245b 100644 --- a/.github/workflows/reentrancy-guard.yml +++ b/.github/workflows/reentrancy-guard.yml @@ -35,6 +35,8 @@ jobs: pip install black moonworm - name: Install dependencies for CLI working-directory: cli/ + env: + BROWNIE_LIB: 1 run: | pip install -e . - name: Run tests From dd38fe084553a662933b600f782cd6a798dd7ca0 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Mon, 31 Jul 2023 00:07:35 -0700 Subject: [PATCH 27/30] Updated chainlink version in brownie-config.yaml --- brownie-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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" From e2f90f981fe5a2b673dcad81507df83d555e4f2b Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 1 Aug 2023 13:55:05 -0700 Subject: [PATCH 28/30] Fixed two tests based on @kellan-simiotics feedback I was using an ERC20 token to test ERC721 behavior in the inventory. The tests asserted a `VirtualMachineError`, which could have been triggered by the incorrect use of an ERC20 token as an ERC721 token. --- cli/web3cli/test_inventory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index e988a8b9..6def9161 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -528,7 +528,7 @@ def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_if_pool_id_is_non self.inventory.mark_item_as_equippable_in_slot( slot, erc721_type, - self.payment_token.address, + self.nft.address, 1, 1, {"from": self.admin}, @@ -558,7 +558,7 @@ def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_with_max_amount_g self.inventory.mark_item_as_equippable_in_slot( slot, erc721_type, - self.payment_token.address, + self.nft.address, 0, 2, {"from": self.admin}, From f38985fd59824932c349d4bbd9cede7b9151d5a3 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 1 Aug 2023 13:57:58 -0700 Subject: [PATCH 29/30] Renamed test as per @kellan-simiotics suggestion --- cli/web3cli/test_inventory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index 6def9161..d875ce8a 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -2071,7 +2071,7 @@ def test_player_can_equip_an_item_and_then_replace_it_onto_their_subject_tokens_ self.player.address, ) - def test_player_cannot_unequip_erc20_tokens_from_persistent_slot_but_can_increase_amount(self): + 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. @@ -2142,7 +2142,7 @@ def test_player_cannot_unequip_erc20_tokens_from_persistent_slot_but_can_increas 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: + should be extended with the following code (and should be renamed): self.inventory.equip( subject_token_id, From 5405532d9c22a6067219acd240db15d545850415 Mon Sep 17 00:00:00 2001 From: Neeraj Kashyap Date: Tue, 1 Aug 2023 14:53:56 -0700 Subject: [PATCH 30/30] Fixed (thank you @kellan-simiotics again) --- cli/web3cli/test_inventory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/web3cli/test_inventory.py b/cli/web3cli/test_inventory.py index d875ce8a..d47eb236 100644 --- a/cli/web3cli/test_inventory.py +++ b/cli/web3cli/test_inventory.py @@ -528,7 +528,7 @@ def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_if_pool_id_is_non self.inventory.mark_item_as_equippable_in_slot( slot, erc721_type, - self.nft.address, + self.item_nft.address, 1, 1, {"from": self.admin}, @@ -558,7 +558,7 @@ def test_admin_cannot_mark_erc721_tokens_as_eligible_for_slots_with_max_amount_g self.inventory.mark_item_as_equippable_in_slot( slot, erc721_type, - self.nft.address, + self.item_nft.address, 0, 2, {"from": self.admin},