Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AGIP135: add batch equip functions for wearables and wearable configs #3

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
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