DCIPs/assets/eip-6220/contracts/utils/EquipRenderUtils.sol

482 lines
17 KiB
Solidity
Raw Normal View History

// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.16;
import "../ICatalog.sol";
import "../IEquippable.sol";
import "../library/EquippableLib.sol";
error TokenHasNoAssets();
error NotComposableAsset();
/**
* @title EquipRenderUtils
* @author RMRK team
* @notice Smart contract of the RMRK Equip render utils module.
* @dev Extra utility functions for composing RMRK extended assets.
*/
contract EquipRenderUtils {
using EquippableLib for uint64[];
/**
* @notice The structure used to display a full information of an active asset.
* @return id ID of the asset
* @return equppableGroupId ID of the equippable group this asset belongs to
* @return priority Priority of the asset in the active assets array it belongs to
* @return catalogAddress Address of the `Catalog` smart contract this asset belongs to
* @return metadata Metadata URI of the asset
* @return partIds[] An array of IDs of fixed and slot parts present in the asset
*/
struct ExtendedActiveAsset {
uint64 id;
uint64 equippableGroupId;
uint16 priority;
address catalogAddress;
string metadata;
uint64[] partIds;
}
/**
* @notice The structure used to display a full information of a pending asset.
* @return id ID of the asset
* @return equppableGroupId ID of the equippable group this asset belongs to
* @return acceptRejectIndex The index of the given asset in the pending assets array it belongs to
* @return replacesAssetWithId ID of the asset the given asset will replace if accepted
* @return catalogAddress Address of the `Catalog` smart contract this asset belongs to
* @return metadata Metadata URI of the asset
* @return partIds[] An array of IDs of fixed and slot parts present in the asset
*/
struct ExtendedPendingAsset {
uint64 id;
uint64 equippableGroupId;
uint128 acceptRejectIndex;
uint64 replacesAssetWithId;
address catalogAddress;
string metadata;
uint64[] partIds;
}
/**
* @notice The structure used to display a full information of an equippend slot part.
* @return partId ID of the slot part
* @return childAssetId ID of the child asset equipped into the slot part
* @return z The z value of the part defining how it should be rendered when presenting the full NFT
* @return childAddress Address of the collection smart contract of the child token equipped into the slot
* @return childId ID of the child token equipped into the slot
* @return childAssetMetadata Metadata URI of the child token equipped into the slot
* @return partMetadata Metadata URI of the given slot part
*/
struct EquippedSlotPart {
uint64 partId;
uint64 childAssetId;
uint8 z; //1 byte
address childAddress;
uint256 childId;
string childAssetMetadata; //n bytes 32+
string partMetadata; //n bytes 32+
}
/**
* @notice Used to provide data about fixed parts.
* @return partId ID of the part
* @return z The z value of the asset, specifying how the part should be rendered in a composed NFT
* @return matadataURI The metadata URI of the fixed part
*/
struct FixedPart {
uint64 partId;
uint8 z; //1 byte
string metadataURI; //n bytes 32+
}
/**
* @notice Used to get extended active assets of the given token.
* @dev The full `ExtendedActiveAsset` looks like this:
* [
* ID,
* equippableGroupId,
* priority,
* catalogAddress,
* metadata,
* [
* fixedPartId0,
* fixedPartId1,
* fixedPartId2,
* slotPartId0,
* slotPartId1,
* slotPartId2
* ]
* ]
* @param target Address of the smart contract of the given token
* @param tokenId ID of the token to retrieve the extended active assets for
* @return sturct[] An array of ExtendedActiveAssets present on the given token
*/
function getExtendedActiveAssets(address target, uint256 tokenId)
public
view
virtual
returns (ExtendedActiveAsset[] memory)
{
IEquippable target_ = IEquippable(target);
uint64[] memory assets = target_.getActiveAssets(tokenId);
uint16[] memory priorities = target_.getActiveAssetPriorities(tokenId);
uint256 len = assets.length;
if (len == 0) {
revert TokenHasNoAssets();
}
ExtendedActiveAsset[] memory activeAssets = new ExtendedActiveAsset[](
len
);
for (uint256 i; i < len; ) {
(
string memory metadataURI,
uint64 equippableGroupId,
address catalogAddress,
uint64[] memory partIds
) = target_.getAssetAndEquippableData(tokenId, assets[i]);
activeAssets[i] = ExtendedActiveAsset({
id: assets[i],
equippableGroupId: equippableGroupId,
priority: priorities[i],
catalogAddress: catalogAddress,
metadata: metadataURI,
partIds: partIds
});
unchecked {
++i;
}
}
return activeAssets;
}
/**
* @notice Used to get the extended pending assets of the given token.
* @dev The full `ExtendedPendingAsset` looks like this:
* [
* ID,
* equippableGroupId,
* acceptRejectIndex,
* replacesAssetWithId,
* catalogAddress,
* metadata,
* [
* fixedPartId0,
* fixedPartId1,
* fixedPartId2,
* slotPartId0,
* slotPartId1,
* slotPartId2
* ]
* ]
* @param target Address of the smart contract of the given token
* @param tokenId ID of the token to retrieve the extended pending assets for
* @return sturct[] An array of ExtendedPendingAssets present on the given token
*/
function getExtendedPendingAssets(address target, uint256 tokenId)
public
view
virtual
returns (ExtendedPendingAsset[] memory)
{
IEquippable target_ = IEquippable(target);
uint64[] memory assets = target_.getPendingAssets(tokenId);
uint256 len = assets.length;
if (len == 0) {
revert TokenHasNoAssets();
}
ExtendedPendingAsset[]
memory pendingAssets = new ExtendedPendingAsset[](len);
uint64 replacesAssetWithId;
for (uint256 i; i < len; ) {
(
string memory metadataURI,
uint64 equippableGroupId,
address catalogAddress,
uint64[] memory partIds
) = target_.getAssetAndEquippableData(tokenId, assets[i]);
replacesAssetWithId = target_.getAssetReplacements(
tokenId,
assets[i]
);
pendingAssets[i] = ExtendedPendingAsset({
id: assets[i],
equippableGroupId: equippableGroupId,
acceptRejectIndex: uint128(i),
replacesAssetWithId: replacesAssetWithId,
catalogAddress: catalogAddress,
metadata: metadataURI,
partIds: partIds
});
unchecked {
++i;
}
}
return pendingAssets;
}
/**
* @notice Used to retrieve the equipped parts of the given token.
* @dev NOTE: Some of the equipped children might be empty.
* @dev The full `Equipment` struct looks like this:
* [
* assetId,
* childAssetId,
* childId,
* childEquippableAddress
* ]
* @param target Address of the smart contract of the given token
* @param tokenId ID of the token to retrieve the equipped items in the asset for
* @param assetId ID of the asset being queried for equipped parts
* @return slotPartIds An array of the IDs of the slot parts present in the given asset
* @return childrenEquipped An array of `Equipment` structs containing info about the equipped children
*/
function getEquipped(
address target,
uint64 tokenId,
uint64 assetId
)
public
view
returns (
uint64[] memory slotPartIds,
IEquippable.Equipment[] memory childrenEquipped
)
{
IEquippable target_ = IEquippable(target);
(, , address catalogAddress, uint64[] memory partIds) = target_
.getAssetAndEquippableData(tokenId, assetId);
(slotPartIds, ) = splitSlotAndFixedParts(partIds, catalogAddress);
childrenEquipped = new IEquippable.Equipment[](slotPartIds.length);
uint256 len = slotPartIds.length;
for (uint256 i; i < len; ) {
IEquippable.Equipment memory equipment = target_.getEquipment(
tokenId,
catalogAddress,
slotPartIds[i]
);
if (equipment.assetId == assetId) {
childrenEquipped[i] = equipment;
}
unchecked {
++i;
}
}
}
/**
* @notice Used to compose the given equippables.
* @dev The full `FixedPart` struct looks like this:
* [
* partId,
* z,
* metadataURI
* ]
* @dev The full `EquippedSlotPart` struct looks like this:
* [
* partId,
* childAssetId,
* z,
* childAddress,
* childId,
* childAssetMetadata,
* partMetadata
* ]
* @param target Address of the smart contract of the given token
* @param tokenId ID of the token to compose the equipped items in the asset for
* @param assetId ID of the asset being queried for equipped parts
* @return metadataURI Metadata URI of the asset
* @return equippableGroupId Equippable group ID of the asset
* @return catalogAddress Address of the catalog to which the asset belongs to
* @return fixedParts An array of fixed parts respresented by the `FixedPart` structs present on the asset
* @return slotParts An array of slot parts represented by the `EquippedSlotPart` structs present on the asset
*/
function composeEquippables(
address target,
uint256 tokenId,
uint64 assetId
)
public
view
returns (
string memory metadataURI,
uint64 equippableGroupId,
address catalogAddress,
FixedPart[] memory fixedParts,
EquippedSlotPart[] memory slotParts
)
{
IEquippable target_ = IEquippable(target);
uint64[] memory partIds;
// If token does not have uint64[] memory slotPartId to save the asset, it would fail here.
(metadataURI, equippableGroupId, catalogAddress, partIds) = target_
.getAssetAndEquippableData(tokenId, assetId);
if (catalogAddress == address(0)) revert NotComposableAsset();
(
uint64[] memory slotPartIds,
uint64[] memory fixedPartIds
) = splitSlotAndFixedParts(partIds, catalogAddress);
// Fixed parts:
fixedParts = new FixedPart[](fixedPartIds.length);
uint256 len = fixedPartIds.length;
if (len != 0) {
ICatalog.Part[] memory catalogFixedParts = ICatalog(
catalogAddress
).getParts(fixedPartIds);
for (uint256 i; i < len; ) {
fixedParts[i] = FixedPart({
partId: fixedPartIds[i],
z: catalogFixedParts[i].z,
metadataURI: catalogFixedParts[i].metadataURI
});
unchecked {
++i;
}
}
}
slotParts = getEquippedSlotParts(
target_,
tokenId,
assetId,
catalogAddress,
slotPartIds
);
}
/**
* @notice Used to retrieve the equipped slot parts.
* @dev The full `EquippedSlotPart` struct looks like this:
* [
* partId,
* childAssetId,
* z,
* childAddress,
* childId,
* childAssetMetadata,
* partMetadata
* ]
* @param target_ An address of the `IEquippable` smart contract to retrieve the equipped slot parts from.
* @param tokenId ID of the token for which to retrieve the equipped slot parts
* @param assetId ID of the asset on the token to retrieve the equipped slot parts
* @param catalogAddress The address of the catalog to which the given asset belongs to
* @param slotPartIds An array of slot part IDs in the asset for which to retrieve the equipped slot parts
* @return slotParts An array of `EquippedSlotPart` structs representing the equipped slot parts
*/
function getEquippedSlotParts(
IEquippable target_,
uint256 tokenId,
uint64 assetId,
address catalogAddress,
uint64[] memory slotPartIds
) private view returns (EquippedSlotPart[] memory slotParts) {
slotParts = new EquippedSlotPart[](slotPartIds.length);
uint256 len = slotPartIds.length;
if (len != 0) {
string memory metadata;
ICatalog.Part[] memory catalogSlotParts = ICatalog(catalogAddress)
.getParts(slotPartIds);
for (uint256 i; i < len; ) {
IEquippable.Equipment memory equipment = target_.getEquipment(
tokenId,
catalogAddress,
slotPartIds[i]
);
if (equipment.assetId == assetId) {
metadata = IEquippable(equipment.childEquippableAddress)
.getAssetMetadata(
equipment.childId,
equipment.childAssetId
);
slotParts[i] = EquippedSlotPart({
partId: slotPartIds[i],
childAssetId: equipment.childAssetId,
z: catalogSlotParts[i].z,
childId: equipment.childId,
childAddress: equipment.childEquippableAddress,
childAssetMetadata: metadata,
partMetadata: catalogSlotParts[i].metadataURI
});
} else {
slotParts[i] = EquippedSlotPart({
partId: slotPartIds[i],
childAssetId: uint64(0),
z: catalogSlotParts[i].z,
childId: uint256(0),
childAddress: address(0),
childAssetMetadata: "",
partMetadata: catalogSlotParts[i].metadataURI
});
}
unchecked {
++i;
}
}
}
}
/**
* @notice Used to split slot and fixed parts.
* @param allPartIds[] An array of `Part` IDs containing both, `Slot` and `Fixed` parts
* @param catalogAddress An address of the catalog to which the given `Part`s belong to
* @return slotPartIds An array of IDs of the `Slot` parts included in the `allPartIds`
* @return fixedPartIds An array of IDs of the `Fixed` parts included in the `allPartIds`
*/
function splitSlotAndFixedParts(
uint64[] memory allPartIds,
address catalogAddress
)
public
view
returns (uint64[] memory slotPartIds, uint64[] memory fixedPartIds)
{
ICatalog.Part[] memory allParts = ICatalog(catalogAddress)
.getParts(allPartIds);
uint256 numFixedParts;
uint256 numSlotParts;
uint256 numParts = allPartIds.length;
// This for loop is just to discover the right size of the split arrays, since we can't create them dynamically
for (uint256 i; i < numParts; ) {
if (allParts[i].itemType == ICatalog.ItemType.Fixed)
numFixedParts += 1;
// We could just take the numParts - numFixedParts, but it doesn't hurt to double check it's not an uninitialized part:
else if (allParts[i].itemType == ICatalog.ItemType.Slot)
numSlotParts += 1;
unchecked {
++i;
}
}
slotPartIds = new uint64[](numSlotParts);
fixedPartIds = new uint64[](numFixedParts);
uint256 slotPartsIndex;
uint256 fixedPartsIndex;
// This for loop is to actually fill the split arrays
for (uint256 i; i < numParts; ) {
if (allParts[i].itemType == ICatalog.ItemType.Fixed) {
fixedPartIds[fixedPartsIndex] = allPartIds[i];
fixedPartsIndex += 1;
} else if (allParts[i].itemType == ICatalog.ItemType.Slot) {
slotPartIds[slotPartsIndex] = allPartIds[i];
slotPartsIndex += 1;
}
unchecked {
++i;
}
}
}
}