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

Implements BulkWriter.transferAndEquip. #269

Merged
merged 4 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions contracts/RMRK/utils/RMRKBulkWriter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ contract RMRKBulkWriter is Context {
uint64 slotPartId;
}

struct ParentData {
address parentCollection;
uint256 parentTokenId;
uint256 childIndexInParent;
uint64 parentEquippedAssetId;
uint64 parentEquippedSlotPartId;
}

/**
* @notice Reverts if the caller is not the owner of the token.
* @param collection Address of the collection that this contract is managing
Expand Down Expand Up @@ -216,6 +224,126 @@ contract RMRKBulkWriter is Context {
}
}

/**
* @notice Transfers an NFT to and NFT and optionally equips it.
* @dev If the NFT is currently equipped, you MUST set full `parentData` and it will be unequipped before transfer.
* @dev If the NFT is currently nested, you MUST set `parentData` with `parentCollection`, `parentTokenId` and `childIndexInParent`. The child will be transferred directly to the destination NFT via `transferChild`.
* @dev If the NFT is not currently nested you MUST send empty `parentData` and the transfer will be done via `nestTransferFrom`.
* @dev If the destination contract does not have an auto accept mechanism for children, the child will be auto accepted.
* @dev If the `equipData` is set, the NFT will be equipped after the transfer. Mind that the child index equip data is the index after the transfer, so it MUST be set to the number of children the destination NFT has.
* @dev If equipping into destination, the `equipData.tokenId` will match the `destinationTokenId`.
* @dev This contract MUST have approval on the parent collection, if any, to transfer the NFT.
* @dev This contract MUST have approval for assets on the parent collection, if any, to unequip the NFT.
* @dev This contract MUST have approval on the NFT collection, if not nested, to transfer the NFT.
* @dev This contract MUST have approval on the destination collection to accept the NFT.
* @dev This contract MUST have approval for assets on the destination collection to equip the NFT.
* @param collection Address of the collection that this contract is managing
* @param tokenId ID of the token we are managing
* @param destinationCollection Address of the destination collection
* @param destinationTokenId ID of the destination token
* @param parentData A `ParentData` struct specifying the parent data
* @param equipData An `IntakeEquip` struct specifying the equip data
*/
function transferAndEquip(
address collection,
uint256 tokenId,
address destinationCollection,
uint256 destinationTokenId,
ParentData memory parentData,
IERC6220.IntakeEquip memory equipData
) public onlyTokenOwner(collection, tokenId) {
if (parentData.parentCollection != address(0)) {
if (
parentData.parentEquippedAssetId != 0 &&
parentData.parentEquippedSlotPartId != 0
) {
IERC6220(parentData.parentCollection).unequip(
parentData.parentTokenId,
parentData.parentEquippedAssetId,
parentData.parentEquippedSlotPartId
);
}
IERC7401(parentData.parentCollection).transferChild(
parentData.parentTokenId,
destinationCollection,
destinationTokenId,
parentData.childIndexInParent,
collection,
tokenId,
false,
""
);
} else {
IERC7401(collection).nestTransferFrom(
_msgSender(),
destinationCollection,
tokenId,
destinationTokenId,
""
);
}

_acceptChildIfNecessary(
destinationCollection,
destinationTokenId,
collection,
tokenId
);

if (equipData.assetId == 0) return;

_unequipSlotIfNecessary(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if there's any benefit in re-using replaceEquipement function from this contract here instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sound great, will give it a try.

destinationCollection,
destinationTokenId,
equipData.assetId,
equipData.slotPartId
);

IERC6220(destinationCollection).equip(equipData);
}

function _acceptChildIfNecessary(
address parentCollection,
uint256 parentId,
address childCollection,
uint256 childId
) private {
IERC7401 destination = IERC7401(parentCollection);
IERC7401.Child[] memory children = destination.pendingChildrenOf(
parentId
);
uint256 length = children.length;
for (uint256 i; i < length; ) {
IERC7401.Child memory child = children[i];
if (
child.contractAddress == childCollection &&
child.tokenId == childId
) {
destination.acceptChild(parentId, i, childCollection, childId);
return;
}
unchecked {
++i;
}
}
}

function _unequipSlotIfNecessary(
address collection,
uint256 tokenId,
uint64 assetId,
uint64 slotPartId
) private {
IERC6220 targetCollection = IERC6220(collection);
(, , address catalogAddress, ) = targetCollection
.getAssetAndEquippableData(tokenId, assetId);
if (
targetCollection
.getEquipment(tokenId, catalogAddress, slotPartId)
.childId != 0
) targetCollection.unequip(tokenId, assetId, slotPartId);
}

/**
* @notice Validates that the caller is the owner of the token.
* @dev Reverts if the caller is not the owner of the token.
Expand Down
21 changes: 21 additions & 0 deletions docs/RMRK/utils/RMRKBulkWriter.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,27 @@ function replaceEquip(address collection, IERC6220.IntakeEquip data) external no
| collection | address | undefined |
| data | IERC6220.IntakeEquip | undefined |

### transferAndEquip

```solidity
function transferAndEquip(address collection, uint256 tokenId, address destinationCollection, uint256 destinationTokenId, RMRKBulkWriter.ParentData parentData, IERC6220.IntakeEquip equipData) external nonpayable
```





#### Parameters

| Name | Type | Description |
|---|---|---|
| collection | address | undefined |
| tokenId | uint256 | undefined |
| destinationCollection | address | undefined |
| destinationTokenId | uint256 | undefined |
| parentData | RMRKBulkWriter.ParentData | undefined |
| equipData | IERC6220.IntakeEquip | undefined |




Expand Down
Loading
Loading