Skip to content
38 changes: 36 additions & 2 deletions contracts/Aavegotchi/facets/ItemsFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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,
Expand Down
194 changes: 194 additions & 0 deletions contracts/Aavegotchi/facets/WearablesConfigFacet.sol
Original file line number Diff line number Diff line change
@@ -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];
}
}
9 changes: 9 additions & 0 deletions contracts/Aavegotchi/libraries/LibAppStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
76 changes: 76 additions & 0 deletions contracts/Aavegotchi/libraries/LibWearablesConfig.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
3 changes: 2 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion scripts/deployFullDiamond.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -799,6 +799,7 @@ export async function deployFullDiamond(useFreshDeploy: boolean = false) {
"ItemsRolesRegistryFacet",
"ERC1155BuyOrderFacet",
"PolygonXGeistBridgeFacet",
"WearablesConfigFacet",
],
owner: ownerAddress,
args: initArgs,
Expand Down
Loading