Skip to content

Commit

Permalink
feat(transfer): batch transfers and across collections
Browse files Browse the repository at this point in the history
  • Loading branch information
bitbeckers committed Oct 28, 2023
1 parent fe87e38 commit 922569b
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 10 deletions.
11 changes: 11 additions & 0 deletions contracts/src/marketplace/TransferManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,17 @@ contract TransferManager is ITransferManager, LowLevelERC721Transfer, LowLevelER
}
}
_executeERC1155SafeBatchTransferFrom(items[i].collection, from, to, itemIds, amounts);
} else if (collectionType == CollectionType.Hypercert) {
for (uint256 j; j < itemIdsLengthForSingleCollection;) {
if (amounts[j] == 0) {
revert AmountInvalid();
}

unchecked {
++j;
}
}
_executeERC1155SafeBatchTransferFrom(items[i].collection, from, to, itemIds, amounts);
}

unchecked {
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/marketplace/interfaces/ITransferManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface ITransferManager {
/**
* @notice This struct is only used for transferBatchItemsAcrossCollections.
* @param collection Collection address
* @param collectionType 0 for ERC721, 1 for ERC1155
* @param collectionType 0 for ERC721, 1 for ERC1155, 2 for Hypercert, 3 for Hyperboard
* @param itemIds Array of item ids to transfer
* @param amounts Array of amounts to transfer
*/
Expand Down
238 changes: 229 additions & 9 deletions contracts/test/foundry/marketplace/TransferManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
uint256 private constant amount1Hypercert = 1;
uint256 private constant units1Hypercert = 10_000;
uint256 private constant fractionId1Hypercert = claimId1Hypercert + 1;

uint256 private constant claimId2Hypercert = 2 << 128;
uint256 private constant amount2Hypercert = 1;
uint256 private constant units2Hypercert = 10_000;
uint256 private constant fractionId2Hypercert = claimId2Hypercert + 1;

IHypercertToken.TransferRestrictions private constant FROM_CREATOR_ONLY =
IHypercertToken.TransferRestrictions.FromCreatorOnly;

Expand Down Expand Up @@ -200,23 +206,58 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
assertEq(mockERC1155.balanceOf(_recipient, tokenId2), amount2);
}

function testTransferBatchItemsAcrossCollectionERC721AndERC1155() public {
function testTransferBatchItemsHypercerts() public {
_allowOperator(_transferrer);
_grantApprovals(_sender);

ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems();
uint256 tokenId1 = fractionId1Hypercert;
uint256 amount1 = amount1Hypercert;
uint256 tokenId2 = fractionId2Hypercert;
uint256 amount2 = amount2Hypercert;

vm.startPrank(_sender);
mockHypercertMinter.mintClaim(_sender, units1Hypercert, "https://example.com/1", FROM_CREATOR_ONLY);
mockHypercertMinter.mintClaim(_sender, units2Hypercert, "https://example.com/2", FROM_CREATOR_ONLY);
vm.stopPrank();

uint256[] memory itemIds = new uint256[](2);
itemIds[0] = tokenId1;
itemIds[1] = tokenId2;

uint256[] memory amounts = new uint256[](2);
amounts[0] = amount1;
amounts[1] = amount2;

vm.prank(_transferrer);
transferManager.transferItemsHypercert(address(mockHypercertMinter), _sender, _recipient, itemIds, amounts);

assertEq(mockHypercertMinter.balanceOf(_recipient, tokenId1), amount1);
assertEq(mockHypercertMinter.balanceOf(_recipient, tokenId2), amount2);
}

function testTransferBatchItemsAcrossCollectionERC721AndERC1155AndHypercert() public {
_allowOperator(_transferrer);
_grantApprovals(_sender);

ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItemsPranked(_sender);

vm.prank(_transferrer);
transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient);

assertEq(mockERC721.ownerOf(tokenIdERC721), _recipient);
assertEq(mockERC1155.balanceOf(_recipient, tokenId1ERC1155), amount1ERC1155);
assertEq(mockERC1155.balanceOf(_recipient, tokenId2ERC1155), amount2ERC1155);
assertEq(mockHypercertMinter.balanceOf(_recipient, fractionId1Hypercert), amount1Hypercert);
assertEq(mockHypercertMinter.balanceOf(_recipient, fractionId2Hypercert), amount2Hypercert);
}

function testTransferBatchItemsAcrossCollectionERC721AndERC1155ByOwner() public asPrankedUser(_sender) {
function testTransferBatchItemsAcrossCollectionERC721AndERC1155AndHypercertByOwner()
public
asPrankedUser(_sender)
{
mockERC721.setApprovalForAll(address(transferManager), true);
mockERC1155.setApprovalForAll(address(transferManager), true);
mockHypercertMinter.setApprovalForAll(address(transferManager), true);

ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems();

Expand All @@ -225,6 +266,8 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
assertEq(mockERC721.ownerOf(tokenIdERC721), _recipient);
assertEq(mockERC1155.balanceOf(_recipient, tokenId1ERC1155), amount1ERC1155);
assertEq(mockERC1155.balanceOf(_recipient, tokenId2ERC1155), amount2ERC1155);
assertEq(mockHypercertMinter.balanceOf(_recipient, fractionId1Hypercert), amount1Hypercert);
assertEq(mockHypercertMinter.balanceOf(_recipient, fractionId2Hypercert), amount2Hypercert);
}

/**
Expand Down Expand Up @@ -268,6 +311,25 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts);
}

function testTransferSingleItemHypercertAmountIsZero() public {
_allowOperator(_transferrer);
_grantApprovals(_sender);

uint256 itemId = fractionId1Hypercert;

vm.prank(_sender);
mockHypercertMinter.mintClaim(_sender, 1, "https://example.com/1", FROM_CREATOR_ONLY);

uint256[] memory itemIds = new uint256[](1);
itemIds[0] = itemId;
uint256[] memory amounts = new uint256[](1);
amounts[0] = 0;

vm.expectRevert(AmountInvalid.selector);
vm.prank(_transferrer);
transferManager.transferItemsHypercert(address(mockHypercertMinter), _sender, _recipient, itemIds, amounts);
}

function testTransferMultipleItemsERC1155AmountIsZero() public {
_allowOperator(_transferrer);
_grantApprovals(_sender);
Expand All @@ -290,6 +352,30 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts);
}

function testTransferMultipleItemsHypercertAmountIsZero() public {
_allowOperator(_transferrer);
_grantApprovals(_sender);

uint256 itemIdOne = fractionId1Hypercert;
uint256 itemIdTwo = fractionId2Hypercert;

vm.prank(_sender);
mockHypercertMinter.mintClaim(_sender, 1, "https://example.com/1", FROM_CREATOR_ONLY);
vm.prank(_sender);
mockHypercertMinter.mintClaim(_sender, 1, "https://example.com/2", FROM_CREATOR_ONLY);

uint256[] memory itemIds = new uint256[](2);
itemIds[0] = itemIdOne;
itemIds[1] = itemIdTwo;
uint256[] memory amounts = new uint256[](2);
amounts[0] = 0;
amounts[1] = 0;

vm.expectRevert(AmountInvalid.selector);
vm.prank(_transferrer);
transferManager.transferItemsHypercert(address(mockHypercertMinter), _sender, _recipient, itemIds, amounts);
}

function testTransferBatchItemsAcrossCollectionZeroLength() public {
_allowOperator(_transferrer);
_grantApprovals(_sender);
Expand All @@ -307,7 +393,7 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
_allowOperator(_transferrer);
_grantApprovals(_sender);

ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems();
ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItemsPranked(_sender);
items[1].amounts[0] = amount;

vm.expectRevert(AmountInvalid.selector);
Expand All @@ -319,19 +405,31 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
_allowOperator(_transferrer);
_grantApprovals(_sender);

ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems();
ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItemsPranked(_sender);
items[0].amounts[0] = 0;

vm.expectRevert(AmountInvalid.selector);
vm.prank(_sender);
transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient);
}

function testCannotBatchTransferIfHypercertAmountIsZero() public {
_allowOperator(_transferrer);
_grantApprovals(_sender);

ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItemsPranked(_sender);
items[2].amounts[0] = 0;

vm.expectRevert(AmountInvalid.selector);
vm.prank(_sender);
transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient);
}

function testTransferBatchItemsAcrossCollectionPerCollectionItemIdsLengthZero() public {
_allowOperator(_transferrer);
_grantApprovals(_sender);

ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems();
ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItemsPranked(_sender);
items[0].itemIds = new uint256[](0);
items[0].amounts = new uint256[](0);

Expand Down Expand Up @@ -404,6 +502,38 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts);
}

function testCannotTransferHypercertIfOperatorApprovalsRevokedByUserOrOperatorRemovedByOwner() public {
_allowOperator(_transferrer);
_grantApprovals(_sender);

// 1. User revokes the operator
vm.prank(_sender);
vm.expectEmit(false, false, false, true);
emit ApprovalsRemoved(_sender, operators);
transferManager.revokeApprovals(operators);

uint256 itemId = fractionId1Hypercert;
uint256[] memory itemIds = new uint256[](1);
itemIds[0] = itemId;
uint256[] memory amounts = new uint256[](1);
amounts[0] = 5;

vm.prank(_transferrer);
vm.expectRevert(ITransferManager.TransferCallerInvalid.selector);
transferManager.transferItemsHypercert(address(mockHypercertMinter), _sender, _recipient, itemIds, amounts);

// 2. Sender grants again approvals but owner removes the operators
_grantApprovals(_sender);
vm.prank(_owner);
vm.expectEmit(false, false, false, true);
emit OperatorRemoved(_transferrer);
transferManager.removeOperator(_transferrer);

vm.prank(_transferrer);
vm.expectRevert(ITransferManager.TransferCallerInvalid.selector);
transferManager.transferItemsHypercert(address(mockHypercertMinter), _sender, _recipient, itemIds, amounts);
}

function testCannotBatchTransferIfOperatorApprovalsRevoked() public {
_allowOperator(_transferrer);
_grantApprovals(_sender);
Expand All @@ -414,7 +544,7 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
emit ApprovalsRemoved(_sender, operators);
transferManager.revokeApprovals(operators);

ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItems();
ITransferManager.BatchTransferItem[] memory items = _generateValidBatchTransferItemsPranked(_sender);

vm.prank(_transferrer);
vm.expectRevert(ITransferManager.TransferCallerInvalid.selector);
Expand All @@ -432,7 +562,7 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
transferManager.transferBatchItemsAcrossCollections(items, _sender, _recipient);
}

function testCannotTransferERC721OrERC1155IfArrayLengthIs0() public {
function testCannotTransferERC721OrERC1155orHypercertIfArrayLengthIs0() public {
uint256[] memory emptyArrayUint256 = new uint256[](0);

// 1. ERC721
Expand All @@ -446,6 +576,12 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
transferManager.transferItemsERC1155(
address(mockERC1155), _sender, _recipient, emptyArrayUint256, emptyArrayUint256
);

// 3. Hypercert length is 0
vm.expectRevert(LengthsInvalid.selector);
transferManager.transferItemsHypercert(
address(mockHypercertMinter), _sender, _recipient, emptyArrayUint256, emptyArrayUint256
);
}

function testCannotTransferERC1155IfArrayLengthDiffers() public {
Expand All @@ -456,6 +592,14 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
transferManager.transferItemsERC1155(address(mockERC1155), _sender, _recipient, itemIds, amounts);
}

function testCannotTransferHypercertsIfArrayLengthDiffers() public {
uint256[] memory itemIds = new uint256[](2);
uint256[] memory amounts = new uint256[](3);

vm.expectRevert(LengthsInvalid.selector);
transferManager.transferItemsHypercert(address(mockHypercertMinter), _sender, _recipient, itemIds, amounts);
}

function testUserCannotGrantOrRevokeApprovalsIfArrayLengthIs0() public {
address[] memory emptyArrayAddresses = new address[](0);

Expand Down Expand Up @@ -529,13 +673,83 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
}

function _generateValidBatchTransferItems() private returns (BatchTransferItem[] memory items) {
items = new ITransferManager.BatchTransferItem[](2);
items = new ITransferManager.BatchTransferItem[](3);

{
mockERC721.mint(_sender, tokenIdERC721);
mockERC1155.mint(_sender, tokenId1ERC1155, amount1ERC1155);
mockERC1155.mint(_sender, tokenId2ERC1155, amount2ERC1155);

mockHypercertMinter.mintClaim(_sender, units1Hypercert, "https://example.com/1", FROM_CREATOR_ONLY);
mockHypercertMinter.mintClaim(_sender, units2Hypercert, "https://example.com/2", FROM_CREATOR_ONLY);

uint256[] memory fractionIdsHypercerts = new uint256[](2);
fractionIdsHypercerts[0] = fractionId1Hypercert;
fractionIdsHypercerts[1] = fractionId2Hypercert;

uint256[] memory amountsHypercerts = new uint256[](2);
amountsHypercerts[0] = amount1Hypercert;
amountsHypercerts[1] = amount2Hypercert;

uint256[] memory tokenIdsERC1155 = new uint256[](2);
tokenIdsERC1155[0] = tokenId1ERC1155;
tokenIdsERC1155[1] = tokenId2ERC1155;

uint256[] memory amountsERC1155 = new uint256[](2);
amountsERC1155[0] = amount1ERC1155;
amountsERC1155[1] = amount2ERC1155;

uint256[] memory tokenIdsERC721 = new uint256[](1);
tokenIdsERC721[0] = tokenIdERC721;

uint256[] memory amountsERC721 = new uint256[](1);
amountsERC721[0] = 1;

items[0] = ITransferManager.BatchTransferItem({
collection: address(mockERC1155),
collectionType: CollectionType.ERC1155,
itemIds: tokenIdsERC1155,
amounts: amountsERC1155
});
items[1] = ITransferManager.BatchTransferItem({
collection: address(mockERC721),
collectionType: CollectionType.ERC721,
itemIds: tokenIdsERC721,
amounts: amountsERC721
});
items[2] = ITransferManager.BatchTransferItem({
collection: address(mockHypercertMinter),
collectionType: CollectionType.Hypercert,
itemIds: fractionIdsHypercerts,
amounts: amountsHypercerts
});
}
}

function _generateValidBatchTransferItemsPranked(address pranker)
private
returns (BatchTransferItem[] memory items)
{
items = new ITransferManager.BatchTransferItem[](3);

{
mockERC721.mint(_sender, tokenIdERC721);
mockERC1155.mint(_sender, tokenId1ERC1155, amount1ERC1155);
mockERC1155.mint(_sender, tokenId2ERC1155, amount2ERC1155);

vm.startPrank(pranker);
mockHypercertMinter.mintClaim(pranker, units1Hypercert, "https://example.com/1", FROM_CREATOR_ONLY);
mockHypercertMinter.mintClaim(pranker, units2Hypercert, "https://example.com/2", FROM_CREATOR_ONLY);
vm.stopPrank();

uint256[] memory fractionIdsHypercerts = new uint256[](2);
fractionIdsHypercerts[0] = fractionId1Hypercert;
fractionIdsHypercerts[1] = fractionId2Hypercert;

uint256[] memory amountsHypercerts = new uint256[](2);
amountsHypercerts[0] = amount1Hypercert;
amountsHypercerts[1] = amount2Hypercert;

uint256[] memory tokenIdsERC1155 = new uint256[](2);
tokenIdsERC1155[0] = tokenId1ERC1155;
tokenIdsERC1155[1] = tokenId2ERC1155;
Expand All @@ -562,6 +776,12 @@ contract TransferManagerTest is ITransferManager, TestHelpers, TestParameters {
itemIds: tokenIdsERC721,
amounts: amountsERC721
});
items[2] = ITransferManager.BatchTransferItem({
collection: address(mockHypercertMinter),
collectionType: CollectionType.Hypercert,
itemIds: fractionIdsHypercerts,
amounts: amountsHypercerts
});
}
}
}

0 comments on commit 922569b

Please sign in to comment.