forked from DecentralizedClimateFoundation/DCIPs
268 lines
8.0 KiB
Solidity
268 lines
8.0 KiB
Solidity
// SPDX-License-Identifier: CC0-1.0
|
|
|
|
pragma solidity ^0.8.16;
|
|
|
|
import "./ICatalog.sol";
|
|
import "@openzeppelin/contracts/utils/Address.sol";
|
|
|
|
error BadConfig();
|
|
error IdZeroForbidden();
|
|
error PartAlreadyExists();
|
|
error PartDoesNotExist();
|
|
error PartIsNotSlot();
|
|
error ZeroLengthIdsPassed();
|
|
|
|
/**
|
|
* @title Catalog
|
|
* @author RMRK team
|
|
* @notice Catalog contract for RMRK equippable module.
|
|
*/
|
|
contract Catalog is ICatalog {
|
|
using Address for address;
|
|
|
|
/**
|
|
* @notice Mapping of uint64 `partId` to Catalog `Part` struct
|
|
*/
|
|
mapping(uint64 => Part) private _parts;
|
|
|
|
/**
|
|
* @notice Mapping of uint64 `partId` to boolean flag, indicating that a given `Part` can be equippable by any address
|
|
*/
|
|
mapping(uint64 => bool) private _isEquippableToAll;
|
|
|
|
uint64[] private _partIds;
|
|
|
|
string private _metadataURI;
|
|
string private _type;
|
|
|
|
/**
|
|
* @notice Used to initialize the catalog.
|
|
* @param metadataURI Base metadata URI of the catalog
|
|
* @param type_ Type of catalog
|
|
*/
|
|
constructor(string memory metadataURI, string memory type_) {
|
|
_metadataURI = metadataURI;
|
|
_type = type_;
|
|
}
|
|
|
|
/**
|
|
* @notice Used to limit execution of functions intended for the `Slot` parts to only execute when used with such
|
|
* parts.
|
|
* @dev Reverts execution of a function if the part with associated `partId` is uninitailized or is `Fixed`.
|
|
* @param partId ID of the part that we want the function to interact with
|
|
*/
|
|
modifier onlySlot(uint64 partId) {
|
|
_onlySlot(partId);
|
|
_;
|
|
}
|
|
|
|
function _onlySlot(uint64 partId) private view {
|
|
ItemType itemType = _parts[partId].itemType;
|
|
if (itemType == ItemType.None) revert PartDoesNotExist();
|
|
if (itemType == ItemType.Fixed) revert PartIsNotSlot();
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc IERC165
|
|
*/
|
|
function supportsInterface(bytes4 interfaceId)
|
|
public
|
|
view
|
|
virtual
|
|
returns (bool)
|
|
{
|
|
return
|
|
interfaceId == type(IERC165).interfaceId ||
|
|
interfaceId == type(ICatalog).interfaceId;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc ICatalog
|
|
*/
|
|
function getMetadataURI() external view returns (string memory) {
|
|
return _metadataURI;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc ICatalog
|
|
*/
|
|
function getType() external view returns (string memory) {
|
|
return _type;
|
|
}
|
|
|
|
/**
|
|
* @notice Internal helper function that adds `Part` entries to storage.
|
|
* @dev Delegates to { _addPart } below.
|
|
* @param partIntake An array of `IntakeStruct` structs, consisting of `partId` and a nested `Part` struct
|
|
*/
|
|
function _addPartList(IntakeStruct[] calldata partIntake) internal {
|
|
uint256 len = partIntake.length;
|
|
for (uint256 i; i < len; ) {
|
|
_addPart(partIntake[i]);
|
|
unchecked {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notice Internal function that adds a single `Part` to storage.
|
|
* @param partIntake `IntakeStruct` struct consisting of `partId` and a nested `Part` struct
|
|
*
|
|
*/
|
|
function _addPart(IntakeStruct calldata partIntake) internal {
|
|
uint64 partId = partIntake.partId;
|
|
Part memory part = partIntake.part;
|
|
|
|
if (partId == uint64(0)) revert IdZeroForbidden();
|
|
if (_parts[partId].itemType != ItemType.None)
|
|
revert PartAlreadyExists();
|
|
if (part.itemType == ItemType.None) revert BadConfig();
|
|
if (part.itemType == ItemType.Fixed && part.equippable.length != 0)
|
|
revert BadConfig();
|
|
|
|
_parts[partId] = part;
|
|
_partIds.push(partId);
|
|
|
|
emit AddedPart(
|
|
partId,
|
|
part.itemType,
|
|
part.z,
|
|
part.equippable,
|
|
part.metadataURI
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice Internal function used to add multiple `equippableAddresses` to a single catalog entry.
|
|
* @dev Can only be called on `Part`s of `Slot` type.
|
|
* @param partId ID of the `Part` that we are adding the equippable addresses to
|
|
* @param equippableAddresses An array of addresses that can be equipped into the `Part` associated with the `partId`
|
|
*/
|
|
function _addEquippableAddresses(
|
|
uint64 partId,
|
|
address[] calldata equippableAddresses
|
|
) internal onlySlot(partId) {
|
|
if (equippableAddresses.length <= 0) revert ZeroLengthIdsPassed();
|
|
|
|
uint256 len = equippableAddresses.length;
|
|
for (uint256 i; i < len; ) {
|
|
_parts[partId].equippable.push(equippableAddresses[i]);
|
|
unchecked {
|
|
++i;
|
|
}
|
|
}
|
|
delete _isEquippableToAll[partId];
|
|
|
|
emit AddedEquippables(partId, equippableAddresses);
|
|
}
|
|
|
|
/**
|
|
* @notice Internal function used to set the new list of `equippableAddresses`.
|
|
* @dev Overwrites existing `equippableAddresses`.
|
|
* @dev Can only be called on `Part`s of `Slot` type.
|
|
* @param partId ID of the `Part`s that we are overwiting the `equippableAddresses` for
|
|
* @param equippableAddresses A full array of addresses that can be equipped into this `Part`
|
|
*/
|
|
function _setEquippableAddresses(
|
|
uint64 partId,
|
|
address[] calldata equippableAddresses
|
|
) internal onlySlot(partId) {
|
|
if (equippableAddresses.length <= 0) revert ZeroLengthIdsPassed();
|
|
_parts[partId].equippable = equippableAddresses;
|
|
delete _isEquippableToAll[partId];
|
|
|
|
emit SetEquippables(partId, equippableAddresses);
|
|
}
|
|
|
|
/**
|
|
* @notice Internal function used to remove all of the `equippableAddresses` for a `Part` associated with the `partId`.
|
|
* @dev Can only be called on `Part`s of `Slot` type.
|
|
* @param partId ID of the part that we are clearing the `equippableAddresses` from
|
|
*/
|
|
function _resetEquippableAddresses(uint64 partId)
|
|
internal
|
|
onlySlot(partId)
|
|
{
|
|
delete _parts[partId].equippable;
|
|
delete _isEquippableToAll[partId];
|
|
|
|
emit SetEquippables(partId, new address[](0));
|
|
}
|
|
|
|
/**
|
|
* @notice Sets the isEquippableToAll flag to true, meaning that any collection may be equipped into the `Part` with this
|
|
* `partId`.
|
|
* @dev Can only be called on `Part`s of `Slot` type.
|
|
* @param partId ID of the `Part` that we are setting as equippable by any address
|
|
*/
|
|
function _setEquippableToAll(uint64 partId) internal onlySlot(partId) {
|
|
_isEquippableToAll[partId] = true;
|
|
emit SetEquippableToAll(partId);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc ICatalog
|
|
*/
|
|
function checkIsEquippableToAll(uint64 partId) public view returns (bool) {
|
|
return _isEquippableToAll[partId];
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc ICatalog
|
|
*/
|
|
function checkIsEquippable(uint64 partId, address targetAddress)
|
|
public
|
|
view
|
|
returns (bool)
|
|
{
|
|
// If this is equippable to all, we're good
|
|
bool isEquippable = _isEquippableToAll[partId];
|
|
|
|
// Otherwise, must check against each of the equippable for the part
|
|
if (!isEquippable && _parts[partId].itemType == ItemType.Slot) {
|
|
address[] memory equippable = _parts[partId].equippable;
|
|
uint256 len = equippable.length;
|
|
for (uint256 i; i < len; ) {
|
|
if (targetAddress == equippable[i]) {
|
|
isEquippable = true;
|
|
break;
|
|
}
|
|
unchecked {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
return isEquippable;
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc ICatalog
|
|
*/
|
|
function getPart(uint64 partId) public view returns (Part memory) {
|
|
return (_parts[partId]);
|
|
}
|
|
|
|
/**
|
|
* @inheritdoc ICatalog
|
|
*/
|
|
function getParts(uint64[] calldata partIds)
|
|
public
|
|
view
|
|
returns (Part[] memory)
|
|
{
|
|
uint256 numParts = partIds.length;
|
|
Part[] memory parts = new Part[](numParts);
|
|
|
|
for (uint256 i; i < numParts; ) {
|
|
uint64 partId = partIds[i];
|
|
parts[i] = _parts[partId];
|
|
unchecked {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
return parts;
|
|
}
|
|
}
|