Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion abis/IRareBatchListingMarketplace.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion abis/RareBatchAuctionHouse.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion abis/RareBatchListingMarketplace.json

Large diffs are not rendered by default.

364 changes: 362 additions & 2 deletions src/test/v2/auctionhouse/RareBatchAuctionHouse.t.sol

Large diffs are not rendered by default.

258 changes: 247 additions & 11 deletions src/test/v2/marketplace/RareBatchListingMarketplace.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,16 @@ contract RareBatchListingMarketplaceTest is Test {
// Execute purchase
vm.startPrank(buyer);
bytes32[] memory emptyAllowListProof = new bytes32[](0);
marketplace.buyWithMerkleProof(address(nftContract), firstTokenId, creator, root, proof, emptyAllowListProof);
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
proof,
emptyAllowListProof
);
vm.stopPrank();

// Verify token ownership changed
Expand Down Expand Up @@ -464,6 +473,8 @@ contract RareBatchListingMarketplaceTest is Test {
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
invalidProof,
Expand Down Expand Up @@ -497,11 +508,29 @@ contract RareBatchListingMarketplaceTest is Test {
bytes32[] memory emptyAllowListProof = new bytes32[](0);

// First purchase should succeed
marketplace.buyWithMerkleProof(address(nftContract), firstTokenId, creator, root, proof, emptyAllowListProof);
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
proof,
emptyAllowListProof
);

// Second purchase should fail
vm.expectRevert("buyWithMerkleProof::Token already used for this Merkle root");
marketplace.buyWithMerkleProof(address(nftContract), firstTokenId, creator, root, proof, emptyAllowListProof);
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
proof,
emptyAllowListProof
);
vm.stopPrank();
}

Expand Down Expand Up @@ -533,7 +562,16 @@ contract RareBatchListingMarketplaceTest is Test {
// Try to buy when creator no longer owns the token
bytes32[] memory emptyAllowListProof = new bytes32[](0);
vm.expectRevert("buyWithMerkleProof::Not token owner");
marketplace.buyWithMerkleProof(address(nftContract), firstTokenId, creator, root, proof, emptyAllowListProof);
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
proof,
emptyAllowListProof
);
vm.stopPrank();
}

