diff --git a/contracts/Aavegotchi/facets/ItemsFacet.sol b/contracts/Aavegotchi/facets/ItemsFacet.sol index 111b1da2..7e6e6e2a 100644 --- a/contracts/Aavegotchi/facets/ItemsFacet.sol +++ b/contracts/Aavegotchi/facets/ItemsFacet.sol @@ -183,7 +183,7 @@ contract ItemsFacet is Modifiers { function equipWearables( uint256 _tokenId, uint16[EQUIPPED_WEARABLE_SLOTS] calldata _wearablesToEquip - ) onlyAavegotchiOwner(_tokenId) onlyUnlocked(_tokenId) external { + ) onlyAavegotchiOwner(_tokenId) onlyUnlocked(_tokenId) public { uint256[EQUIPPED_WEARABLE_SLOTS] memory _depositIds; _equipWearables(_tokenId, _wearablesToEquip, _depositIds); } @@ -199,10 +199,44 @@ contract ItemsFacet is Modifiers { uint256 _tokenId, uint16[EQUIPPED_WEARABLE_SLOTS] calldata _wearablesToEquip, uint256[EQUIPPED_WEARABLE_SLOTS] calldata _depositIds - ) onlyAavegotchiOwner(_tokenId) onlyUnlocked(_tokenId) external { + ) onlyAavegotchiOwner(_tokenId) onlyUnlocked(_tokenId) public { _equipWearables(_tokenId, _wearablesToEquip, _depositIds); } + ///@notice Allow the owner of a claimed aavegotchi to equip/unequip wearables to his aavegotchis in batch + ///@dev Arrays in _wearablesToEquip need to be the same length as _tokenIds + ///@dev _wearablesToEquip are equiped to aavegotchis in the order of _tokenIds + ///@param _tokenIds Array containing the identifiers of the aavegotchis to make changes to + ///@param _wearablesToEquip An array of arrays containing the identifiers of the wearables to equip for aavegotchi in _tokenIds + function batchEquipWearables( + uint256[] calldata _tokenIds, + uint16[EQUIPPED_WEARABLE_SLOTS][] calldata _wearablesToEquip + ) external { + require(_wearablesToEquip.length == _tokenIds.length, "ItemsFacet: _wearablesToEquip length not same as _tokenIds length"); + for (uint256 i = 0; i < _tokenIds.length; i++) { + equipWearables(_tokenIds[i], _wearablesToEquip[i]); + } + } + + ///@notice Allow the owner of a claimed aavegotchi to equip/unequip wearables to his aavegotchis in batch + ///@dev Arrays in _wearablesToEquip need to be the same length as _tokenIds + ///@dev _wearablesToEquip are equiped to aavegotchis in the order of _tokenIds + ///@dev _depositIds are equiped to aavegotchis in the order of _tokenIds + ///@param _tokenIds Array containing the identifiers of the aavegotchis to make changes to + ///@param _wearablesToEquip An array of arrays containing the identifiers of the wearables to equip for aavegotchis in _tokenIds + ///@param _depositIds An array of arrays containing the identifiers of the deposited wearables to equip for aavegotchis in _tokenIds + function batchEquipDelegatedWearables( + uint256[] calldata _tokenIds, + uint16[EQUIPPED_WEARABLE_SLOTS][] calldata _wearablesToEquip, + uint256[EQUIPPED_WEARABLE_SLOTS][] calldata _depositIds + ) external { + require(_wearablesToEquip.length == _tokenIds.length, "ItemsFacet: _wearablesToEquip length not same as _tokenIds length"); + require(_depositIds.length == _tokenIds.length, "ItemsFacet: _depositIds length not same as _tokenIds length"); + for (uint256 i = 0; i < _tokenIds.length; i++) { + equipDelegatedWearables(_tokenIds[i], _wearablesToEquip[i], _depositIds[i]); + } + } + function _equipWearables( uint256 _tokenId, uint16[EQUIPPED_WEARABLE_SLOTS] calldata _wearablesToEquip, diff --git a/contracts/Aavegotchi/facets/WearablesConfigFacet.sol b/contracts/Aavegotchi/facets/WearablesConfigFacet.sol new file mode 100644 index 00000000..2c9c4fd6 --- /dev/null +++ b/contracts/Aavegotchi/facets/WearablesConfigFacet.sol @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.1; + +import {LibMeta} from "../../shared/libraries/LibMeta.sol"; +import {Modifiers, WearablesConfig, EQUIPPED_WEARABLE_SLOTS} from "../libraries/LibAppStorage.sol"; +import {LibAavegotchi} from "../libraries/LibAavegotchi.sol"; +import {LibWearablesConfig} from "../libraries/LibWearablesConfig.sol"; + +contract WearablesConfigFacet is Modifiers { + + // constants + uint16 public constant WEARABLESCONFIG_MAX_SLOTS = 2**16 - 1; + uint16 public constant WEARABLESCONFIG_FREE_SLOTS = 3; + uint256 public constant WEARABLESCONFIG_SLOT_PRICE = 1000000000000000000; // 1 GHST + uint256 public constant WEARABLESCONFIG_OWNER_FEE = 100000000000000000; // 0.1 GHST + + // events + event WearablesConfigCreated(address indexed owner, uint256 indexed tokenId, uint16 indexed wearablesConfigId, uint16[EQUIPPED_WEARABLE_SLOTS] wearables, uint256 value); + event WearablesConfigUpdated(address indexed owner, uint256 indexed tokenId, uint16 indexed wearablesConfigId, uint16[EQUIPPED_WEARABLE_SLOTS] wearables); + event WearablesConfigDaoPaymentReceived(address indexed owner, uint256 indexed tokenId, uint16 indexed wearablesConfigId, uint256 value); + event WearablesConfigOwnerPaymentReceived(address indexed sender, address indexed owner, uint256 indexed tokenId, uint16 wearablesConfigId, uint256 value); + + /// @notice Creates and stores a new wearables configuration (max 65535 per Aavegotchi per owner). + /// @notice First three slots are free, the rest are paid. + /// @notice To create a wearables config for someone else aavegotchi there is a fee + /// @notice We support wearables config creation for unbridged gotchis (config owner is set to sender) + /// @param _tokenId The ID of the aavegotchi to create the wearables configuration for. + /// @param _name The name of the wearables configuration. + /// @param _wearablesToStore The wearables to store for this wearables configuration. + /// @return wearablesConfigId The ID of the newly created wearables configuration. + function createWearablesConfig( + uint256 _tokenId, + string calldata _name, + uint16[EQUIPPED_WEARABLE_SLOTS] calldata _wearablesToStore + ) + external + payable + returns (uint16 wearablesConfigId) + { + // check that creation of this wearables config is allowed (only aavegotchi or unbridged) + require(LibWearablesConfig._checkAavegotchiOrUnbridged(_tokenId), "WearablesConfigFacet: Not allowed to create wearables config"); + // check that wearables are valid and for the right slots + require(LibWearablesConfig._checkValidWearables(_wearablesToStore), "WearablesConfigFacet: Invalid wearables"); + // check that name is not empty + require(bytes(_name).length > 0, "WearablesConfigFacet: WearablesConfig name cannot be blank"); + + address sender = LibMeta.msgSender(); + address owner = s.aavegotchis[_tokenId].owner; + bool paidslot; + bool notowner; + uint256 fee; + + if (owner == address(0)) { + // set the owner to the sender for unbridged gotchis + owner = sender; + } + + // get the next available slot + wearablesConfigId = LibWearablesConfig._getNextWearablesConfigId(owner, _tokenId); + // solidity will throw if slots used overflows + require(wearablesConfigId < WEARABLESCONFIG_MAX_SLOTS, "WearablesConfigFacet: No more wearables config slots available"); + + // if the owner has reached the free slots limit then they need to pay for the extra slot + if (wearablesConfigId >= WEARABLESCONFIG_FREE_SLOTS) { + paidslot = true; + fee += WEARABLESCONFIG_SLOT_PRICE; + } + + // if the sender is not the owner and the gotchi has been bridged + // then they need to pay a fee to the owner + if (sender != owner) { + notowner = true; + fee += WEARABLESCONFIG_OWNER_FEE; + } + + if (fee > 0) { + require(msg.value == fee, "WearablesConfigFacet: Incorrect GHST value sent"); + + if (paidslot) { + // send GHST to the dao treasury + (bool success, ) = payable(s.daoTreasury).call{value: WEARABLESCONFIG_SLOT_PRICE}(""); + require(success, "WearablesConfigFacet: Failed to send GHST to DAO treasury"); + + emit WearablesConfigDaoPaymentReceived(owner, _tokenId, wearablesConfigId, WEARABLESCONFIG_SLOT_PRICE); + } + + if (notowner) { + // send GHST to the owner + (bool success, ) = payable(owner).call{value: WEARABLESCONFIG_OWNER_FEE}(""); + require(success, "WearablesConfigFacet: Failed to send GHST to owner"); + + emit WearablesConfigOwnerPaymentReceived(sender, owner, _tokenId, wearablesConfigId, WEARABLESCONFIG_OWNER_FEE); + } + } + + // create the new wearables config and add it to the gotchi for that owner + WearablesConfig memory wearablesConfig = WearablesConfig({name: _name, wearables: _wearablesToStore}); + s.gotchiWearableConfigs[_tokenId][owner].push(wearablesConfig); + s.ownerGotchiSlotsUsed[owner][_tokenId] += 1; + + emit WearablesConfigCreated(owner, _tokenId, wearablesConfigId, _wearablesToStore, msg.value); + } + + /// @notice Updates the wearables config for the given wearables config id + /// @param _tokenId The ID of the aavegotchi to update the wearables configuration for + /// @param _wearablesConfigId The ID of the wearables configuration to update + /// @param _name The name of the wearables configuration + /// @param _wearablesToStore The wearables to store + /// @dev if _name is empty, only wearables are updated. + function updateWearablesConfig( + uint256 _tokenId, + uint16 _wearablesConfigId, + string calldata _name, + uint16[EQUIPPED_WEARABLE_SLOTS] calldata _wearablesToStore + ) + external payable + { + // check that update of this wearables config is allowed (only aavegotchi or unbridged) + require(LibWearablesConfig._checkAavegotchiOrUnbridged(_tokenId), "WearablesConfigFacet: Not allowed to update wearables config"); + // check that wearables are valid and for the right slots + require(LibWearablesConfig._checkValidWearables(_wearablesToStore), "WearablesConfigFacet: Invalid wearables"); + + address sender = LibMeta.msgSender(); + address owner = s.aavegotchis[_tokenId].owner; + + if (owner == address(0)) { + // save the wearables config under the sender for unbridged aavegotchis + owner = sender; + } else { + // make sure that the sender is also the owner of this aavegotchi + require(sender == owner, "WearablesConfigFacet: Only the owner can update wearables config"); + } + + // make sure we are updating an existing wearables config + require(LibWearablesConfig._wearablesConfigExists(owner, _tokenId, _wearablesConfigId), "WearablesConfigFacet: invalid id, WearablesConfig not found"); + + // skip if name is empty + if (bytes(_name).length > 0) { + s.gotchiWearableConfigs[_tokenId][owner][_wearablesConfigId].name = _name; + } + + // update the wearables + s.gotchiWearableConfigs[_tokenId][owner][_wearablesConfigId].wearables = _wearablesToStore; + + emit WearablesConfigUpdated(owner, _tokenId, _wearablesConfigId, _wearablesToStore); + } + + /// @notice Returns true if the given wearables config id exists for the given aavegotchi + /// @param _owner The owner of the aavegotchi + /// @param _tokenId The ID of the aavegotchi to update the wearables configuration for + /// @param _wearablesConfigId The ID of the wearables configuration to update + /// @return exists true if the wearables config exists + function wearablesConfigExists(address _owner, uint256 _tokenId, uint16 _wearablesConfigId) external view returns (bool exists) { + exists = LibWearablesConfig._wearablesConfigExists(_owner, _tokenId, _wearablesConfigId); + } + + /// @notice Returns the wearables config for the given wearables config id + /// @param _owner The owner of the aavegotchi + /// @param _tokenId The ID of the aavegotchi to update the wearables configuration for + /// @param _wearablesConfigId The ID of the wearables configuration to update + /// @return wearablesConfig The wearables config + function getWearablesConfig(address _owner, uint256 _tokenId, uint16 _wearablesConfigId) external view returns (WearablesConfig memory wearablesConfig) { + require(LibWearablesConfig._wearablesConfigExists(_owner, _tokenId, _wearablesConfigId), "WearablesConfigFacet: invalid id, WearablesConfig not found"); + return s.gotchiWearableConfigs[_tokenId][_owner][_wearablesConfigId]; + } + + /// @notice Returns the name of the wearables config for the given wearables config id + /// @param _owner The owner of the aavegotchi + /// @param _tokenId The ID of the aavegotchi to update the wearables configuration for + /// @param _wearablesConfigId The ID of the wearables configuration to update + /// @return name The name of the wearables config + function getWearablesConfigName(address _owner, uint256 _tokenId, uint16 _wearablesConfigId) external view returns (string memory name) { + require(LibWearablesConfig._wearablesConfigExists(_owner, _tokenId, _wearablesConfigId), "WearablesConfigFacet: invalid id, WearablesConfig not found"); + name = s.gotchiWearableConfigs[_tokenId][_owner][_wearablesConfigId].name; + } + + /// @notice Returns the wearables for the given wearables config id + /// @param _owner The owner of the aavegotchi + /// @param _tokenId The ID of the aavegotchi to update the wearables configuration for + /// @param _wearablesConfigId The ID of the wearables configuration to update + /// @return wearables The wearables stored for this wearables config + function getWearablesConfigWearables(address _owner, uint256 _tokenId, uint16 _wearablesConfigId) external view returns (uint16[EQUIPPED_WEARABLE_SLOTS] memory wearables) { + require(LibWearablesConfig._wearablesConfigExists(_owner, _tokenId, _wearablesConfigId), "WearablesConfigFacet: invalid id, WearablesConfig not found"); + wearables = s.gotchiWearableConfigs[_tokenId][_owner][_wearablesConfigId].wearables; + } + + /// @notice Returns the number of wearables configs for the given aavegotchi for this owner + /// @param _owner The owner of the aavegotchi + /// @param _tokenId The ID of the aavegotchi to update the wearables configuration for + /// @return slotsUsed The number of wearables configs + function getAavegotchiWearablesConfigCount(address _owner, uint256 _tokenId) external view returns (uint16 slotsUsed) { + slotsUsed = s.ownerGotchiSlotsUsed[_owner][_tokenId]; + } +} diff --git a/contracts/Aavegotchi/libraries/LibAppStorage.sol b/contracts/Aavegotchi/libraries/LibAppStorage.sol index 09172bd9..a5f3e304 100644 --- a/contracts/Aavegotchi/libraries/LibAppStorage.sol +++ b/contracts/Aavegotchi/libraries/LibAppStorage.sol @@ -252,6 +252,11 @@ struct ERC1155BuyOrder { bool completed; } +struct WearablesConfig { + string name; + uint16[EQUIPPED_WEARABLE_SLOTS] wearables; +} + struct AppStorage { mapping(address => AavegotchiCollateralTypeInfo) collateralTypeInfo; mapping(address => uint256) collateralTypeIndexes; @@ -387,6 +392,10 @@ struct AppStorage { mapping(uint256 => ERC1155BuyOrder) erc1155BuyOrders; // buyOrderId => data address gotchGeistBridge; address itemGeistBridge; + // gotchi => owner => wearable configs + mapping(uint256 => mapping(address => WearablesConfig[])) gotchiWearableConfigs; + // owner => gotchi => slots used + mapping(address => mapping (uint256 => uint16)) ownerGotchiSlotsUsed; } library LibAppStorage { diff --git a/contracts/Aavegotchi/libraries/LibWearablesConfig.sol b/contracts/Aavegotchi/libraries/LibWearablesConfig.sol new file mode 100644 index 00000000..274828c6 --- /dev/null +++ b/contracts/Aavegotchi/libraries/LibWearablesConfig.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.1; + +import {LibMeta} from "../../shared/libraries/LibMeta.sol"; +import {LibAavegotchi} from "../libraries/LibAavegotchi.sol"; +import {LibItems} from "../libraries/LibItems.sol"; +import {LibAppStorage, AppStorage, WearablesConfig, ItemType, EQUIPPED_WEARABLE_SLOTS} from "../libraries/LibAppStorage.sol"; + +library LibWearablesConfig { + + /// @notice Returns true only if the given tokenId is a valid aavegotchi or unbridged + /// @param _tokenId The tokenId of the aavegotchi + /// @return result True if the tokenId is valid or unbridged + function _checkAavegotchiOrUnbridged(uint256 _tokenId) internal view returns (bool result) { + AppStorage storage s = LibAppStorage.diamondStorage(); + if (s.aavegotchis[_tokenId].status == LibAavegotchi.STATUS_AAVEGOTCHI) { + result = true; + // Unbridged aavegotchis do not have a owner or a haunt set + } else if (s.aavegotchis[_tokenId].hauntId == 0 && s.aavegotchis[_tokenId].owner == address(0)) { + // Only allow unbridged aavegotchis up to the current supply + uint256 maxSupply; + for (uint256 i = 1; i <= s.currentHauntId; i++) { + maxSupply += s.haunts[i].hauntMaxSize; + } + require(_tokenId < maxSupply, "LibWearablesConfig: Invalid tokenId for unbridged aavegotchi"); + + result = true; + } + } + + /// @notice Returns the next wearables config id for that gotchi given that owner + /// @param _owner The owner of the gotchi + /// @param _tokenId The tokenId of the gotchi + /// @return nextWearablesConfigId The next free wearables config id + function _getNextWearablesConfigId(address _owner, uint256 _tokenId) internal view returns (uint16 nextWearablesConfigId) { + AppStorage storage s = LibAppStorage.diamondStorage(); + // slots start at 0 so slotsUsed is always the next config id + nextWearablesConfigId = s.ownerGotchiSlotsUsed[_owner][_tokenId]; + } + + /// @notice Checks if a wearables config exists for a gotchi given an owner + /// @param _owner The owner of the gotchi + /// @param _tokenId The tokenId of the gotchi + /// @param _wearablesConfigId The wearables config id + /// @return exists True if the wearables config exists false otherwise + function _wearablesConfigExists(address _owner, uint256 _tokenId, uint16 _wearablesConfigId) internal view returns (bool exists) { + AppStorage storage s = LibAppStorage.diamondStorage(); + // slots start at 0 so slots used should always be greater by 1 than the last config id + exists = (s.ownerGotchiSlotsUsed[_owner][_tokenId] > _wearablesConfigId); + } + + /// @notice Checks if a wearables configuration consist of valid wearables and are for the correct slot + /// @param _wearablesToStore The wearables to store + /// @return valid True if the wearables configuration is valid and false otherwise + function _checkValidWearables(uint16[EQUIPPED_WEARABLE_SLOTS] memory _wearablesToStore) internal view returns (bool) { + AppStorage storage s = LibAppStorage.diamondStorage(); + uint256 itemTypesLength = s.itemTypes.length; + bool valid = true; + for (uint256 slot; slot < EQUIPPED_WEARABLE_SLOTS; slot++) { + uint256 toStoreId = _wearablesToStore[slot]; + if (toStoreId != 0) { + require(itemTypesLength > toStoreId, "LibWearablesConfig: Item type does not exist"); + ItemType storage itemType = s.itemTypes[toStoreId]; + if (itemType.category != LibItems.ITEM_CATEGORY_WEARABLE) { + valid = false; + break; + } + if (itemType.slotPositions[slot] == false) { + valid = false; + break; + } + } + } + return valid; + } +} diff --git a/hardhat.config.ts b/hardhat.config.ts index b8cb1b8e..30e8cf8a 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -65,10 +65,11 @@ export default { }, networks: { hardhat: { + allowUnlimitedContractSize: true, // for testing forking: { url: process.env.GEIST_URL, // timeout: 12000000, - blockNumber: 1743308, + blockNumber: 2523699, }, blockGasLimit: 20000000, timeout: 120000, diff --git a/scripts/deployFullDiamond.ts b/scripts/deployFullDiamond.ts index 886650cf..4e764f6e 100644 --- a/scripts/deployFullDiamond.ts +++ b/scripts/deployFullDiamond.ts @@ -773,7 +773,7 @@ export async function deployFullDiamond(useFreshDeploy: boolean = false) { diamondName: "AavegotchiDiamond", initDiamond: "contracts/Aavegotchi/InitDiamond.sol:InitDiamond", facetNames: [ - "contracts/Aavegotchi/facets/BridgeFacet.sol:BridgeFacet", + //"contracts/Aavegotchi/facets/BridgeFacet.sol:BridgeFacet", "contracts/Aavegotchi/facets/AavegotchiFacet.sol:AavegotchiFacet", "AavegotchiGameFacet", "SvgFacet", @@ -799,6 +799,7 @@ export async function deployFullDiamond(useFreshDeploy: boolean = false) { "ItemsRolesRegistryFacet", "ERC1155BuyOrderFacet", "PolygonXGeistBridgeFacet", + "WearablesConfigFacet", ], owner: ownerAddress, args: initArgs, diff --git a/scripts/upgrades/upgrade-itemsFacet.ts b/scripts/upgrades/upgrade-itemsFacet.ts new file mode 100644 index 00000000..9672af43 --- /dev/null +++ b/scripts/upgrades/upgrade-itemsFacet.ts @@ -0,0 +1,45 @@ +import { ethers, run } from "hardhat"; +import { + convertFacetAndSelectorsToString, + DeployUpgradeTaskArgs, + FacetsAndAddSelectors, +} from "../../tasks/deployUpgrade"; +import { loadDeploymentConfig } from "../deployFullDiamond"; + +export async function upgrade() { + const facets: FacetsAndAddSelectors[] = [ + { + facetName: "contracts/Aavegotchi/facets/ItemsFacet.sol:ItemsFacet", + addSelectors: [ + "function batchEquipWearables(uint256[] _tokenIds, uint16[16][] _wearablesToEquip)", + "function batchEquipDelegatedWearables(uint256[] _tokenIds, uint16[16][] _wearablesToEquip, uint256[16][] _depositIds)", + ], + removeSelectors: [], + }, + ]; + + const deploymentConfig = loadDeploymentConfig(63157); + const diamondAddress = deploymentConfig.aavegotchiDiamond as string; + const diamondOwner = deploymentConfig.itemManagers[0] as string; + + const joined = convertFacetAndSelectorsToString(facets); + + const args: DeployUpgradeTaskArgs = { + diamondAddress: diamondAddress, + diamondOwner: diamondOwner, + facetsAndAddSelectors: joined, + useLedger: false, + useMultisig: false, + }; + + await run("deployUpgrade", args); +} + +if (require.main === module) { + upgrade() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); +} diff --git a/scripts/upgrades/upgrade-wearablesConfigFacet.ts b/scripts/upgrades/upgrade-wearablesConfigFacet.ts new file mode 100644 index 00000000..6bc3168f --- /dev/null +++ b/scripts/upgrades/upgrade-wearablesConfigFacet.ts @@ -0,0 +1,54 @@ +import { ethers, run } from "hardhat"; +import { + convertFacetAndSelectorsToString, + DeployUpgradeTaskArgs, + FacetsAndAddSelectors, +} from "../../tasks/deployUpgrade"; +import { loadDeploymentConfig } from "../deployFullDiamond"; + +export async function upgrade() { + const facets: FacetsAndAddSelectors[] = [ + { + facetName: "WearablesConfigFacet", + addSelectors: [ + 'function createWearablesConfig(uint256 _tokenId, string _name, uint16[16] _wearablesToStore) external payable', + 'function updateWearablesConfig(uint256 _tokenId, uint16 _wearablesConfigId, string _name, uint16[16] _wearablesToStore) external', + 'function getWearablesConfig(address _owner, uint256 _tokenId, uint16 _wearablesConfigId) external view', + 'function getWearablesConfigName(address _owner, uint256 _tokenId, uint16 _wearablesConfigId) external view', + 'function getWearablesConfigWearables(address _owner, uint256 _tokenId, uint16 _wearablesConfigId) external view', + 'function getAavegotchiWearablesConfigCount(address _owner, uint256 _tokenId) external view', + 'function wearablesConfigExists(address _owner, uint256 _tokenId, uint16 _wearablesConfigId) external view', + ], + removeSelectors: [], + }, + ]; + + const deploymentConfig = loadDeploymentConfig(63157); + const diamondAddress = deploymentConfig.aavegotchiDiamond as string; + const diamondOwnerAddress = deploymentConfig.itemManagers[0] as string; + + const joined = convertFacetAndSelectorsToString(facets); + + const args: DeployUpgradeTaskArgs = { + diamondAddress: diamondAddress, + diamondOwner: diamondOwnerAddress, + facetsAndAddSelectors: joined, + useLedger: false, + useMultisig: false, + freshDeployment: true, + // initAddress + // initCalldata + }; + + await run("deployUpgrade", args); +} + +if (require.main === module) { + upgrade() + .then(() => process.exit(0)) + // .then(() => console.log('upgrade completed') /* process.exit(0) */) + .catch((error) => { + console.error(error); + process.exit(1); + }); +} diff --git a/test/itemsFacetBatchEquip.ts b/test/itemsFacetBatchEquip.ts new file mode 100644 index 00000000..e607daf4 --- /dev/null +++ b/test/itemsFacetBatchEquip.ts @@ -0,0 +1,335 @@ +/* global describe it before ethers network */ +/* eslint prefer-const: "off" */ + +//@ts-ignore +import { ethers, network } from "hardhat"; +import chai from "chai"; +import { upgrade } from "../scripts/upgrades/upgrade-itemsFacet"; +import { impersonate, resetChain } from "../scripts/helperFunctions"; +import { + AavegotchiFacet, + WearablesFacet, + ItemsFacet, + ItemsRolesRegistryFacet, +} from "../typechain"; +import { loadDeploymentConfig } from "../scripts/deployFullDiamond"; +import * as helpers from "@nomicfoundation/hardhat-network-helpers"; +import { + buildCommitment, + buildGrantRole, +} from "./ItemsRolesRegistryFacet/helpers"; + +const { expect } = chai; + +describe("Testing Batch Equip Wearables", async function () { + this.timeout(300000); + + const deploymentConfig = loadDeploymentConfig(63157); + const diamondAddress = deploymentConfig.aavegotchiDiamond as string; + const wearablesDiamondAddress = deploymentConfig.wearableDiamond as string; + const wearables = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const otherWearables = [0, 0, 0, 0, 205, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const wearablesWithInvalidId = [ + 418, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + const wearablesWithInvalidSlot = [ + 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + const wearablesWithRentals = [ + 0, 0, 0, 1, 205, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + const depositIdsOfWearableRentals = [ + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + const emptyWearables = new Array(16).fill(0); + const aavegotchiId = 15748; + const anotherOwnerAavegotchiId = 19488; + const anotherWearablesOwner = "0xC3c2e1Cf099Bc6e1fA94ce358562BCbD5cc59FE5"; + const nullAddress = ethers.constants.AddressZero; + + let aavegotchiOwnerAddress: any; + let anotherAavegotchiOwnerAddress: any; + let gotchisOfOwner: number[]; + let aavegotchiFacet: AavegotchiFacet; + let wearablesFacet: WearablesFacet; + let itemsRolesRegistryFacet: ItemsRolesRegistryFacet; + let aavegotchiFacetWithOwner: AavegotchiFacet; + let itemsFacetWithOwner: ItemsFacet; + let wearablesFacetWithOwner: WearablesFacet; + let itemsFacetWithOtherOwner: ItemsFacet; + let itemsRolesRegistryFacetWithOwner: Contract; + + before(async function () { + //await resetChain(hre); + + // workaround for issue https://github.com/NomicFoundation/hardhat/issues/5511 + //await helpers.mine() + + await upgrade(); + + aavegotchiFacet = (await ethers.getContractAt( + "contracts/Aavegotchi/facets/AavegotchiFacet.sol:AavegotchiFacet", + diamondAddress, + )) as AavegotchiFacet; + + wearablesFacet = (await ethers.getContractAt( + "contracts/Aavegotchi/WearableDiamond/facets/WearablesFacet.sol:WearablesFacet", + wearablesDiamondAddress, + )) as WearablesFacet; + + itemsRolesRegistryFacet = await ethers.getContractAt( + "ItemsRolesRegistryFacet", + diamondAddress, + ); + + let itemsFacet = (await ethers.getContractAt( + "contracts/Aavegotchi/facets/ItemsFacet.sol:ItemsFacet", + diamondAddress, + )) as ItemsFacet; + + // get owners of gotchis + aavegotchiOwnerAddress = await aavegotchiFacet.ownerOf(aavegotchiId); + anotherAavegotchiOwnerAddress = await aavegotchiFacet.ownerOf( + anotherOwnerAavegotchiId, + ); + + // impersonate signers + let aavegotchiFacetWithOtherSigner: AavegotchiFacet = await impersonate( + anotherAavegotchiOwnerAddress, + aavegotchiFacet, + ethers, + network, + ); + let wearablesFacetWithOtherSigner: WearablesFacet = await impersonate( + anotherWearablesOwner, + wearablesFacet, + ethers, + network, + ); + wearablesFacetWithOwner = await impersonate( + aavegotchiOwnerAddress, + wearablesFacet, + ethers, + network, + ); + itemsFacetWithOwner = await impersonate( + aavegotchiOwnerAddress, + itemsFacet, + ethers, + network, + ); + itemsFacetWithOtherOwner = await impersonate( + anotherAavegotchiOwnerAddress, + itemsFacet, + ethers, + network, + ); + aavegotchiFacetWithOwner = await impersonate( + aavegotchiOwnerAddress, + aavegotchiFacet, + ethers, + network, + ); + itemsRolesRegistryFacetWithOwner = await impersonate( + aavegotchiOwnerAddress, + itemsRolesRegistryFacet, + ethers, + network, + ); + + // transfer gotchi and wearable for tests + await aavegotchiFacetWithOtherSigner.transferFrom( + anotherAavegotchiOwnerAddress, + aavegotchiOwnerAddress, + anotherOwnerAavegotchiId, + ); + await wearablesFacetWithOtherSigner.safeTransferFrom( + anotherWearablesOwner, + aavegotchiOwnerAddress, + 205, + 1, + "0x", + ); + + // get gotchis of owner + gotchisOfOwner = await aavegotchiFacet.tokenIdsOfOwner( + aavegotchiOwnerAddress, + ); + + // equip transfered wearable on transeferd gotchi + await itemsFacetWithOwner.equipWearables(gotchisOfOwner[1], otherWearables); + }); + + describe("Testing batch functions to equip wearables", async function () { + async function getWearables(_tokenId: number): number[] { + const currentWearables = + await itemsFacetWithOwner.equippedWearables(_tokenId); + return currentWearables; + } + + it("Should unequip all wearables from multiple gotchis", async function () { + await itemsFacetWithOwner.batchEquipWearables( + [gotchisOfOwner[0], gotchisOfOwner[1]], + [emptyWearables, emptyWearables], + ); + expect(await getWearables(gotchisOfOwner[0])).to.deep.equal( + emptyWearables, + ); + expect(await getWearables(gotchisOfOwner[1])).to.deep.equal( + emptyWearables, + ); + + // reset for next test + await itemsFacetWithOwner.equipWearables(gotchisOfOwner[0], wearables); + await itemsFacetWithOwner.equipWearables( + gotchisOfOwner[1], + otherWearables, + ); + }); + it("Should be able to unequip and requip the same gotchi", async function () { + await itemsFacetWithOwner.batchEquipWearables( + [aavegotchiId, aavegotchiId], + [emptyWearables, wearables], + ); + expect(await getWearables(aavegotchiId)).to.deep.equal(wearables); + }); + it("Should be able to unequip from one gotchi to equip on the next", async function () { + await itemsFacetWithOwner.batchEquipWearables( + [gotchisOfOwner[0], gotchisOfOwner[1]], + [emptyWearables, wearables], + ); + expect(await getWearables(gotchisOfOwner[0])).to.deep.equal( + emptyWearables, + ); + expect(await getWearables(gotchisOfOwner[1])).to.deep.equal(wearables); + + // reequip for next test (inversed) + await itemsFacetWithOwner.equipWearables( + gotchisOfOwner[0], + otherWearables, + ); + }); + it("Should be able to completely flip wearables between two gotchis", async function () { + const firstGotchiId = gotchisOfOwner[0]; + const secondGotchiId = gotchisOfOwner[1]; + const firstGotchiWearables = await getWearables(firstGotchiId); + const secondGotchiWearables = await getWearables(secondGotchiId); + await itemsFacetWithOwner.batchEquipWearables( + [firstGotchiId, secondGotchiId, firstGotchiId, secondGotchiId], + [ + emptyWearables, + emptyWearables, + secondGotchiWearables, + firstGotchiWearables, + ], + ); + expect(await getWearables(firstGotchiId)).to.deep.equal( + secondGotchiWearables, + ); + expect(await getWearables(secondGotchiId)).to.deep.equal( + firstGotchiWearables, + ); + + // unequip to free wearables for next test + await itemsFacetWithOwner.equipWearables( + gotchisOfOwner[0], + emptyWearables, + ); + + // retransfer to other owner for next test + await aavegotchiFacetWithOwner.transferFrom( + aavegotchiOwnerAddress, + anotherAavegotchiOwnerAddress, + anotherOwnerAavegotchiId, + ); + }); + it("Should unequip and requip wearables with rentals ", async function () { + let CommitmentCreated: Commitment; + let GrantRoleData: GrantRoleData; + let depositIdsCounter = 0; + + // rent a wearable + CommitmentCreated = buildCommitment({ + grantor: aavegotchiOwnerAddress, + tokenAddress: wearablesDiamondAddress, + tokenId: 1, + }); + + depositIdsCounter = Number( + ( + await itemsRolesRegistryFacet + .connect(aavegotchiOwnerAddress) + .callStatic.commitTokens( + CommitmentCreated.grantor, + CommitmentCreated.tokenAddress, + CommitmentCreated.tokenId, + CommitmentCreated.tokenAmount, + ) + ).toString(), + ); + + GrantRoleData = await buildGrantRole({ + depositId: depositIdsCounter, + grantee: anotherAavegotchiOwnerAddress, + }); + + await wearablesFacetWithOwner.setApprovalForAll( + itemsRolesRegistryFacet.address, + true, + ); + + await itemsRolesRegistryFacetWithOwner.commitTokens( + CommitmentCreated.grantor, + CommitmentCreated.tokenAddress, + CommitmentCreated.tokenId, + CommitmentCreated.tokenAmount, + ); + await itemsRolesRegistryFacetWithOwner.grantRole( + GrantRoleData.depositId, + GrantRoleData.role, + GrantRoleData.grantee, + GrantRoleData.expirationDate, + GrantRoleData.revocable, + GrantRoleData.data, + ); + + // unequip and reequip with rental + await itemsFacetWithOtherOwner.batchEquipDelegatedWearables( + [anotherOwnerAavegotchiId, anotherOwnerAavegotchiId], + [emptyWearables, wearablesWithRentals], + [emptyWearables, depositIdsOfWearableRentals], + ); + expect(await getWearables(anotherOwnerAavegotchiId)).to.deep.equal( + wearablesWithRentals, + ); + }); + it("Should revert if arguments are not all the same length ", async function () { + await expect( + itemsFacetWithOwner.batchEquipWearables( + [gotchisOfOwner[0], gotchisOfOwner[1]], + [emptyWearables], + ), + ).to.be.revertedWith( + "ItemsFacet: _wearablesToEquip length not same as _tokenIds length", + ); + await expect( + itemsFacetWithOtherOwner.batchEquipDelegatedWearables( + [anotherOwnerAavegotchiId, anotherOwnerAavegotchiId], + [emptyWearables], + [emptyWearables, emptyWearables], + ), + ).to.be.revertedWith( + "ItemsFacet: _wearablesToEquip length not same as _tokenIds length", + ); + await expect( + itemsFacetWithOtherOwner.batchEquipDelegatedWearables( + [aavegotchiId, aavegotchiId], + [emptyWearables, emptyWearables], + [emptyWearables], + ), + ).to.be.revertedWith( + "ItemsFacet: _depositIds length not same as _tokenIds length", + ); + }); + }); +}); diff --git a/test/wearablesConfigTest.ts b/test/wearablesConfigTest.ts new file mode 100644 index 00000000..c22708c5 --- /dev/null +++ b/test/wearablesConfigTest.ts @@ -0,0 +1,747 @@ +/* global describe it before ethers network */ +/* eslint prefer-const: "off" */ + +//@ts-ignore +import { ethers, network } from "hardhat"; +import { BigNumber, BigNumberish } from "ethers"; +import chai from "chai"; +import { upgrade } from "../scripts/upgrades/upgrade-wearablesConfigFacet"; +import { impersonate, resetChain } from "../scripts/helperFunctions"; +import { + AavegotchiFacet, + WearablesConfigFacet, + GotchiLendingFacet, + LendingGetterAndSetterFacet, +} from "../typechain"; +import * as helpers from "@nomicfoundation/hardhat-network-helpers"; +import { loadDeploymentConfig } from "../scripts/deployFullDiamond"; + +const { expect } = chai; + +describe("Testing Wearables Config", async function () { + this.timeout(300000); + + const deploymentConfig = loadDeploymentConfig(63157); + const diamondAddress = deploymentConfig.aavegotchiDiamond as string; + const diamondOwner = deploymentConfig.itemManagers[0] as string; + + const slotPrice = ethers.utils.parseUnits("1", "ether"); + const wearablesToStore = [ + 105, 209, 159, 104, 106, 65, 413, 210, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + const wearablesToStoreWithInvalidId = [ + 418, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + const wearablesToStoreWithInvalidSlot = [ + 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + const aavegotchiId = 15748; + const unsummonedAavegotchiId = 1463; + const someoneElseAavegotchiId = 19488; + + let aavegotchiOwnerAddress: any; + let anotherAavegotchiOwnerAddress: any; + let daoAddress: any; + let aavegotchiFacet: AavegotchiFacet; + let lendingGetterFacet: LendingGetterAndSetterFacet; + let aavegotchiFacetWithOwner: AavegotchiFacet; + let wearablesConfigFacetWithOwner: WearablesConfigFacet; + let wearablesConfigFacetWithOtherOwner: WearablesConfigFacet; + let gotchiLendingFacetWithOwner: GotchiLendingFacet; + let gotchiLendingFacetWithOtherOwner: GotchiLendingFacet; + + before(async function () { + //await resetChain(hre); + // workaround for issue https://github.com/NomicFoundation/hardhat/issues/5511 + await helpers.mine(); + + await upgrade(); + + aavegotchiFacet = (await ethers.getContractAt( + "contracts/Aavegotchi/facets/AavegotchiFacet.sol:AavegotchiFacet", + diamondAddress, + )) as AavegotchiFacet; + + lendingGetterFacet = await ethers.getContractAt( + "LendingGetterAndSetterFacet", + diamondAddress, + ); + + aavegotchiOwnerAddress = await aavegotchiFacet.ownerOf(aavegotchiId); + anotherAavegotchiOwnerAddress = + "0xC3c2e1Cf099Bc6e1fA94ce358562BCbD5cc59FE5"; + + //const accounts = await ethers.getSigners(); + //const ownerAddress = await accounts[0].getAddress(); + //daoAddress = ownerAddress; + daoAddress = diamondOwner; // Geist fresh deployment + + const gotchiLendingFacet = (await ethers.getContractAt( + "GotchiLendingFacet", + diamondAddress, + )) as GotchiLendingFacet; + + const wearablesConfigFacet = (await ethers.getContractAt( + "WearablesConfigFacet", + diamondAddress, + )) as WearablesConfigFacet; + + aavegotchiFacetWithOwner = await impersonate( + anotherAavegotchiOwnerAddress, + aavegotchiFacet, + ethers, + network, + ); + + wearablesConfigFacetWithOwner = await impersonate( + aavegotchiOwnerAddress, + wearablesConfigFacet, + ethers, + network, + ); + + wearablesConfigFacetWithOtherOwner = await impersonate( + anotherAavegotchiOwnerAddress, + wearablesConfigFacet, + ethers, + network, + ); + + gotchiLendingFacetWithOwner = await impersonate( + aavegotchiOwnerAddress, + gotchiLendingFacet, + ethers, + network, + ); + + gotchiLendingFacetWithOtherOwner = await impersonate( + anotherAavegotchiOwnerAddress, + gotchiLendingFacet, + ethers, + network, + ); + + await aavegotchiFacetWithOwner.transferFrom( + anotherAavegotchiOwnerAddress, + aavegotchiOwnerAddress, + unsummonedAavegotchiId, + ); + }); + + describe("Testing createWearablesConfig", async function () { + it("Should revert if wearables list is invalid", async function () { + const invalidWearables = new Array(16).fill(1); + await expect( + wearablesConfigFacetWithOwner.createWearablesConfig( + aavegotchiId, + "Test", + invalidWearables, + ), + ).to.be.revertedWith("WearablesConfigFacet: Invalid wearables"); + }); + it("Should revert if wearablesConfig name is empty", async function () { + await expect( + wearablesConfigFacetWithOwner.createWearablesConfig( + aavegotchiId, + "", + wearablesToStore, + ), + ).to.be.revertedWith( + "WearablesConfigFacet: WearablesConfig name cannot be blank", + ); + }); + it("Should succeed to create wearablesConfig if all parameters are valid and emit event", async function () { + const receipt = await // config #1 (id: 0) + ( + await wearablesConfigFacetWithOwner.createWearablesConfig( + aavegotchiId, + "Test", + wearablesToStore, + ) + ).wait(); + // check that event is emitted with the right parameters + const event = receipt!.events!.find( + (event) => event.event === "WearablesConfigCreated", + ); + const owner = event!.args!.owner; + const tokenId = event!.args!.tokenId; + const wearablesConfigId = event!.args!.wearablesConfigId; + const wearables = event!.args!.wearables; + const value = event!.args!.value; + expect(owner).to.equal(aavegotchiOwnerAddress); + expect(tokenId).to.equal(aavegotchiId); + expect(wearablesConfigId).to.equal(0); + expect(wearables).to.eql(wearablesToStore); + expect(value).to.equal(0); + }); + }); + + describe("Testing getWearablesConfigName", async function () { + it("Should revert if invalid wearablesConfig id", async function () { + await expect( + wearablesConfigFacetWithOwner.getWearablesConfigName( + aavegotchiOwnerAddress, + aavegotchiId, + 99, + ), + ).to.be.revertedWith( + "WearablesConfigFacet: invalid id, WearablesConfig not found", + ); + }); + it("Should revert if invalid aavegotchi id for owner", async function () { + await expect( + wearablesConfigFacetWithOwner.getWearablesConfigName( + aavegotchiOwnerAddress, + someoneElseAavegotchiId, + 0, + ), + ).to.be.revertedWith( + "WearablesConfigFacet: invalid id, WearablesConfig not found", + ); + }); + it("Should return the name of a valid wearablesConfig id", async function () { + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigName( + aavegotchiOwnerAddress, + aavegotchiId, + 0, + ), + ).to.equal("Test"); + }); + }); + + describe("Testing getWearablesConfigWearables", async function () { + it("Should revert if invalid wearablesConfig id", async function () { + await expect( + wearablesConfigFacetWithOwner.getWearablesConfigWearables( + aavegotchiOwnerAddress, + aavegotchiId, + 99, + ), + ).to.be.revertedWith( + "WearablesConfigFacet: invalid id, WearablesConfig not found", + ); + }); + it("Should revert if invalid aavegotchi id for owner", async function () { + await expect( + wearablesConfigFacetWithOwner.getWearablesConfigWearables( + aavegotchiOwnerAddress, + someoneElseAavegotchiId, + 0, + ), + ).to.be.revertedWith( + "WearablesConfigFacet: invalid id, WearablesConfig not found", + ); + }); + it("Should return the wearables array of a valid wearablesConfig id", async function () { + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigWearables( + aavegotchiOwnerAddress, + aavegotchiId, + 0, + ), + ).to.eql(wearablesToStore); + }); + }); + + describe("Testing getWearablesConfig", async function () { + it("Should revert if invalid wearablesConfig id", async function () { + await expect( + wearablesConfigFacetWithOwner.getWearablesConfig( + aavegotchiOwnerAddress, + aavegotchiId, + 99, + ), + ).to.be.revertedWith( + "WearablesConfigFacet: invalid id, WearablesConfig not found", + ); + }); + it("Should revert if invalid aavegotchi id for owner", async function () { + await expect( + wearablesConfigFacetWithOwner.getWearablesConfig( + aavegotchiOwnerAddress, + someoneElseAavegotchiId, + 0, + ), + ).to.be.revertedWith( + "WearablesConfigFacet: invalid id, WearablesConfig not found", + ); + }); + it("Should return name and wearables array if valid wearablesConfig id", async function () { + const wearablesConfig = + await wearablesConfigFacetWithOwner.getWearablesConfig( + aavegotchiOwnerAddress, + aavegotchiId, + 0, + ); + expect(wearablesConfig.name).to.equal("Test"); + expect(wearablesConfig.wearables).to.eql(wearablesToStore); + }); + }); + + describe("Testing updateWearablesConfig", async function () { + it("Should be able to update existing WearablesConfig and emit event if all parameters are valid", async function () { + const newWearablesToStore = new Array(16).fill(0); + const receipt = await ( + await wearablesConfigFacetWithOwner.updateWearablesConfig( + aavegotchiId, + 0, + "New Name", + newWearablesToStore, + ) + ).wait(); + // check that event is emitted with the right parameters + const event = receipt!.events!.find( + (event) => event.event === "WearablesConfigUpdated", + ); + const owner = event!.args!.owner; + const tokenId = event!.args!.tokenId; + const wearablesConfigId = event!.args!.wearablesConfigId; + const wearables = event!.args!.wearables; + expect(owner).to.equal(aavegotchiOwnerAddress); + expect(tokenId).to.equal(aavegotchiId); + expect(wearablesConfigId).to.equal(0); + expect(wearables).to.eql(newWearablesToStore); + // check wearablesConfig name has been updated + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigName( + aavegotchiOwnerAddress, + aavegotchiId, + 0, + ), + ).to.equal("New Name"); + // check wearablesConfig stored wearables have been updated + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigWearables( + aavegotchiOwnerAddress, + aavegotchiId, + 0, + ), + ).to.eql(newWearablesToStore); + }); + it("Should not update the name if the new name is empty", async function () { + await wearablesConfigFacetWithOwner.updateWearablesConfig( + aavegotchiId, + 0, + "", + wearablesToStore, + ); + // check wearablesConfig name has been updated + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigName( + aavegotchiOwnerAddress, + aavegotchiId, + 0, + ), + ).to.equal("New Name"); + // check wearablesConfig stored wearables have been updated + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigWearables( + aavegotchiOwnerAddress, + aavegotchiId, + 0, + ), + ).to.eql(wearablesToStore); + }); + it("Should revert if invalid wearablesConfig id", async function () { + await expect( + wearablesConfigFacetWithOwner.updateWearablesConfig( + aavegotchiId, + 99, + "Test", + wearablesToStore, + ), + ).to.be.revertedWith( + "WearablesConfigFacet: invalid id, WearablesConfig not found", + ); + }); + }); + + describe("Testing wearablesConfigExists", async function () { + it("Should return true for valid wearablesConfig", async function () { + expect( + await wearablesConfigFacetWithOwner.wearablesConfigExists( + aavegotchiOwnerAddress, + aavegotchiId, + 0, + ), + ).to.be.true; + }); + it("Should return false for invalid wearablesConfig", async function () { + expect( + await wearablesConfigFacetWithOwner.wearablesConfigExists( + aavegotchiOwnerAddress, + aavegotchiId, + 99, + ), + ).to.be.false; + }); + }); + + describe("Testing getAavegotchiWearablesConfigCount", async function () { + it("Should return the right amount of wearablesConfig", async function () { + expect( + await wearablesConfigFacetWithOwner.getAavegotchiWearablesConfigCount( + aavegotchiOwnerAddress, + aavegotchiId, + ), + ).to.equal(1); + // config #2 (id: 1) + await wearablesConfigFacetWithOwner.createWearablesConfig( + aavegotchiId, + "Test", + wearablesToStore, + ); + expect( + await wearablesConfigFacetWithOwner.getAavegotchiWearablesConfigCount( + aavegotchiOwnerAddress, + aavegotchiId, + ), + ).to.equal(2); + expect( + await wearablesConfigFacetWithOwner.getAavegotchiWearablesConfigCount( + aavegotchiOwnerAddress, + 2499, + ), + ).to.equal(0); + }); + }); + + describe("Testing createWearablesConfig with payment", async function () { + it("Should be able to create a 3rd for free", async function () { + // config #3 (id: 2) + await wearablesConfigFacetWithOwner.createWearablesConfig( + aavegotchiId, + "Test", + wearablesToStore, + ); + expect( + await wearablesConfigFacetWithOwner.getAavegotchiWearablesConfigCount( + aavegotchiOwnerAddress, + aavegotchiId, + ), + ).to.equal(3); + }); + it("Should not be able to create a 4th for free", async function () { + await expect( + wearablesConfigFacetWithOwner.createWearablesConfig( + aavegotchiId, + "Test 4th", + wearablesToStore, + ), + ).to.be.revertedWith("WearablesConfigFacet: Incorrect GHST value sent"); + }); + it("Should not be able to create a 4th for more than 1 GHST", async function () { + await expect( + wearablesConfigFacetWithOwner.createWearablesConfig( + aavegotchiId, + "Test 4th", + wearablesToStore, + { value: ethers.utils.parseEther("10") }, + ), + ).to.be.revertedWith("WearablesConfigFacet: Incorrect GHST value sent"); + }); + it("Should be able to pay for the 4th (with event emitted)", async function () { + const daoBalanceBefore = await ethers.provider.getBalance(daoAddress); + const receipt = await // config #4 (id: 3) + ( + await wearablesConfigFacetWithOwner.createWearablesConfig( + aavegotchiId, + "Test 4th", + wearablesToStore, + { value: ethers.utils.parseEther("1") }, + ) + ).wait(); + // check that event is emitted with the right parameters + const event = receipt!.events!.find( + (event) => event.event === "WearablesConfigDaoPaymentReceived", + ); + const owner = event!.args!.owner; + const tokenId = event!.args!.tokenId; + const wearablesConfigId = event!.args!.wearablesConfigId; + const value = event!.args!.value; + expect(owner).to.equal(aavegotchiOwnerAddress); + expect(tokenId).to.equal(aavegotchiId); + expect(wearablesConfigId).to.equal(3); + expect(value).to.equal(ethers.utils.parseEther("1")); + // compare balance before and after for dao address + const daoBalanceAfter = await ethers.provider.getBalance(daoAddress); + expect(daoBalanceAfter).to.equal( + daoBalanceBefore.add(ethers.utils.parseEther("1")), + ); + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigName( + aavegotchiOwnerAddress, + aavegotchiId, + 3, + ), + ).to.equal("Test 4th"); + }); + }); + // + // Note: Disabled until the realm diamond is set on Geist as it prevents to accept a lending + // + //describe("Testing for rented gotchis", async function () { + // + // it("Should be able to save for a rented gotchi", async function () { + // + // // create a listing rent a gotchi + // const revenueSplit = [100, 0, 0] as [ + // BigNumberish, + // BigNumberish, + // BigNumberish + // ]; + // await gotchiLendingFacetWithOwner.addGotchiListing({ + // tokenId: aavegotchiId, + // initialCost: 0, + // period: 86400, + // revenueSplit: revenueSplit, + // originalOwner: aavegotchiOwnerAddress, + // thirdParty: ethers.constants.AddressZero, + // whitelistId: 0, + // revenueTokens: [], + // permissions: 0, + // }); + // + // const listing = await lendingGetterFacet.getGotchiLendingFromToken( + // aavegotchiId + // ); + // console.log(listing); + // + // await gotchiLendingFacetWithOtherOwner.agreeGotchiLending( + // listing.listingId, + // aavegotchiId, + // 0, + // 86400, + // revenueSplit, + // ); + // + // const lending = await lendingGetterFacet.getGotchiLendingFromToken( + // aavegotchiId + // ); + // expect(lending.borrower).to.equal(anotherAavegotchiOwnerAddress); + // + // // config #5 (id: 4) + // await wearablesConfigFacetWithOtherOwner.createWearablesConfig(aavegotchiId, "Test Rental", wearablesToStore, { value: ethers.utils.parseEther("1") }) + // expect( + // await wearablesConfigFacetWithOwner.getWearablesConfigName(anotherAavegotchiOwnerAddress, aavegotchiId, 0) + // ).to.equal("Test Rental"); + // }); + // it("Should be able to update for a rented gotchi", async function () { + // const newWearablesToStore = new Array(16).fill(0); + // await wearablesConfigFacetWithOtherOwner.updateWearablesConfig(aavegotchiId, 0, "Test Update Rental", newWearablesToStore) + // expect( + // await wearablesConfigFacetWithOwner.getWearablesConfigName(anotherAavegotchiOwnerAddress, aavegotchiId, 0) + // ).to.equal("Test Update Rental"); + // expect( + // await wearablesConfigFacetWithOwner.getWearablesConfigWearables(anotherAavegotchiOwnerAddress, aavegotchiId, 0) + // ).to.eql(newWearablesToStore); + // }); + // it("Should revert on create for rented out gotchi", async function () { + // await expect( + // wearablesConfigFacetWithOwner.createWearablesConfig(aavegotchiId, "Test Rented Out", wearablesToStore, { value: ethers.utils.parseEther("1") }) + // ).to.be.revertedWith("LibAppStorage: Only aavegotchi owner can call this function"); + // }); + //}); + describe("Testing for unbridged gotchis", async function () { + it("Should be able to create for unbridged gotchi for free", async function () { + // unbridged config #1 (id: 0) + await wearablesConfigFacetWithOwner.createWearablesConfig( + 24999, + "Test Create for Aavegotchi not Bridged", + wearablesToStore, + ); + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigName( + aavegotchiOwnerAddress, + 24999, + 0, + ), + ).to.equal("Test Create for Aavegotchi not Bridged"); + }); + it("Should have to pay for the 4th slot even for unbridged gotchis", async function () { + const daoBalanceBefore = await ethers.provider.getBalance(daoAddress); + // unbridged config #2 (id: 1) + await wearablesConfigFacetWithOwner.createWearablesConfig( + 24999, + "Test Create for Aavegotchi not Bridged", + wearablesToStore, + ); + // unbridged config #3 (id: 2) + await wearablesConfigFacetWithOwner.createWearablesConfig( + 24999, + "Test Create for Aavegotchi not Bridged", + wearablesToStore, + ); + // unbridged config #4 (id: 3) + await wearablesConfigFacetWithOwner.createWearablesConfig( + 24999, + "Test Create 4th for Aavegotchi not Bridged", + wearablesToStore, + { value: ethers.utils.parseEther("1") }, + ); + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigName( + aavegotchiOwnerAddress, + 24999, + 3, + ), + ).to.equal("Test Create 4th for Aavegotchi not Bridged"); + const daoBalanceAfter = await ethers.provider.getBalance(daoAddress); + expect(daoBalanceAfter).to.equal( + daoBalanceBefore.add(ethers.utils.parseEther("1")), + ); + }); + it("Should be able to update for unbridged gotchi for free", async function () { + await wearablesConfigFacetWithOwner.updateWearablesConfig( + 24999, + 0, + "Test Update for Aavegotchi not Bridged", + wearablesToStore, + ); + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigName( + aavegotchiOwnerAddress, + 24999, + 0, + ), + ).to.equal("Test Update for Aavegotchi not Bridged"); + }); + }); + describe("Testing for other owners gotchis", async function () { + it("Should have to pay a fee for a gotchi not owned (create)", async function () { + const owner = await aavegotchiFacet.ownerOf(someoneElseAavegotchiId); + const ownerBalanceBefore = await ethers.provider.getBalance(owner); + // alt config #1 (id: 0) + await wearablesConfigFacetWithOwner.createWearablesConfig( + someoneElseAavegotchiId, + "Test Create for Aavegotchi not Owned", + wearablesToStore, + { value: ethers.utils.parseEther("0.1") }, + ); + const ownerBalanceAfter = await ethers.provider.getBalance(owner); + expect(ownerBalanceAfter).to.equal( + ownerBalanceBefore.add(ethers.utils.parseEther("0.1")), + ); + expect( + await wearablesConfigFacetWithOwner.getWearablesConfigName( + owner, + someoneElseAavegotchiId, + 0, + ), + ).to.equal("Test Create for Aavegotchi not Owned"); + }); + it("Should have to pay a fee in addition of the slot for a gotchi not owned (create)", async function () { + const owner = await aavegotchiFacet.ownerOf(someoneElseAavegotchiId); + const ownerBalanceBefore = await ethers.provider.getBalance(owner); + const daoBalanceBefore = await ethers.provider.getBalance(daoAddress); + // alt config #2 (id: 1) + await wearablesConfigFacetWithOwner.createWearablesConfig( + someoneElseAavegotchiId, + "Test Create for Aavegotchi not Owned", + wearablesToStore, + { value: ethers.utils.parseEther("0.1") }, + ); + // alt config #3 (id: 2) + await wearablesConfigFacetWithOwner.createWearablesConfig( + someoneElseAavegotchiId, + "Test Create for Aavegotchi not Owned", + wearablesToStore, + { value: ethers.utils.parseEther("0.1") }, + ); + // alt config #4 (id: 3) + await wearablesConfigFacetWithOwner.createWearablesConfig( + someoneElseAavegotchiId, + "Test Create for Aavegotchi not Owned", + wearablesToStore, + { value: ethers.utils.parseEther("1.1") }, + ); + const ownerBalanceAfter = await ethers.provider.getBalance(owner); + expect(ownerBalanceAfter).to.equal( + ownerBalanceBefore.add(ethers.utils.parseEther("0.3")), + ); + const daoBalanceAfter = await ethers.provider.getBalance(daoAddress); + expect(daoBalanceAfter).to.equal( + daoBalanceBefore.add(ethers.utils.parseEther("1")), + ); + }); + it("Should revert for a gotchi not owned (update)", async function () { + await expect( + wearablesConfigFacetWithOwner.updateWearablesConfig( + someoneElseAavegotchiId, + 0, + "", + wearablesToStore, + ), + ).to.be.revertedWith( + "WearablesConfigFacet: Only the owner can update wearables config", + ); + }); + }); + describe("Testing for invalid gotchis", async function () { + it("Should revert for a portal", async function () { + await expect( + wearablesConfigFacetWithOwner.createWearablesConfig( + unsummonedAavegotchiId, + "Test Portal", + wearablesToStore, + ), + ).to.be.revertedWith( + "WearablesConfigFacet: Not allowed to create wearables config", + ); + }); + it("Should revert for an invalid id (over max supply)", async function () { + await expect( + wearablesConfigFacetWithOwner.createWearablesConfig( + 25000, + "Test Invalid Id", + wearablesToStore, + ), + ).to.be.revertedWith( + "LibWearablesConfig: Invalid tokenId for unbridged aavegotchi", + ); + }); + }); + describe("Testing for invalid wearables", async function () { + it("Should revert for an invalid wearable id (create)", async function () { + await expect( + wearablesConfigFacetWithOwner.createWearablesConfig( + aavegotchiId, + "Test Invalid Wearable Id", + wearablesToStoreWithInvalidId, + { value: ethers.utils.parseEther("1") }, + ), + ).to.be.revertedWith("LibWearablesConfig: Item type does not exist"); + }); + it("Should revert for an invalid wearable id (update)", async function () { + await expect( + wearablesConfigFacetWithOwner.updateWearablesConfig( + aavegotchiId, + 0, + "", + wearablesToStoreWithInvalidId, + ), + ).to.be.revertedWith("LibWearablesConfig: Item type does not exist"); + }); + it("Should revert for an invalid wearable slot (create)", async function () { + await expect( + wearablesConfigFacetWithOwner.createWearablesConfig( + aavegotchiId, + "Test Invalid Wearable Slot", + wearablesToStoreWithInvalidSlot, + { value: ethers.utils.parseEther("1") }, + ), + ).to.be.revertedWith("WearablesConfigFacet: Invalid wearables"); + }); + it("Should revert for an invalid wearable slot (update)", async function () { + await expect( + wearablesConfigFacetWithOwner.updateWearablesConfig( + aavegotchiId, + 0, + "", + wearablesToStoreWithInvalidSlot, + ), + ).to.be.revertedWith("WearablesConfigFacet: Invalid wearables"); + }); + }); +});