Expand Down Expand Up @@ -563,6 +601,8 @@ contract RareBatchListingMarketplaceTest is Test {
marketplace.buyWithMerkleProof{value: requiredAmount}(
address(nftContract),
firstTokenId,
address(0),
SALE_PRICE,
creator,
root,
proof,
Expand Down Expand Up @@ -600,6 +640,8 @@ contract RareBatchListingMarketplaceTest is Test {
marketplace.buyWithMerkleProof{value: SALE_PRICE / 2}(
address(nftContract),
firstTokenId,
address(0),
SALE_PRICE,
creator,
root,
proof,
Expand Down Expand Up @@ -653,6 +695,8 @@ contract RareBatchListingMarketplaceTest is Test {
marketplace.buyWithMerkleProof{value: requiredAmount}(
address(nftContract),
targetTokenId,
address(0),
SALE_PRICE,
creator,
root,
proof,
Expand All @@ -672,7 +716,16 @@ contract RareBatchListingMarketplaceTest is Test {
vm.startPrank(buyer);
bytes32[] memory emptyAllowListProof = new bytes32[](0);
vm.expectRevert("buyWithMerkleProof::Merkle root not registered");
marketplace.buyWithMerkleProof(address(nftContract), firstTokenId, creator, root, proof, emptyAllowListProof);
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
proof,
emptyAllowListProof
);
vm.stopPrank();
}

Expand Down Expand Up @@ -711,7 +764,16 @@ contract RareBatchListingMarketplaceTest is Test {
);

vm.startPrank(buyer);
marketplace.buyWithMerkleProof(address(nftContract), tokenIds[i], creator, root, proof, emptyAllowListProof);
marketplace.buyWithMerkleProof(
address(nftContract),
tokenIds[i],
address(currencyContract),
SALE_PRICE,
creator,
root,
proof,
emptyAllowListProof
);
vm.stopPrank();

// Verify ownership
Expand Down Expand Up @@ -797,6 +859,32 @@ contract RareBatchListingMarketplaceTest is Test {
vm.stopPrank();
}

function test_setAllowListConfig_endTimestampInPast() public {
// Create fresh tokens and Merkle tree
(bytes32 root, , , ) = _createFreshMerkleTree(3);

// Register the sale price merkle root first
vm.startPrank(creator);
nftContract.setApprovalForAll(address(erc721ApprovalManager), true);
marketplace.registerSalePriceMerkleRoot(
root,
address(currencyContract),
SALE_PRICE,
salePriceConfig.splitRecipients,
salePriceConfig.splitRatios
);

// Try to set allowlist config with end timestamp in the past
vm.expectRevert("setAllowListConfig::Allow-list end must be in the future");
marketplace.setAllowListConfig(root, bytes32(uint256(1)), block.timestamp - 1);

// Try to set allowlist config with end timestamp equal to current timestamp
vm.expectRevert("setAllowListConfig::Allow-list end must be in the future");
marketplace.setAllowListConfig(root, bytes32(uint256(1)), block.timestamp);

vm.stopPrank();
}

function test_buyWithMerkleProof_withAllowList_success() public {
// Create fresh tokens and Merkle tree
(bytes32 root, bytes32[] memory proof, , uint256 firstTokenId) = _createFreshMerkleTree(3);
Expand Down Expand Up @@ -834,7 +922,16 @@ contract RareBatchListingMarketplaceTest is Test {
currencyContract.approve(address(erc20ApprovalManager), requiredAmount);

// Execute purchase with allowlist proof
marketplace.buyWithMerkleProof(address(nftContract), firstTokenId, creator, root, proof, allowListProof);
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
proof,
allowListProof
);
vm.stopPrank();

// Verify token ownership changed
Expand Down Expand Up @@ -879,7 +976,16 @@ contract RareBatchListingMarketplaceTest is Test {

// Try to purchase without being on allowlist
vm.expectRevert("buyWithMerkleProof::Not on allowlist");
marketplace.buyWithMerkleProof(address(nftContract), firstTokenId, creator, root, proof, allowListProof);
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
proof,
allowListProof
);
vm.stopPrank();
}

Expand Down Expand Up @@ -909,10 +1015,14 @@ contract RareBatchListingMarketplaceTest is Test {
bytes32 allowListRoot = merkle.getRoot(allowListLeaves);
bytes32[] memory allowListProof = merkle.getProof(allowListLeaves, 0); // Get proof for buyer's address

// Set allowlist config with past timestamp
marketplace.setAllowListConfig(root, allowListRoot, block.timestamp - 1);
// Set allowlist config with future timestamp
uint256 endTimestamp = block.timestamp + 1 hours;
marketplace.setAllowListConfig(root, allowListRoot, endTimestamp);
vm.stopPrank();

// Warp time to after the allowlist expiration
vm.warp(endTimestamp + 1);

// Setup: Approve the marketplace to spend buyer's tokens
vm.startPrank(buyer);
uint256 requiredAmount = SALE_PRICE +
Expand All @@ -921,7 +1031,16 @@ contract RareBatchListingMarketplaceTest is Test {

// Try to purchase after allowlist expired
vm.expectRevert("buyWithMerkleProof::Allowlist period has ended");
marketplace.buyWithMerkleProof(address(nftContract), firstTokenId, creator, root, proof, allowListProof);
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
proof,
allowListProof
);
vm.stopPrank();
}

Expand Down Expand Up @@ -1015,4 +1134,121 @@ contract RareBatchListingMarketplaceTest is Test {
marketplace.cancelSalePriceMerkleRoot(nonexistentRoot);
vm.stopPrank();
}

/// @notice Test that zero-length Merkle proofs are rejected for token verification
function test_buyWithMerkleProof_zeroLengthProofRejected() public {
// Create fresh tokens and Merkle tree
(bytes32 root, , , uint256 firstTokenId) = _createFreshMerkleTree(3);

// Register the sale price merkle root
vm.startPrank(creator);
nftContract.setApprovalForAll(address(erc721ApprovalManager), true);
marketplace.registerSalePriceMerkleRoot(
root,
address(currencyContract),
SALE_PRICE,
salePriceConfig.splitRecipients,
salePriceConfig.splitRatios
);
vm.stopPrank();

// Setup: Approve the marketplace to spend buyer's tokens
vm.startPrank(buyer);
uint256 requiredAmount = SALE_PRICE +
IMarketplaceSettings(_marketplaceSettings).calculateMarketplaceFee(SALE_PRICE);
currencyContract.approve(address(erc20ApprovalManager), requiredAmount);

// Try to buy with zero-length proof (should fail)
bytes32[] memory emptyProof = new bytes32[](0);
bytes32[] memory emptyAllowListProof = new bytes32[](0);

vm.expectRevert("buyWithMerkleProof::Proof cannot be empty");
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
emptyProof, // Zero-length proof
emptyAllowListProof
);
vm.stopPrank();
}

/// @notice Test that zero-length allowlist proofs are rejected when allowlist is configured
function test_buyWithMerkleProof_zeroLengthAllowListProofRejected() public {
// Create fresh tokens and Merkle tree
(bytes32 root, bytes32[] memory proof, , uint256 firstTokenId) = _createFreshMerkleTree(3);

// Register the sale price merkle root
vm.startPrank(creator);
nftContract.setApprovalForAll(address(erc721ApprovalManager), true);
marketplace.registerSalePriceMerkleRoot(
root,
address(currencyContract),
SALE_PRICE,
salePriceConfig.splitRecipients,
salePriceConfig.splitRatios
);

// Create allowlist Merkle tree with buyer and another address
address[] memory allowedAddresses = new address[](2);
allowedAddresses[0] = buyer;
allowedAddresses[1] = makeAddr("otherAllowedUser");

bytes32[] memory allowListLeaves = new bytes32[](2);
allowListLeaves[0] = keccak256(abi.encodePacked(buyer));
allowListLeaves[1] = keccak256(abi.encodePacked(allowedAddresses[1]));
bytes32 allowListRoot = merkle.getRoot(allowListLeaves);

// Set allowlist config
marketplace.setAllowListConfig(root, allowListRoot, block.timestamp + 1 days);
vm.stopPrank();

// Setup: Approve the marketplace to spend buyer's tokens
vm.startPrank(buyer);
uint256 requiredAmount = SALE_PRICE +
IMarketplaceSettings(_marketplaceSettings).calculateMarketplaceFee(SALE_PRICE);
currencyContract.approve(address(erc20ApprovalManager), requiredAmount);

// Try to buy with zero-length allowlist proof (should fail)
bytes32[] memory emptyAllowListProof = new bytes32[](0);

vm.expectRevert("buyWithMerkleProof::Allowlist proof cannot be empty");
marketplace.buyWithMerkleProof(
address(nftContract),
firstTokenId,
address(currencyContract),
SALE_PRICE,
creator,
root,
proof,
emptyAllowListProof // Zero-length allowlist proof
);
vm.stopPrank();
}

/// @notice Test that isTokenInRoot returns false for zero-length proofs
function test_isTokenInRoot_zeroLengthProofReturnsFalse() public {
// Create fresh tokens and Merkle tree
(bytes32 root, , , uint256 firstTokenId) = _createFreshMerkleTree(3);

// Create zero-length proof
bytes32[] memory emptyProof = new bytes32[](0);

// Test that zero-length proof returns false
bool result = marketplace.isTokenInRoot(root, address(nftContract), firstTokenId, emptyProof);
assertFalse(result, "Zero-length proof should return false");
}

/// @notice Test that isTokenInRoot works correctly with valid proofs
function test_isTokenInRoot_validProofReturnsTrue() public {
// Create fresh tokens and Merkle tree
(bytes32 root, bytes32[] memory proof, , uint256 firstTokenId) = _createFreshMerkleTree(3);

// Test that valid proof returns true
bool result = marketplace.isTokenInRoot(root, address(nftContract), firstTokenId, proof);
assertTrue(result, "Valid proof should return true");
}
}
Loading