diff --git a/contracts/prebuilts/marketplace/direct-listings/MintraDirectListings.sol b/contracts/prebuilts/marketplace/direct-listings/MintraDirectListings.sol index 1683b503a..371593a31 100644 --- a/contracts/prebuilts/marketplace/direct-listings/MintraDirectListings.sol +++ b/contracts/prebuilts/marketplace/direct-listings/MintraDirectListings.sol @@ -49,6 +49,7 @@ contract MintraDirectListings is IDirectListings, Multicall, ReentrancyGuard { ); event RoyaltyUpdated(address assetContract, uint256 royaltyAmount, address royaltyRecipient); + event PlatformFeeUpdated(uint256 platformFeeBps); address public immutable wizard; address private immutable mintTokenAddress; @@ -679,5 +680,7 @@ contract MintraDirectListings is IDirectListings, Multicall, ReentrancyGuard { require(_platformFeeBps <= 369, "Fee not in range"); platformFeeBps = _platformFeeBps; + + emit PlatformFeeUpdated(_platformFeeBps); } } diff --git a/contracts/prebuilts/marketplace/direct-listings/MintraDirectListingsLogicStandalone.sol b/contracts/prebuilts/marketplace/direct-listings/MintraDirectListingsLogicStandalone.sol deleted file mode 100644 index fc65ac417..000000000 --- a/contracts/prebuilts/marketplace/direct-listings/MintraDirectListingsLogicStandalone.sol +++ /dev/null @@ -1,683 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.11; - -/// @author thirdweb.com / mintra.ai - -import "./DirectListingsStorage.sol"; - -// ====== External imports ====== -import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/interfaces/IERC2981.sol"; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; - -// ====== Internal imports ====== -import "../../../eip/interface/IERC721.sol"; -import "../../../extension/Multicall.sol"; -import "../../../extension/upgradeable/ReentrancyGuard.sol"; -import { CurrencyTransferLib } from "../../../lib/CurrencyTransferLib.sol"; - -/** - * @author thirdweb.com / mintra.ai - */ -contract MintraDirectListingsLogicStandalone is IDirectListings, Multicall, ReentrancyGuard { - /*/////////////////////////////////////////////////////////////// - Mintra - //////////////////////////////////////////////////////////////*/ - struct Royalty { - address receiver; - uint256 basisPoints; - } - - event MintraNewSale( - uint256 listingId, - address buyer, - uint256 quantityBought, - uint256 totalPricePaid, - address currency - ); - - event MintraRoyaltyTransfered( - address assetContract, - uint256 tokenId, - uint256 listingId, - uint256 totalPrice, - uint256 royaltyAmount, - uint256 platformFee, - address royaltyRecipient, - address currency - ); - - event RoyaltyUpdated(address assetContract, uint256 royaltyAmount, address royaltyRecipient); - - address public immutable wizard; - address private immutable mintTokenAddress; - address public immutable platformFeeRecipient; - uint256 public platformFeeBps = 225; - uint256 public platformFeeBpsMint = 150; - mapping(address => Royalty) public royalties; - - /*/////////////////////////////////////////////////////////////// - Constants / Immutables - //////////////////////////////////////////////////////////////*/ - - /// @dev The max bps of the contract. So, 10_000 == 100 % - uint64 private constant MAX_BPS = 10_000; - - /// @dev The address of the native token wrapper contract. - address private immutable nativeTokenWrapper; - - /*/////////////////////////////////////////////////////////////// - Modifier - //////////////////////////////////////////////////////////////*/ - - modifier onlyWizard() { - require(msg.sender == wizard, "Not Wizard"); - _; - } - - /// @dev Checks whether caller is a listing creator. - modifier onlyListingCreator(uint256 _listingId) { - require( - _directListingsStorage().listings[_listingId].listingCreator == msg.sender, - "Marketplace: not listing creator." - ); - _; - } - - /// @dev Checks whether a listing exists. - modifier onlyExistingListing(uint256 _listingId) { - require( - _directListingsStorage().listings[_listingId].status == IDirectListings.Status.CREATED, - "Marketplace: invalid listing." - ); - _; - } - - /*/////////////////////////////////////////////////////////////// - Constructor logic - //////////////////////////////////////////////////////////////*/ - - constructor( - address _nativeTokenWrapper, - address _mintTokenAddress, - address _platformFeeRecipient, - address _wizard - ) { - nativeTokenWrapper = _nativeTokenWrapper; - mintTokenAddress = _mintTokenAddress; - platformFeeRecipient = _platformFeeRecipient; - wizard = _wizard; - } - - /*/////////////////////////////////////////////////////////////// - External functions - //////////////////////////////////////////////////////////////*/ - - /// @notice List NFTs (ERC721 or ERC1155) for sale at a fixed price. - function createListing(ListingParameters calldata _params) external returns (uint256 listingId) { - listingId = _getNextListingId(); - address listingCreator = msg.sender; - TokenType tokenType = _getTokenType(_params.assetContract); - - uint128 startTime = _params.startTimestamp; - uint128 endTime = _params.endTimestamp; - require(startTime < endTime, "Marketplace: endTimestamp not greater than startTimestamp."); - if (startTime < block.timestamp) { - require(startTime + 60 minutes >= block.timestamp, "Marketplace: invalid startTimestamp."); - - startTime = uint128(block.timestamp); - endTime = endTime == type(uint128).max - ? endTime - : startTime + (_params.endTimestamp - _params.startTimestamp); - } - - _validateNewListing(_params, tokenType); - - Listing memory listing = Listing({ - listingId: listingId, - listingCreator: listingCreator, - assetContract: _params.assetContract, - tokenId: _params.tokenId, - quantity: _params.quantity, - currency: _params.currency, - pricePerToken: _params.pricePerToken, - startTimestamp: startTime, - endTimestamp: endTime, - reserved: _params.reserved, - tokenType: tokenType, - status: IDirectListings.Status.CREATED - }); - - _directListingsStorage().listings[listingId] = listing; - - emit NewListing(listingCreator, listingId, _params.assetContract, listing); - - return listingId; - } - - /// @notice Update parameters of a listing of NFTs. - function updateListing( - uint256 _listingId, - ListingParameters memory _params - ) external onlyExistingListing(_listingId) onlyListingCreator(_listingId) { - address listingCreator = msg.sender; - Listing memory listing = _directListingsStorage().listings[_listingId]; - TokenType tokenType = _getTokenType(_params.assetContract); - - require(listing.endTimestamp > block.timestamp, "Marketplace: listing expired."); - - require( - listing.assetContract == _params.assetContract && listing.tokenId == _params.tokenId, - "Marketplace: cannot update what token is listed." - ); - - uint128 startTime = _params.startTimestamp; - uint128 endTime = _params.endTimestamp; - require(startTime < endTime, "Marketplace: endTimestamp not greater than startTimestamp."); - require( - listing.startTimestamp > block.timestamp || - (startTime == listing.startTimestamp && endTime > block.timestamp), - "Marketplace: listing already active." - ); - if (startTime != listing.startTimestamp && startTime < block.timestamp) { - require(startTime + 60 minutes >= block.timestamp, "Marketplace: invalid startTimestamp."); - - startTime = uint128(block.timestamp); - - endTime = endTime == listing.endTimestamp || endTime == type(uint128).max - ? endTime - : startTime + (_params.endTimestamp - _params.startTimestamp); - } - - { - uint256 _approvedCurrencyPrice = _directListingsStorage().currencyPriceForListing[_listingId][ - _params.currency - ]; - require( - _approvedCurrencyPrice == 0 || _params.pricePerToken == _approvedCurrencyPrice, - "Marketplace: price different from approved price" - ); - } - - _validateNewListing(_params, tokenType); - - listing = Listing({ - listingId: _listingId, - listingCreator: listingCreator, - assetContract: _params.assetContract, - tokenId: _params.tokenId, - quantity: _params.quantity, - currency: _params.currency, - pricePerToken: _params.pricePerToken, - startTimestamp: startTime, - endTimestamp: endTime, - reserved: _params.reserved, - tokenType: tokenType, - status: IDirectListings.Status.CREATED - }); - - _directListingsStorage().listings[_listingId] = listing; - - emit UpdatedListing(listingCreator, _listingId, _params.assetContract, listing); - } - - /// @notice Cancel a listing. - function cancelListing(uint256 _listingId) external onlyExistingListing(_listingId) onlyListingCreator(_listingId) { - _directListingsStorage().listings[_listingId].status = IDirectListings.Status.CANCELLED; - emit CancelledListing(msg.sender, _listingId); - } - - /// @notice Approve a buyer to buy from a reserved listing. - function approveBuyerForListing( - uint256 _listingId, - address _buyer, - bool _toApprove - ) external onlyExistingListing(_listingId) onlyListingCreator(_listingId) { - require(_directListingsStorage().listings[_listingId].reserved, "Marketplace: listing not reserved."); - - _directListingsStorage().isBuyerApprovedForListing[_listingId][_buyer] = _toApprove; - - emit BuyerApprovedForListing(_listingId, _buyer, _toApprove); - } - - /// @notice Approve a currency as a form of payment for the listing. - function approveCurrencyForListing( - uint256 _listingId, - address _currency, - uint256 _pricePerTokenInCurrency - ) external onlyExistingListing(_listingId) onlyListingCreator(_listingId) { - Listing memory listing = _directListingsStorage().listings[_listingId]; - require( - _currency != listing.currency || _pricePerTokenInCurrency == listing.pricePerToken, - "Marketplace: approving listing currency with different price." - ); - require( - _directListingsStorage().currencyPriceForListing[_listingId][_currency] != _pricePerTokenInCurrency, - "Marketplace: price unchanged." - ); - - _directListingsStorage().currencyPriceForListing[_listingId][_currency] = _pricePerTokenInCurrency; - - emit CurrencyApprovedForListing(_listingId, _currency, _pricePerTokenInCurrency); - } - - function bulkBuyFromListing( - uint256[] memory _listingId, - address[] memory _buyFor, - uint256[] memory _quantity, - address[] memory _currency, - uint256[] memory _expectedTotalPrice - ) external payable nonReentrant { - uint256 totalAmountPls = 0; - // Iterate over each tokenId - for (uint256 i = 0; i < _listingId.length; i++) { - // Are we buying this item in PLS - uint256 price; - - Listing memory listing = _directListingsStorage().listings[_listingId[i]]; - - require(listing.status == IDirectListings.Status.CREATED, "Marketplace: invalid listing."); - - if (_currency[i] == CurrencyTransferLib.NATIVE_TOKEN) { - //calculate total amount for items being sold for PLS - if (_directListingsStorage().currencyPriceForListing[_listingId[i]][_currency[i]] > 0) { - price = - _quantity[i] * - _directListingsStorage().currencyPriceForListing[_listingId[i]][_currency[i]]; - } else { - require(_currency[i] == listing.currency, "Paying in invalid currency."); - price = _quantity[i] * listing.pricePerToken; - } - - totalAmountPls += price; - } - - // Call the buy function for the current tokenId - _buyFromListing(listing, _buyFor[i], _quantity[i], _currency[i], _expectedTotalPrice[i]); - } - - // Make sure that the total price for items bought with PLS is equal to the amount sent - require(msg.value == totalAmountPls || (totalAmountPls == 0 && msg.value == 0), "Incorrect PLS amount sent"); - } - - /// @notice Buy NFTs from a listing. - function _buyFromListing( - Listing memory listing, - address _buyFor, - uint256 _quantity, - address _currency, - uint256 _expectedTotalPrice - ) internal { - uint256 listingId = listing.listingId; - address buyer = msg.sender; - - require( - !listing.reserved || _directListingsStorage().isBuyerApprovedForListing[listingId][buyer], - "buyer not approved" - ); - require(_quantity > 0 && _quantity <= listing.quantity, "Buying invalid quantity"); - require( - block.timestamp < listing.endTimestamp && block.timestamp >= listing.startTimestamp, - "not within sale window." - ); - - require( - _validateOwnershipAndApproval( - listing.listingCreator, - listing.assetContract, - listing.tokenId, - _quantity, - listing.tokenType - ), - "Marketplace: not owner or approved tokens." - ); - - uint256 targetTotalPrice; - - // Check: is the buyer paying in a currency that the listing creator approved - if (_directListingsStorage().currencyPriceForListing[listingId][_currency] > 0) { - targetTotalPrice = _quantity * _directListingsStorage().currencyPriceForListing[listingId][_currency]; - } else { - require(_currency == listing.currency, "Paying in invalid currency."); - targetTotalPrice = _quantity * listing.pricePerToken; - } - - // Check: is the buyer paying the price that the buyer is expecting to pay. - // This is to prevent attack where the seller could change the price - // right before the buyers tranaction executes. - require(targetTotalPrice == _expectedTotalPrice, "Unexpected total price"); - - if (_currency != CurrencyTransferLib.NATIVE_TOKEN) { - _validateERC20BalAndAllowance(buyer, _currency, targetTotalPrice); - } - - if (listing.quantity == _quantity) { - _directListingsStorage().listings[listingId].status = IDirectListings.Status.COMPLETED; - } - _directListingsStorage().listings[listingId].quantity -= _quantity; - - _payout(buyer, listing.listingCreator, _currency, targetTotalPrice, listing); - - _transferListingTokens(listing.listingCreator, _buyFor, _quantity, listing); - - emit MintraNewSale(listing.listingId, buyer, _quantity, targetTotalPrice, _currency); - } - - /*/////////////////////////////////////////////////////////////// - View functions - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Returns the total number of listings created. - * @dev At any point, the return value is the ID of the next listing created. - */ - function totalListings() external view returns (uint256) { - return _directListingsStorage().totalListings; - } - - /// @notice Returns whether a buyer is approved for a listing. - function isBuyerApprovedForListing(uint256 _listingId, address _buyer) external view returns (bool) { - return _directListingsStorage().isBuyerApprovedForListing[_listingId][_buyer]; - } - - /// @notice Returns whether a currency is approved for a listing. - function isCurrencyApprovedForListing(uint256 _listingId, address _currency) external view returns (bool) { - return _directListingsStorage().currencyPriceForListing[_listingId][_currency] > 0; - } - - /// @notice Returns the price per token for a listing, in the given currency. - function currencyPriceForListing(uint256 _listingId, address _currency) external view returns (uint256) { - if (_directListingsStorage().currencyPriceForListing[_listingId][_currency] == 0) { - revert("Currency not approved for listing"); - } - - return _directListingsStorage().currencyPriceForListing[_listingId][_currency]; - } - - /// @notice Returns all non-cancelled listings. - function getAllListings(uint256 _startId, uint256 _endId) external view returns (Listing[] memory _allListings) { - require(_startId <= _endId && _endId < _directListingsStorage().totalListings, "invalid range"); - - _allListings = new Listing[](_endId - _startId + 1); - - for (uint256 i = _startId; i <= _endId; i += 1) { - _allListings[i - _startId] = _directListingsStorage().listings[i]; - } - } - - /** - * @notice Returns all valid listings between the start and end Id (both inclusive) provided. - * A valid listing is where the listing creator still owns and has approved Marketplace - * to transfer the listed NFTs. - */ - function getAllValidListings( - uint256 _startId, - uint256 _endId - ) external view returns (Listing[] memory _validListings) { - require(_startId <= _endId && _endId < _directListingsStorage().totalListings, "invalid range"); - - Listing[] memory _listings = new Listing[](_endId - _startId + 1); - uint256 _listingCount; - - for (uint256 i = _startId; i <= _endId; i += 1) { - _listings[i - _startId] = _directListingsStorage().listings[i]; - if (_validateExistingListing(_listings[i - _startId])) { - _listingCount += 1; - } - } - - _validListings = new Listing[](_listingCount); - uint256 index = 0; - uint256 count = _listings.length; - for (uint256 i = 0; i < count; i += 1) { - if (_validateExistingListing(_listings[i])) { - _validListings[index++] = _listings[i]; - } - } - } - - /// @notice Returns a listing at a particular listing ID. - function getListing(uint256 _listingId) external view returns (Listing memory listing) { - listing = _directListingsStorage().listings[_listingId]; - } - - /** - * @notice Set or update the royalty for a collection - * @dev Sets or updates the royalty for a collection to a new value - * @param _collectionAddress Address of the collection to set the royalty for - * @param _royaltyInBasisPoints New royalty value, in basis points (1 basis point = 0.01%) - */ - function createOrUpdateRoyalty( - address _collectionAddress, - uint256 _royaltyInBasisPoints, - address receiver - ) public nonReentrant { - require(_collectionAddress != address(0), "_collectionAddress is not set"); - require(_royaltyInBasisPoints >= 0 && _royaltyInBasisPoints <= 10000, "Royalty not in range"); - require(receiver != address(0), "receiver is not set"); - - // Check that the caller is the owner/creator of the collection contract - require(Ownable(_collectionAddress).owner() == msg.sender, "Unauthorized"); - - // Create a new Royalty object with the given value and store it in the royalties mapping - Royalty memory royalty = Royalty(receiver, _royaltyInBasisPoints); - royalties[_collectionAddress] = royalty; - - // Emit a RoyaltyUpdated - emit RoyaltyUpdated(_collectionAddress, _royaltyInBasisPoints, receiver); - } - - /*/////////////////////////////////////////////////////////////// - Internal functions - //////////////////////////////////////////////////////////////*/ - - /// @dev Returns the next listing Id. - function _getNextListingId() internal returns (uint256 id) { - id = _directListingsStorage().totalListings; - _directListingsStorage().totalListings += 1; - } - - /// @dev Returns the interface supported by a contract. - function _getTokenType(address _assetContract) internal view returns (TokenType tokenType) { - if (IERC165(_assetContract).supportsInterface(type(IERC1155).interfaceId)) { - tokenType = TokenType.ERC1155; - } else if (IERC165(_assetContract).supportsInterface(type(IERC721).interfaceId)) { - tokenType = TokenType.ERC721; - } else { - revert("Marketplace: listed token must be ERC1155 or ERC721."); - } - } - - /// @dev Checks whether the listing creator owns and has approved marketplace to transfer listed tokens. - function _validateNewListing(ListingParameters memory _params, TokenType _tokenType) internal view { - require(_params.quantity > 0, "Marketplace: listing zero quantity."); - require(_params.quantity == 1 || _tokenType == TokenType.ERC1155, "Marketplace: listing invalid quantity."); - - require( - _validateOwnershipAndApproval( - msg.sender, - _params.assetContract, - _params.tokenId, - _params.quantity, - _tokenType - ), - "Marketplace: not owner or approved tokens." - ); - } - - /// @dev Checks whether the listing exists, is active, and if the lister has sufficient balance. - function _validateExistingListing(Listing memory _targetListing) internal view returns (bool isValid) { - isValid = - _targetListing.startTimestamp <= block.timestamp && - _targetListing.endTimestamp > block.timestamp && - _targetListing.status == IDirectListings.Status.CREATED && - _validateOwnershipAndApproval( - _targetListing.listingCreator, - _targetListing.assetContract, - _targetListing.tokenId, - _targetListing.quantity, - _targetListing.tokenType - ); - } - - /// @dev Validates that `_tokenOwner` owns and has approved Marketplace to transfer NFTs. - function _validateOwnershipAndApproval( - address _tokenOwner, - address _assetContract, - uint256 _tokenId, - uint256 _quantity, - TokenType _tokenType - ) internal view returns (bool isValid) { - address market = address(this); - - if (_tokenType == TokenType.ERC1155) { - isValid = - IERC1155(_assetContract).balanceOf(_tokenOwner, _tokenId) >= _quantity && - IERC1155(_assetContract).isApprovedForAll(_tokenOwner, market); - } else if (_tokenType == TokenType.ERC721) { - address owner; - address operator; - - // failsafe for reverts in case of non-existent tokens - try IERC721(_assetContract).ownerOf(_tokenId) returns (address _owner) { - owner = _owner; - - // Nesting the approval check inside this try block, to run only if owner check doesn't revert. - // If the previous check for owner fails, then the return value will always evaluate to false. - try IERC721(_assetContract).getApproved(_tokenId) returns (address _operator) { - operator = _operator; - } catch {} - } catch {} - - isValid = - owner == _tokenOwner && - (operator == market || IERC721(_assetContract).isApprovedForAll(_tokenOwner, market)); - } - } - - /// @dev Validates that `_tokenOwner` owns and has approved Markeplace to transfer the appropriate amount of currency - function _validateERC20BalAndAllowance(address _tokenOwner, address _currency, uint256 _amount) internal view { - require( - IERC20(_currency).balanceOf(_tokenOwner) >= _amount && - IERC20(_currency).allowance(_tokenOwner, address(this)) >= _amount, - "!BAL20" - ); - } - - /// @dev Transfers tokens listed for sale in a direct or auction listing. - function _transferListingTokens(address _from, address _to, uint256 _quantity, Listing memory _listing) internal { - if (_listing.tokenType == TokenType.ERC1155) { - IERC1155(_listing.assetContract).safeTransferFrom(_from, _to, _listing.tokenId, _quantity, ""); - } else if (_listing.tokenType == TokenType.ERC721) { - IERC721(_listing.assetContract).safeTransferFrom(_from, _to, _listing.tokenId, ""); - } - } - - /// @dev Pays out stakeholders in a sale. - function _payout( - address _payer, - address _payee, - address _currencyToUse, - uint256 _totalPayoutAmount, - Listing memory _listing - ) internal { - uint256 amountRemaining; - uint256 platformFeeCut; - - // Payout platform fee - { - // Descrease platform fee for mint token - if (_currencyToUse == mintTokenAddress) { - platformFeeCut = (_totalPayoutAmount * platformFeeBpsMint) / MAX_BPS; - } else { - platformFeeCut = (_totalPayoutAmount * platformFeeBps) / MAX_BPS; - } - - // Transfer platform fee - CurrencyTransferLib.transferCurrency(_currencyToUse, _payer, platformFeeRecipient, platformFeeCut); - - amountRemaining = _totalPayoutAmount - platformFeeCut; - } - - // Payout royalties - { - // Get royalty recipients and amounts - (address royaltyRecipient, uint256 royaltyAmount) = processRoyalty( - _listing.assetContract, - _listing.tokenId, - _totalPayoutAmount - ); - - if (royaltyAmount > 0) { - // Check payout amount remaining is enough to cover royalty payment - require(amountRemaining >= royaltyAmount, "fees exceed the price"); - - // Transfer royalty - CurrencyTransferLib.transferCurrency(_currencyToUse, _payer, royaltyRecipient, royaltyAmount); - - amountRemaining = amountRemaining - royaltyAmount; - - emit MintraRoyaltyTransfered( - _listing.assetContract, - _listing.tokenId, - _listing.listingId, - _totalPayoutAmount, - royaltyAmount, - platformFeeCut, - royaltyRecipient, - _currencyToUse - ); - } - } - - // Distribute price to token owner - CurrencyTransferLib.transferCurrency(_currencyToUse, _payer, _payee, amountRemaining); - } - - function processRoyalty( - address _tokenAddress, - uint256 _tokenId, - uint256 _price - ) internal view returns (address royaltyReceiver, uint256 royaltyAmount) { - // Check if collection has royalty using ERC2981 - if (isERC2981(_tokenAddress)) { - (royaltyReceiver, royaltyAmount) = IERC2981(_tokenAddress).royaltyInfo(_tokenId, _price); - } else { - royaltyAmount = (_price * royalties[_tokenAddress].basisPoints) / 10000; - royaltyReceiver = royalties[_tokenAddress].receiver; - } - - return (royaltyReceiver, royaltyAmount); - } - - /** - * @notice This function checks if a given contract is ERC2981 compliant - * @dev This function is called internally and cannot be accessed outside the contract - * @param _contract The address of the contract to check - * @return A boolean indicating whether the contract is ERC2981 compliant or not - */ - function isERC2981(address _contract) internal view returns (bool) { - try IERC2981(_contract).royaltyInfo(0, 0) returns (address, uint256) { - return true; - } catch { - return false; - } - } - - /// @dev Returns the DirectListings storage. - function _directListingsStorage() internal pure returns (DirectListingsStorage.Data storage data) { - data = DirectListingsStorage.data(); - } - - /** - * @notice Update the market fee percentage - * @dev Updates the market fee percentage to a new value - * @param _platformFeeBps New value for the market fee percentage - */ - function setPlatformFeeBps(uint256 _platformFeeBps) public onlyWizard { - require(_platformFeeBps <= 369, "Fee not in range"); - - platformFeeBps = _platformFeeBps; - } -} diff --git a/src/test/marketplace/MintraDirectListingStandalone.t.sol b/src/test/marketplace/MintraDirectListingStandalone.t.sol deleted file mode 100644 index 55c3c4f7d..000000000 --- a/src/test/marketplace/MintraDirectListingStandalone.t.sol +++ /dev/null @@ -1,2348 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -// Test helper imports -import "../utils/BaseTest.sol"; - -// Test contracts and interfaces -import { RoyaltyPaymentsLogic } from "contracts/extension/plugin/RoyaltyPayments.sol"; -import { MarketplaceV3, IPlatformFee } from "contracts/prebuilts/marketplace/entrypoint/MarketplaceV3.sol"; -import { TWProxy } from "contracts/infra/TWProxy.sol"; -import { ERC721Base } from "contracts/base/ERC721Base.sol"; -import { MockRoyaltyEngineV1 } from "../mocks/MockRoyaltyEngineV1.sol"; - -import { IDirectListings } from "contracts/prebuilts/marketplace/IMarketplace.sol"; -import { MintraDirectListingsLogicStandalone } from "contracts/prebuilts/marketplace/direct-listings/MintraDirectListingsLogicStandalone.sol"; -import "@thirdweb-dev/dynamic-contracts/src/interface/IExtension.sol"; -import { MockERC721Ownable } from "../mocks/MockERC721Ownable.sol"; - -contract MintraDirectListingsLogicStandaloneTest is BaseTest, IExtension { - // Target contract - address public marketplace; - - // Participants - address public marketplaceDeployer; - address public seller; - address public buyer; - address public wizard; - address public collectionOwner; - - MintraDirectListingsLogicStandalone public mintraDirectListingsLogicStandalone; - MockERC721Ownable public erc721Ownable; - - function setUp() public override { - super.setUp(); - - marketplaceDeployer = getActor(1); - seller = getActor(2); - buyer = getActor(3); - wizard = getActor(4); - collectionOwner = getActor(5); - - // Deploy implementation. - mintraDirectListingsLogicStandalone = new MintraDirectListingsLogicStandalone( - address(weth), - address(erc20Aux), - address(platformFeeRecipient), - address(wizard) - ); - marketplace = address(mintraDirectListingsLogicStandalone); - - vm.prank(collectionOwner); - erc721Ownable = new MockERC721Ownable(); - - //vm.prank(marketplaceDeployer); - - vm.label(marketplace, "Marketplace"); - vm.label(seller, "Seller"); - vm.label(buyer, "Buyer"); - vm.label(address(erc721), "ERC721_Token"); - vm.label(address(erc1155), "ERC1155_Token"); - } - - function _setupERC721BalanceForSeller(address _seller, uint256 _numOfTokens) private { - erc721.mint(_seller, _numOfTokens); - } - - function test_state_initial() public { - uint256 totalListings = MintraDirectListingsLogicStandalone(marketplace).totalListings(); - assertEq(totalListings, 0); - } - - /*/////////////////////////////////////////////////////////////// - Miscellaneous - //////////////////////////////////////////////////////////////*/ - - function test_getValidListings_burnListedTokens() public { - // Sample listing parameters. - address assetContract = address(erc721); - uint256 tokenId = 0; - uint256 quantity = 1; - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = true; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(seller, 1); - - // Approve Marketplace to transfer token. - vm.prank(seller); - erc721.setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - - // Total listings incremented - assertEq(MintraDirectListingsLogicStandalone(marketplace).totalListings(), 1); - - // burn listed token - vm.prank(seller); - erc721.burn(0); - - vm.warp(150); - // Fetch listing and verify state. - uint256 totalListings = MintraDirectListingsLogicStandalone(marketplace).totalListings(); - assertEq(MintraDirectListingsLogicStandalone(marketplace).getAllValidListings(0, totalListings - 1).length, 0); - } - - function test_state_approvedCurrencies() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParams) = _setup_updateListing(0); - address currencyToApprove = address(erc20); // same currency as main listing - uint256 pricePerTokenForCurrency = 2 ether; - - // Seller approves currency for listing. - vm.prank(seller); - vm.expectRevert("Marketplace: approving listing currency with different price."); - MintraDirectListingsLogicStandalone(marketplace).approveCurrencyForListing( - listingId, - currencyToApprove, - pricePerTokenForCurrency - ); - - // change currency - currencyToApprove = NATIVE_TOKEN; - - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveCurrencyForListing( - listingId, - currencyToApprove, - pricePerTokenForCurrency - ); - - assertEq( - MintraDirectListingsLogicStandalone(marketplace).isCurrencyApprovedForListing(listingId, NATIVE_TOKEN), - true - ); - assertEq( - MintraDirectListingsLogicStandalone(marketplace).currencyPriceForListing(listingId, NATIVE_TOKEN), - pricePerTokenForCurrency - ); - - // should revert when updating listing with an approved currency but different price - listingParams.currency = NATIVE_TOKEN; - vm.prank(seller); - vm.expectRevert("Marketplace: price different from approved price"); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParams); - - // change listingParams.pricePerToken to approved price - listingParams.pricePerToken = pricePerTokenForCurrency; - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParams); - } - - /*/////////////////////////////////////////////////////////////// - Royalty Tests (incl Royalty Engine / Registry) - //////////////////////////////////////////////////////////////*/ - - function _setupListingForRoyaltyTests(address erc721TokenAddress) private returns (uint256 listingId) { - // Sample listing parameters. - address assetContract = erc721TokenAddress; - uint256 tokenId = 0; - uint256 quantity = 1; - address currency = address(erc20); - uint256 pricePerToken = 100 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = false; - - // Approve Marketplace to transfer token. - vm.prank(seller); - IERC721(erc721TokenAddress).setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - listingId = MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - } - - function _buyFromListingForRoyaltyTests(uint256 listingId) private returns (uint256 totalPrice) { - IDirectListings.Listing memory listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - address currency = listing.currency; - uint256 pricePerToken = listing.pricePerToken; - totalPrice = pricePerToken * quantityToBuy; - - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice); - - // Approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice); - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - function test_noRoyaltyEngine_defaultERC2981Token() public { - // create token with ERC2981 - address royaltyRecipient = address(0x12345); - uint128 royaltyBps = 10; - uint256 platformFeeBps = MintraDirectListingsLogicStandalone(marketplace).platformFeeBps(); - ERC721Base nft2981 = new ERC721Base(address(0x12345), "NFT 2981", "NFT2981", royaltyRecipient, royaltyBps); - vm.prank(address(0x12345)); - nft2981.mintTo(seller, ""); - - // 1. ========= Create listing ========= - - uint256 listingId = _setupListingForRoyaltyTests(address(nft2981)); - - // 2. ========= Buy from listing ========= - - uint256 totalPrice = _buyFromListingForRoyaltyTests(listingId); - - // 3. ======== Check balances after royalty payments ======== - - { - uint256 platforfee = (platformFeeBps * totalPrice) / 10_000; - uint256 royaltyAmount = (royaltyBps * totalPrice) / 10_000; - - assertBalERC20Eq(address(erc20), platformFeeRecipient, platforfee); - - // Royalty recipient receives correct amounts - assertBalERC20Eq(address(erc20), royaltyRecipient, royaltyAmount); - - // Seller gets total price minus royalty amount minus platform fee - assertBalERC20Eq(address(erc20), seller, totalPrice - royaltyAmount - platforfee); - } - } - - function test_revert_mintra_native_royalty_feesExceedTotalPrice() public { - // Set native royalty too high - vm.prank(collectionOwner); - mintraDirectListingsLogicStandalone.createOrUpdateRoyalty(address(erc721Ownable), 10000, factoryAdmin); - - // 1. ========= Create listing ========= - erc721Ownable.mint(seller, 1); - uint256 listingId = _setupListingForRoyaltyTests(address(erc721Ownable)); - - // 2. ========= Buy from listing ========= - IDirectListings.Listing memory listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - address currency = listing.currency; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice); - // Approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice); - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.expectRevert("fees exceed the price"); - vm.prank(buyer); - - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - function test_revert_erc2981_royalty_feesExceedTotalPrice() public { - // Set erc2981 royalty too high - ERC721Base nft2981 = new ERC721Base(address(0x12345), "NFT 2981", "NFT2981", royaltyRecipient, 10000); - - // 1. ========= Create listing ========= - vm.prank(address(0x12345)); - nft2981.mintTo(seller, ""); - uint256 listingId = _setupListingForRoyaltyTests(address(nft2981)); - - // 2. ========= Buy from listing ========= - IDirectListings.Listing memory listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - address currency = listing.currency; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice); - // Approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice); - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.expectRevert("fees exceed the price"); - vm.prank(buyer); - - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - /*/////////////////////////////////////////////////////////////// - Create listing - //////////////////////////////////////////////////////////////*/ - - function createListing_1155(uint256 tokenId, uint256 totalListings) private returns (uint256 listingId) { - // Sample listing parameters. - address assetContract = address(erc1155); - uint256 quantity = 2; - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = false; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(seller, 1); - erc1155.mint(seller, tokenId, quantity, ""); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = quantity; - - assertBalERC1155Eq(address(erc1155), seller, tokenIds, amounts); - - // Approve Marketplace to transfer token. - vm.prank(seller); - erc1155.setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - listingId = MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - - // Test consequent state of the contract. - - // Seller is still owner of token. - assertBalERC1155Eq(address(erc1155), seller, tokenIds, amounts); - - // Total listings incremented - assertEq(MintraDirectListingsLogicStandalone(marketplace).totalListings(), totalListings); - - // Fetch listing and verify state. - IDirectListings.Listing memory listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - - assertEq(listing.listingId, listingId); - assertEq(listing.listingCreator, seller); - assertEq(listing.assetContract, assetContract); - assertEq(listing.tokenId, tokenId); - assertEq(listing.quantity, quantity); - assertEq(listing.currency, currency); - assertEq(listing.pricePerToken, pricePerToken); - assertEq(listing.startTimestamp, startTimestamp); - assertEq(listing.endTimestamp, endTimestamp); - assertEq(listing.reserved, reserved); - assertEq(uint256(listing.tokenType), uint256(IDirectListings.TokenType.ERC1155)); - - return listingId; - } - - function test_state_createListing() public { - // Sample listing parameters. - address assetContract = address(erc721); - uint256 tokenId = 0; - uint256 quantity = 1; - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = true; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Approve Marketplace to transfer token. - vm.prank(seller); - erc721.setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - uint256 listingId = MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - - // Test consequent state of the contract. - - // Seller is still owner of token. - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Total listings incremented - assertEq(MintraDirectListingsLogicStandalone(marketplace).totalListings(), 1); - - // Fetch listing and verify state. - IDirectListings.Listing memory listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - - assertEq(listing.listingId, listingId); - assertEq(listing.listingCreator, seller); - assertEq(listing.assetContract, assetContract); - assertEq(listing.tokenId, tokenId); - assertEq(listing.quantity, quantity); - assertEq(listing.currency, currency); - assertEq(listing.pricePerToken, pricePerToken); - assertEq(listing.startTimestamp, startTimestamp); - assertEq(listing.endTimestamp, endTimestamp); - assertEq(listing.reserved, reserved); - assertEq(uint256(listing.tokenType), uint256(IDirectListings.TokenType.ERC721)); - } - - function test_state_createListing_start_time_in_past() public { - // Sample listing parameters. - address assetContract = address(erc721); - uint256 tokenId = 0; - uint256 quantity = 1; - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - - vm.warp(10000); // Set the timestop for block 1 to 10000 - - uint256 expectedStartTimestamp = 10000; - uint256 expectedEndTimestamp = type(uint128).max; - // Set the start time to be at a timestamp in the past - uint128 startTimestamp = uint128(block.timestamp) - 1000; - - uint128 endTimestamp = type(uint128).max; - bool reserved = true; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Approve Marketplace to transfer token. - vm.prank(seller); - erc721.setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - uint256 listingId = MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - - // Test consequent state of the contract. - - // Seller is still owner of token. - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Total listings incremented - assertEq(MintraDirectListingsLogicStandalone(marketplace).totalListings(), 1); - - // Fetch listing and verify state. - IDirectListings.Listing memory listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - - assertEq(listing.listingId, listingId); - assertEq(listing.listingCreator, seller); - assertEq(listing.assetContract, assetContract); - assertEq(listing.tokenId, tokenId); - assertEq(listing.quantity, quantity); - assertEq(listing.currency, currency); - assertEq(listing.pricePerToken, pricePerToken); - assertEq(listing.startTimestamp, expectedStartTimestamp); - assertEq(listing.endTimestamp, expectedEndTimestamp); - assertEq(listing.reserved, reserved); - assertEq(uint256(listing.tokenType), uint256(IDirectListings.TokenType.ERC721)); - } - - function test_revert_createListing_notOwnerOfListedToken() public { - // Sample listing parameters. - address assetContract = address(erc721); - uint256 tokenId = 0; - uint256 quantity = 1; - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = true; - - // Don't mint to 'token to be listed' to the seller. - address someWallet = getActor(1000); - _setupERC721BalanceForSeller(someWallet, 1); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - assertIsOwnerERC721(address(erc721), someWallet, tokenIds); - assertIsNotOwnerERC721(address(erc721), seller, tokenIds); - - // Approve Marketplace to transfer token. - vm.prank(someWallet); - erc721.setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - vm.expectRevert("Marketplace: not owner or approved tokens."); - MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - } - - function test_revert_createListing_notApprovedMarketplaceToTransferToken() public { - // Sample listing parameters. - address assetContract = address(erc721); - uint256 tokenId = 0; - uint256 quantity = 1; - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = true; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Don't approve Marketplace to transfer token. - vm.prank(seller); - erc721.setApprovalForAll(marketplace, false); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - vm.expectRevert("Marketplace: not owner or approved tokens."); - MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - } - - function test_revert_createListing_listingZeroQuantity() public { - // Sample listing parameters. - address assetContract = address(erc721); - uint256 tokenId = 0; - uint256 quantity = 0; // Listing ZERO quantity - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = true; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Approve Marketplace to transfer token. - vm.prank(seller); - erc721.setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - vm.expectRevert("Marketplace: listing zero quantity."); - MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - } - - function test_revert_createListing_listingInvalidQuantity() public { - // Sample listing parameters. - address assetContract = address(erc721); - uint256 tokenId = 0; - uint256 quantity = 2; // Listing more than `1` quantity - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = true; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Approve Marketplace to transfer token. - vm.prank(seller); - erc721.setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - vm.expectRevert("Marketplace: listing invalid quantity."); - MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - } - - function test_revert_createListing_invalidStartTimestamp() public { - uint256 blockTimestamp = 100 minutes; - // Set block.timestamp - vm.warp(blockTimestamp); - - // Sample listing parameters. - address assetContract = address(erc721); - uint256 tokenId = 0; - uint256 quantity = 1; - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = uint128(blockTimestamp - 61 minutes); // start time is less than block timestamp. - uint128 endTimestamp = uint128(startTimestamp + 1); - bool reserved = true; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Approve Marketplace to transfer token. - vm.prank(seller); - erc721.setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - vm.expectRevert("Marketplace: invalid startTimestamp."); - MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - } - - function test_revert_createListing_invalidEndTimestamp() public { - // Sample listing parameters. - address assetContract = address(erc721); - uint256 tokenId = 0; - uint256 quantity = 1; - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = uint128(startTimestamp - 1); // End timestamp is less than start timestamp. - bool reserved = true; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Approve Marketplace to transfer token. - vm.prank(seller); - erc721.setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - vm.expectRevert("Marketplace: endTimestamp not greater than startTimestamp."); - MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - } - - function test_revert_createListing_listingNonERC721OrERC1155Token() public { - // Sample listing parameters. - address assetContract = address(erc20); - uint256 tokenId = 0; - uint256 quantity = 1; - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = true; - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.expectRevert("Marketplace: listed token must be ERC1155 or ERC721."); - MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - } - - /*/////////////////////////////////////////////////////////////// - Update listing - //////////////////////////////////////////////////////////////*/ - - function _setup_updateListing( - uint256 tokenId - ) private returns (uint256 listingId, IDirectListings.ListingParameters memory listingParams) { - // listing parameters. - address assetContract = address(erc721); - uint256 quantity = 1; - address currency = address(erc20); - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = true; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Approve Marketplace to transfer token. - vm.prank(seller); - erc721.setApprovalForAll(marketplace, true); - - // List tokens. - listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(seller); - listingId = MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - } - - function test_state_updateListing() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - - // Mint MORE ERC721 tokens to seller. A new tokenId will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - listingParamsToUpdate.pricePerToken = 2 ether; - - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - - // Test consequent state of the contract. - - // Seller is still owner of token. - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Total listings not incremented on update. - assertEq(MintraDirectListingsLogicStandalone(marketplace).totalListings(), 1); - - // Fetch listing and verify state. - IDirectListings.Listing memory listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - - assertEq(listing.listingId, listingId); - assertEq(listing.listingCreator, seller); - assertEq(listing.assetContract, listingParamsToUpdate.assetContract); - assertEq(listing.tokenId, 0); - assertEq(listing.quantity, listingParamsToUpdate.quantity); - assertEq(listing.currency, listingParamsToUpdate.currency); - assertEq(listing.pricePerToken, listingParamsToUpdate.pricePerToken); - assertEq(listing.startTimestamp, listingParamsToUpdate.startTimestamp); - assertEq(listing.endTimestamp, listingParamsToUpdate.endTimestamp); - assertEq(listing.reserved, listingParamsToUpdate.reserved); - assertEq(uint256(listing.tokenType), uint256(IDirectListings.TokenType.ERC721)); - } - - function test_state_updateListing_start_time_in_past() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - - // Mint MORE ERC721 tokens to seller. A new tokenId will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - listingParamsToUpdate.pricePerToken = 2 ether; - - // Update the start time of the listing - uint256 expectedStartTimestamp = block.timestamp + 10; - uint256 expectedEndTimestamp = type(uint128).max; - - listingParamsToUpdate.startTimestamp = uint128(block.timestamp); - listingParamsToUpdate.endTimestamp = type(uint128).max; - vm.warp(block.timestamp + 10); // Set the timestamp 10 seconds in the future - - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - - // Test consequent state of the contract. - - // Seller is still owner of token. - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Total listings not incremented on update. - assertEq(MintraDirectListingsLogicStandalone(marketplace).totalListings(), 1); - - // Fetch listing and verify state. - IDirectListings.Listing memory listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - - assertEq(listing.listingId, listingId); - assertEq(listing.listingCreator, seller); - assertEq(listing.assetContract, listingParamsToUpdate.assetContract); - assertEq(listing.tokenId, 0); - assertEq(listing.quantity, listingParamsToUpdate.quantity); - assertEq(listing.currency, listingParamsToUpdate.currency); - assertEq(listing.pricePerToken, listingParamsToUpdate.pricePerToken); - assertEq(listing.startTimestamp, expectedStartTimestamp); - assertEq(listing.endTimestamp, expectedEndTimestamp); - assertEq(listing.reserved, listingParamsToUpdate.reserved); - assertEq(uint256(listing.tokenType), uint256(IDirectListings.TokenType.ERC721)); - } - - function test_revert_updateListing_notListingCreator() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - - // Mint MORE ERC721 tokens to seller. A new tokenId will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - address notSeller = getActor(1000); // Someone other than the seller calls update. - vm.prank(notSeller); - vm.expectRevert("Marketplace: not listing creator."); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - } - - function test_revert_updateListing_notOwnerOfListedToken() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - - // Mint MORE ERC721 tokens but NOT to seller. A new tokenId will be listed. - address notSeller = getActor(1000); - _setupERC721BalanceForSeller(notSeller, 1); - - // Approve Marketplace to transfer token. - vm.prank(notSeller); - erc721.setApprovalForAll(marketplace, true); - - // Transfer away owned token. - vm.prank(seller); - erc721.transferFrom(seller, address(0x1234), 0); - - vm.prank(seller); - vm.expectRevert("Marketplace: not owner or approved tokens."); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - } - - function test_revert_updateListing_notApprovedMarketplaceToTransferToken() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - - // Mint MORE ERC721 tokens to seller. A new tokenId will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - // Don't approve Marketplace to transfer token. - vm.prank(seller); - erc721.setApprovalForAll(marketplace, false); - - vm.prank(seller); - vm.expectRevert("Marketplace: not owner or approved tokens."); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - } - - function test_revert_updateListing_listingZeroQuantity() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - - // Mint MORE ERC721 tokens to seller. A new tokenId will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - listingParamsToUpdate.quantity = 0; // Listing zero quantity - - vm.prank(seller); - vm.expectRevert("Marketplace: listing zero quantity."); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - } - - function test_revert_updateListing_listingInvalidQuantity() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - - // Mint MORE ERC721 tokens to seller. A new tokenId will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - listingParamsToUpdate.quantity = 2; // Listing more than `1` of the ERC721 token - - vm.prank(seller); - vm.expectRevert("Marketplace: listing invalid quantity."); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - } - - function test_revert_updateListing_listingNonERC721OrERC1155Token() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - - // Mint MORE ERC721 tokens to seller. A new tokenId will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - listingParamsToUpdate.assetContract = address(erc20); // Listing non ERC721 / ERC1155 token. - - vm.prank(seller); - vm.expectRevert("Marketplace: listed token must be ERC1155 or ERC721."); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - } - - function test_revert_updateListing_invalidStartTimestamp() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - - // Mint MORE ERC721 tokens to seller. A new tokenId will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - uint128 currentStartTimestamp = listingParamsToUpdate.startTimestamp; - listingParamsToUpdate.startTimestamp = currentStartTimestamp - 1; // Retroactively decreasing startTimestamp. - - vm.warp(currentStartTimestamp + 50); - vm.prank(seller); - vm.expectRevert("Marketplace: listing already active."); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - } - - function test_revert_updateListing_invalidEndTimestamp() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - - // Mint MORE ERC721 tokens to seller. A new tokenId will be listed. - _setupERC721BalanceForSeller(seller, 1); - - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - - uint128 currentStartTimestamp = listingParamsToUpdate.startTimestamp; - listingParamsToUpdate.endTimestamp = currentStartTimestamp - 1; // End timestamp less than startTimestamp - - vm.prank(seller); - vm.expectRevert("Marketplace: endTimestamp not greater than startTimestamp."); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - } - - /*/////////////////////////////////////////////////////////////// - Cancel listing - //////////////////////////////////////////////////////////////*/ - - function _setup_cancelListing( - uint256 tokenId - ) private returns (uint256 listingId, IDirectListings.Listing memory listing) { - (listingId, ) = _setup_updateListing(tokenId); - listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - } - - function test_state_cancelListing() public { - (uint256 listingId, IDirectListings.Listing memory existingListingAtId) = _setup_cancelListing(0); - - // Verify existing listing at `listingId` - assertEq(existingListingAtId.assetContract, address(erc721)); - - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).cancelListing(listingId); - - // status should be `CANCELLED` - IDirectListings.Listing memory cancelledListing = MintraDirectListingsLogicStandalone(marketplace).getListing( - listingId - ); - assertTrue(cancelledListing.status == IDirectListings.Status.CANCELLED); - } - - function test_revert_cancelListing_notListingCreator() public { - (uint256 listingId, IDirectListings.Listing memory existingListingAtId) = _setup_cancelListing(0); - - // Verify existing listing at `listingId` - assertEq(existingListingAtId.assetContract, address(erc721)); - - address notSeller = getActor(1000); - vm.prank(notSeller); - vm.expectRevert("Marketplace: not listing creator."); - MintraDirectListingsLogicStandalone(marketplace).cancelListing(listingId); - } - - function test_revert_cancelListing_nonExistentListing() public { - _setup_cancelListing(0); - - // Verify no listing exists at `nexListingId` - uint256 nextListingId = MintraDirectListingsLogicStandalone(marketplace).totalListings(); - - vm.prank(seller); - vm.expectRevert("Marketplace: invalid listing."); - MintraDirectListingsLogicStandalone(marketplace).cancelListing(nextListingId); - } - - /*/////////////////////////////////////////////////////////////// - Approve buyer for listing - //////////////////////////////////////////////////////////////*/ - - function _setup_approveBuyerForListing(uint256 tokenId) private returns (uint256 listingId) { - (listingId, ) = _setup_updateListing(tokenId); - } - - function test_state_approveBuyerForListing() public { - uint256 listingId = _setup_approveBuyerForListing(0); - bool toApprove = true; - - assertEq(MintraDirectListingsLogicStandalone(marketplace).getListing(listingId).reserved, true); - - // Seller approves buyer for reserved listing. - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, toApprove); - - assertEq(MintraDirectListingsLogicStandalone(marketplace).isBuyerApprovedForListing(listingId, buyer), true); - } - - function test_revert_approveBuyerForListing_notListingCreator() public { - uint256 listingId = _setup_approveBuyerForListing(0); - bool toApprove = true; - - assertEq(MintraDirectListingsLogicStandalone(marketplace).getListing(listingId).reserved, true); - - // Someone other than the seller approves buyer for reserved listing. - address notSeller = getActor(1000); - vm.prank(notSeller); - vm.expectRevert("Marketplace: not listing creator."); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, toApprove); - } - - function test_revert_approveBuyerForListing_listingNotReserved() public { - (uint256 listingId, IDirectListings.ListingParameters memory listingParamsToUpdate) = _setup_updateListing(0); - bool toApprove = true; - - assertEq(MintraDirectListingsLogicStandalone(marketplace).getListing(listingId).reserved, true); - - listingParamsToUpdate.reserved = false; - - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).updateListing(listingId, listingParamsToUpdate); - - assertEq(MintraDirectListingsLogicStandalone(marketplace).getListing(listingId).reserved, false); - - // Seller approves buyer for reserved listing. - vm.prank(seller); - vm.expectRevert("Marketplace: listing not reserved."); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, toApprove); - } - - /*/////////////////////////////////////////////////////////////// - Approve currency for listing - //////////////////////////////////////////////////////////////*/ - - function _setup_approveCurrencyForListing(uint256 tokenId) private returns (uint256 listingId) { - (listingId, ) = _setup_updateListing(tokenId); - } - - function test_state_approveCurrencyForListing() public { - uint256 listingId = _setup_approveCurrencyForListing(0); - address currencyToApprove = NATIVE_TOKEN; - uint256 pricePerTokenForCurrency = 2 ether; - - // Seller approves buyer for reserved listing. - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveCurrencyForListing( - listingId, - currencyToApprove, - pricePerTokenForCurrency - ); - - assertEq( - MintraDirectListingsLogicStandalone(marketplace).isCurrencyApprovedForListing(listingId, NATIVE_TOKEN), - true - ); - assertEq( - MintraDirectListingsLogicStandalone(marketplace).currencyPriceForListing(listingId, NATIVE_TOKEN), - pricePerTokenForCurrency - ); - } - - function test_revert_approveCurrencyForListing_notListingCreator() public { - uint256 listingId = _setup_approveCurrencyForListing(0); - address currencyToApprove = NATIVE_TOKEN; - uint256 pricePerTokenForCurrency = 2 ether; - - // Someone other than seller approves buyer for reserved listing. - address notSeller = getActor(1000); - vm.prank(notSeller); - vm.expectRevert("Marketplace: not listing creator."); - MintraDirectListingsLogicStandalone(marketplace).approveCurrencyForListing( - listingId, - currencyToApprove, - pricePerTokenForCurrency - ); - } - - function test_revert_approveCurrencyForListing_reApprovingMainCurrency() public { - uint256 listingId = _setup_approveCurrencyForListing(0); - address currencyToApprove = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId).currency; - uint256 pricePerTokenForCurrency = 2 ether; - - // Seller approves buyer for reserved listing. - vm.prank(seller); - vm.expectRevert("Marketplace: approving listing currency with different price."); - MintraDirectListingsLogicStandalone(marketplace).approveCurrencyForListing( - listingId, - currencyToApprove, - pricePerTokenForCurrency - ); - } - - /*/////////////////////////////////////////////////////////////// - Buy from listing - //////////////////////////////////////////////////////////////*/ - - function _setup_buyFromListing( - uint256 tokenId - ) private returns (uint256 listingId, IDirectListings.Listing memory listing) { - (listingId, ) = _setup_updateListing(tokenId); - listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - } - - function test_state_buyFromListing_with_mint_token() public { - uint256 listingId = _createListing(seller, address(erc20Aux)); - IDirectListings.Listing memory listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - address currency = listing.currency; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - uint256 platformFeeBpsMint = MintraDirectListingsLogicStandalone(marketplace).platformFeeBpsMint(); - uint256 platformFee = (totalPrice * platformFeeBpsMint) / 10000; - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 0; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Mint requisite total price to buyer. - erc20Aux.mint(buyer, totalPrice); - assertBalERC20Eq(address(erc20Aux), buyer, totalPrice); - assertBalERC20Eq(address(erc20Aux), seller, 0); - - // Approve marketplace to transfer currency - vm.prank(buyer); - erc20Aux.increaseAllowance(marketplace, totalPrice); - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - - { - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - // Verify that buyer is owner of listed tokens, post-sale. - assertIsOwnerERC721(address(erc721), buyer, tokenIds); - assertIsNotOwnerERC721(address(erc721), seller, tokenIds); - - // Verify seller is paid total price. - assertBalERC20Eq(address(erc20Aux), buyer, 0); - assertBalERC20Eq(address(erc20Aux), seller, totalPrice - platformFee); - - if (quantityToBuy == listing.quantity) { - // Verify listing status is `COMPLETED` if listing tokens are all bought. - IDirectListings.Listing memory completedListing = MintraDirectListingsLogicStandalone(marketplace) - .getListing(listingId); - assertTrue(completedListing.status == IDirectListings.Status.COMPLETED); - } - } - - function test_state_buyFromListing_721() public { - (uint256 listingId, IDirectListings.Listing memory listing) = _setup_buyFromListing(0); - - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - address currency = listing.currency; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - uint256 platformFeeBps = MintraDirectListingsLogicStandalone(marketplace).platformFeeBps(); - uint256 platformFee = (totalPrice * platformFeeBps) / 10000; - - // Seller approves buyer for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, true); - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 0; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice); - assertBalERC20Eq(address(erc20), buyer, totalPrice); - assertBalERC20Eq(address(erc20), seller, 0); - - // Approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice); - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - - { - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - // Verify that buyer is owner of listed tokens, post-sale. - assertIsOwnerERC721(address(erc721), buyer, tokenIds); - assertIsNotOwnerERC721(address(erc721), seller, tokenIds); - - // Verify seller is paid total price. - assertBalERC20Eq(address(erc20), buyer, 0); - assertBalERC20Eq(address(erc20), seller, totalPrice - platformFee); - - if (quantityToBuy == listing.quantity) { - // Verify listing status is `COMPLETED` if listing tokens are all bought. - IDirectListings.Listing memory completedListing = MintraDirectListingsLogicStandalone(marketplace) - .getListing(listingId); - assertTrue(completedListing.status == IDirectListings.Status.COMPLETED); - } - } - - function test_state_buyFromListing_multi_721() public { - vm.prank(seller); - (uint256 listingIdOne, IDirectListings.Listing memory listingOne) = _setup_buyFromListing(0); - vm.prank(seller); - (uint256 listingIdTwo, IDirectListings.Listing memory listingTwo) = _setup_buyFromListing(1); - - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingIdOne, buyer, true); - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingIdTwo, buyer, true); - - address buyFor = buyer; - uint256 quantityToBuy = listingOne.quantity; - address currency = listingOne.currency; - uint256 pricePerToken = listingOne.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - uint256 platformFeeBps = MintraDirectListingsLogicStandalone(marketplace).platformFeeBps(); - uint256 platformFee = (totalPrice * platformFeeBps) / 10000; - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice + totalPrice); - assertBalERC20Eq(address(erc20), buyer, totalPrice + totalPrice); - assertBalERC20Eq(address(erc20), seller, 0); - - // Approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice + totalPrice); - - // Buy tokens from listing. - vm.warp(listingTwo.startTimestamp); - { - uint256[] memory listingIdArray = new uint256[](2); - listingIdArray[0] = listingIdOne; - listingIdArray[1] = listingIdTwo; - - address[] memory buyForArray = new address[](2); - buyForArray[0] = buyFor; - buyForArray[1] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](2); - quantityToBuyArray[0] = quantityToBuy; - quantityToBuyArray[1] = quantityToBuy; - - address[] memory currencyArray = new address[](2); - currencyArray[0] = currency; - currencyArray[1] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](2); - expectedTotalPriceArray[0] = totalPrice; - expectedTotalPriceArray[1] = totalPrice; - - vm.prank(buyer); - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - // Verify that buyer is owner of listed tokens, post-sale. - assertIsOwnerERC721(address(erc721), buyer, tokenIds); - assertIsNotOwnerERC721(address(erc721), seller, tokenIds); - - // Verify seller is paid total price. - assertBalERC20Eq(address(erc20), buyer, 0); - uint256 sellerPayout = totalPrice + totalPrice - platformFee - platformFee; - assertBalERC20Eq(address(erc20), seller, sellerPayout); - } - - function test_state_buyFromListing_1155() public { - // Create the listing - uint256 listingId = createListing_1155(0, 1); - - IDirectListings.Listing memory listing = MintraDirectListingsLogicStandalone(marketplace).getListing(listingId); - - address buyFor = buyer; - uint256 tokenId = listing.tokenId; - uint256 quantity = listing.quantity; - uint256 quantityToBuy = listing.quantity; - address currency = listing.currency; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - uint256 platformFeeBps = MintraDirectListingsLogicStandalone(marketplace).platformFeeBps(); - uint256 platformFee = (totalPrice * platformFeeBps) / 10000; - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = quantity; - - assertBalERC1155Eq(address(erc1155), seller, tokenIds, amounts); - - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice); - assertBalERC20Eq(address(erc20), buyer, totalPrice); - assertBalERC20Eq(address(erc20), seller, 0); - - // Approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice); - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - { - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - // Verify that buyer is owner of listed tokens, post-sale. - assertBalERC1155Eq(address(erc1155), buyer, tokenIds, amounts); - - // Verify seller is paid total price. - assertBalERC20Eq(address(erc20), buyer, 0); - assertBalERC20Eq(address(erc20), seller, totalPrice - platformFee); - - if (quantityToBuy == listing.quantity) { - // Verify listing status is `COMPLETED` if listing tokens are all bought. - IDirectListings.Listing memory completedListing = MintraDirectListingsLogicStandalone(marketplace) - .getListing(listingId); - assertTrue(completedListing.status == IDirectListings.Status.COMPLETED); - } - } - - function test_state_buyFromListing_multi_1155() public { - vm.prank(seller); - uint256 listingIdOne = createListing_1155(0, 1); - IDirectListings.Listing memory listingOne = MintraDirectListingsLogicStandalone(marketplace).getListing( - listingIdOne - ); - - vm.prank(seller); - uint256 listingIdTwo = createListing_1155(1, 2); - IDirectListings.Listing memory listingTwo = MintraDirectListingsLogicStandalone(marketplace).getListing( - listingIdTwo - ); - - address buyFor = buyer; - uint256 quantityToBuy = listingOne.quantity; - address currency = listingOne.currency; - uint256 pricePerToken = listingOne.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - uint256 platformFeeBps = MintraDirectListingsLogicStandalone(marketplace).platformFeeBps(); - uint256 platformFee = (totalPrice * platformFeeBps) / 10000; - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](2); - tokenIds[0] = 0; - tokenIds[1] = 1; - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 2; - amounts[1] = 2; - - assertBalERC1155Eq(address(erc1155), seller, tokenIds, amounts); - - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice + totalPrice); - assertBalERC20Eq(address(erc20), buyer, totalPrice + totalPrice); - assertBalERC20Eq(address(erc20), seller, 0); - - // Approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice + totalPrice); - - // Buy tokens from listing. - vm.warp(listingTwo.startTimestamp); - { - uint256[] memory listingIdArray = new uint256[](2); - listingIdArray[0] = listingIdOne; - listingIdArray[1] = listingIdTwo; - - address[] memory buyForArray = new address[](2); - buyForArray[0] = buyFor; - buyForArray[1] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](2); - quantityToBuyArray[0] = quantityToBuy; - quantityToBuyArray[1] = quantityToBuy; - - address[] memory currencyArray = new address[](2); - currencyArray[0] = currency; - currencyArray[1] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](2); - expectedTotalPriceArray[0] = totalPrice; - expectedTotalPriceArray[1] = totalPrice; - - vm.prank(buyer); - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - // Verify that buyer is owner of listed tokens, post-sale. - assertBalERC1155Eq(address(erc1155), buyer, tokenIds, amounts); - - // Verify seller is paid total price. - assertBalERC20Eq(address(erc20), buyer, 0); - uint256 sellerPayout = totalPrice + totalPrice - platformFee - platformFee; - assertBalERC20Eq(address(erc20), seller, sellerPayout); - } - - function test_state_bulkBuyFromListing_nativeToken() public { - (uint256 listingId, IDirectListings.Listing memory listing) = _setup_buyFromListing(0); - - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - address currency = NATIVE_TOKEN; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - uint256 platformFeeBps = MintraDirectListingsLogicStandalone(marketplace).platformFeeBps(); - uint256 platformFee = (totalPrice * platformFeeBps) / 10000; - - // Approve NATIVE_TOKEN for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveCurrencyForListing(listingId, currency, pricePerToken); - - // Seller approves buyer for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, true); - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 0; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Deal requisite total price to buyer. - vm.deal(buyer, totalPrice); - uint256 buyerBalBefore = buyer.balance; - uint256 sellerBalBefore = seller.balance; - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - { - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing{ value: totalPrice }( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - // Verify that buyer is owner of listed tokens, post-sale. - assertIsOwnerERC721(address(erc721), buyer, tokenIds); - assertIsNotOwnerERC721(address(erc721), seller, tokenIds); - - // Verify seller is paid total price. - assertEq(buyer.balance, buyerBalBefore - totalPrice); - assertEq(seller.balance, sellerBalBefore + (totalPrice - platformFee)); - - if (quantityToBuy == listing.quantity) { - // Verify listing status is `COMPLETED` if listing tokens are all bought. - IDirectListings.Listing memory completedListing = MintraDirectListingsLogicStandalone(marketplace) - .getListing(listingId); - assertTrue(completedListing.status == IDirectListings.Status.COMPLETED); - } - } - - function test_revert_bulkBuyFromListing_nativeToken_incorrectValueSent() public { - (uint256 listingId, IDirectListings.Listing memory listing) = _setup_buyFromListing(0); - - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - address currency = NATIVE_TOKEN; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - - // Approve NATIVE_TOKEN for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveCurrencyForListing(listingId, currency, pricePerToken); - - // Seller approves buyer for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, true); - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 0; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Deal requisite total price to buyer. - vm.deal(buyer, totalPrice); - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - vm.expectRevert("native token transfer failed"); - { - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing{ value: totalPrice - 1 }( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - } - - function test_revert_buyFromListing_unexpectedTotalPrice() public { - (uint256 listingId, IDirectListings.Listing memory listing) = _setup_buyFromListing(0); - - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - address currency = NATIVE_TOKEN; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - - // Approve NATIVE_TOKEN for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveCurrencyForListing(listingId, currency, pricePerToken); - - // Seller approves buyer for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, true); - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 0; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Deal requisite total price to buyer. - vm.deal(buyer, totalPrice); - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - vm.expectRevert("Unexpected total price"); - - { - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice + 1; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing{ value: totalPrice - 1 }( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - } - - function test_revert_buyFromListing_invalidCurrency() public { - (uint256 listingId, IDirectListings.Listing memory listing) = _setup_buyFromListing(0); - - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - - // Seller approves buyer for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, true); - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 0; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice); - assertBalERC20Eq(address(erc20), buyer, totalPrice); - assertBalERC20Eq(address(erc20), seller, 0); - - // Approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice); - - // Buy tokens from listing. - - assertEq(listing.currency, address(erc20)); - assertEq( - MintraDirectListingsLogicStandalone(marketplace).isCurrencyApprovedForListing(listingId, NATIVE_TOKEN), - false - ); - - vm.warp(listing.startTimestamp); - vm.prank(buyer); - vm.expectRevert("Paying in invalid currency."); - - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = NATIVE_TOKEN; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - function test_revert_buyFromListing_buyerBalanceLessThanPrice() public { - (uint256 listingId, IDirectListings.Listing memory listing) = _setup_buyFromListing(0); - - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - address currency = listing.currency; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - - // Seller approves buyer for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, true); - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 0; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice - 1); // Buyer balance less than total price - assertBalERC20Eq(address(erc20), buyer, totalPrice - 1); - assertBalERC20Eq(address(erc20), seller, 0); - - // Approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice); - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - vm.expectRevert("!BAL20"); - - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - function test_revert_buyFromListing_notApprovedMarketplaceToTransferPrice() public { - (uint256 listingId, IDirectListings.Listing memory listing) = _setup_buyFromListing(0); - - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity; - address currency = listing.currency; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - - // Seller approves buyer for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, true); - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 0; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice); - assertBalERC20Eq(address(erc20), buyer, totalPrice); - assertBalERC20Eq(address(erc20), seller, 0); - - // Don't approve marketplace to transfer currency - vm.prank(buyer); - erc20.approve(marketplace, 0); - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - vm.expectRevert("!BAL20"); - - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - function test_revert_buyFromListing_buyingZeroQuantity() public { - (uint256 listingId, IDirectListings.Listing memory listing) = _setup_buyFromListing(0); - - address buyFor = buyer; - uint256 quantityToBuy = 0; // Buying zero quantity - address currency = listing.currency; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - - // Seller approves buyer for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, true); - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 0; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice); - assertBalERC20Eq(address(erc20), buyer, totalPrice); - assertBalERC20Eq(address(erc20), seller, 0); - - // Don't approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice); - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - vm.expectRevert("Buying invalid quantity"); - - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - function test_revert_buyFromListing_buyingMoreQuantityThanListed() public { - (uint256 listingId, IDirectListings.Listing memory listing) = _setup_buyFromListing(0); - - address buyFor = buyer; - uint256 quantityToBuy = listing.quantity + 1; // Buying more than listed. - address currency = listing.currency; - uint256 pricePerToken = listing.pricePerToken; - uint256 totalPrice = pricePerToken * quantityToBuy; - - // Seller approves buyer for listing - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, true); - - // Verify that seller is owner of listed tokens, pre-sale. - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = 0; - assertIsOwnerERC721(address(erc721), seller, tokenIds); - assertIsNotOwnerERC721(address(erc721), buyer, tokenIds); - - // Mint requisite total price to buyer. - erc20.mint(buyer, totalPrice); - assertBalERC20Eq(address(erc20), buyer, totalPrice); - assertBalERC20Eq(address(erc20), seller, 0); - - // Don't approve marketplace to transfer currency - vm.prank(buyer); - erc20.increaseAllowance(marketplace, totalPrice); - - // Buy tokens from listing. - vm.warp(listing.startTimestamp); - vm.prank(buyer); - vm.expectRevert("Buying invalid quantity"); - - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyFor; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = quantityToBuy; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = currency; - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = totalPrice; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - } - - /*/////////////////////////////////////////////////////////////// - View functions - //////////////////////////////////////////////////////////////*/ - function test_getAllListing() public { - // Create the listing - createListing_1155(0, 1); - - IDirectListings.Listing[] memory listings = MintraDirectListingsLogicStandalone(marketplace).getAllListings( - 0, - 0 - ); - - assertEq(listings.length, 1); - - IDirectListings.Listing memory listing = listings[0]; - - assertEq(listing.assetContract, address(erc1155)); - assertEq(listing.tokenId, 0); - assertEq(listing.quantity, 2); - assertEq(listing.currency, address(erc20)); - assertEq(listing.pricePerToken, 1 ether); - assertEq(listing.startTimestamp, 100); - assertEq(listing.endTimestamp, 200); - assertEq(listing.reserved, false); - } - - function test_getAllValidListings() public { - // Create the listing - createListing_1155(0, 1); - - IDirectListings.Listing[] memory listingsAll = MintraDirectListingsLogicStandalone(marketplace).getAllListings( - 0, - 0 - ); - - assertEq(listingsAll.length, 1); - - vm.warp(listingsAll[0].startTimestamp); - IDirectListings.Listing[] memory listings = MintraDirectListingsLogicStandalone(marketplace) - .getAllValidListings(0, 0); - - assertEq(listings.length, 1); - - IDirectListings.Listing memory listing = listings[0]; - - assertEq(listing.assetContract, address(erc1155)); - assertEq(listing.tokenId, 0); - assertEq(listing.quantity, 2); - assertEq(listing.currency, address(erc20)); - assertEq(listing.pricePerToken, 1 ether); - assertEq(listing.startTimestamp, 100); - assertEq(listing.endTimestamp, 200); - assertEq(listing.reserved, false); - } - - function test_currencyPriceForListing_fail() public { - // Create the listing - createListing_1155(0, 1); - - vm.expectRevert("Currency not approved for listing"); - MintraDirectListingsLogicStandalone(marketplace).currencyPriceForListing(0, address(erc20Aux)); - } - - function _createListing(address _seller, address currency) private returns (uint256 listingId) { - // Sample listing parameters. - address assetContract = address(erc721); - uint256 tokenId = 0; - uint256 quantity = 1; - uint256 pricePerToken = 1 ether; - uint128 startTimestamp = 100; - uint128 endTimestamp = 200; - bool reserved = false; - - // Mint the ERC721 tokens to seller. These tokens will be listed. - _setupERC721BalanceForSeller(_seller, 1); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - assertIsOwnerERC721(address(erc721), _seller, tokenIds); - - // Approve Marketplace to transfer token. - vm.prank(_seller); - erc721.setApprovalForAll(marketplace, true); - - // List tokens. - IDirectListings.ListingParameters memory listingParams = IDirectListings.ListingParameters( - assetContract, - tokenId, - quantity, - currency, - pricePerToken, - startTimestamp, - endTimestamp, - reserved - ); - - vm.prank(_seller); - listingId = MintraDirectListingsLogicStandalone(marketplace).createListing(listingParams); - } - - function test_audit_native_tokens_locked() public { - (uint256 listingId, IDirectListings.Listing memory existingListing) = _setup_buyFromListing(0); - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = existingListing.tokenId; - - // Verify existing auction at `auctionId` - assertEq(existingListing.assetContract, address(erc721)); - - vm.warp(existingListing.startTimestamp); - - // No ether is locked in contract - assertEq(marketplace.balance, 0); - - // buy from listing - erc20.mint(buyer, 10 ether); - vm.deal(buyer, 1 ether); - - vm.prank(seller); - MintraDirectListingsLogicStandalone(marketplace).approveBuyerForListing(listingId, buyer, true); - - vm.startPrank(buyer); - erc20.approve(marketplace, 10 ether); - - vm.expectRevert("Incorrect PLS amount sent"); - - uint256[] memory listingIdArray = new uint256[](1); - listingIdArray[0] = listingId; - - address[] memory buyForArray = new address[](1); - buyForArray[0] = buyer; - - uint256[] memory quantityToBuyArray = new uint256[](1); - quantityToBuyArray[0] = 1; - - address[] memory currencyArray = new address[](1); - currencyArray[0] = address(erc20); - - uint256[] memory expectedTotalPriceArray = new uint256[](1); - expectedTotalPriceArray[0] = 1 ether; - - MintraDirectListingsLogicStandalone(marketplace).bulkBuyFromListing{ value: 1 ether }( - listingIdArray, - buyForArray, - quantityToBuyArray, - currencyArray, - expectedTotalPriceArray - ); - - vm.stopPrank(); - - // 1 ether is temporary locked in contract - assertEq(marketplace.balance, 0 ether); - } - - function test_set_platform_fee() public { - uint256 platformFeeBps = MintraDirectListingsLogicStandalone(marketplace).platformFeeBps(); - assertEq(platformFeeBps, 225); - - vm.prank(wizard); - MintraDirectListingsLogicStandalone(marketplace).setPlatformFeeBps(369); - - platformFeeBps = MintraDirectListingsLogicStandalone(marketplace).platformFeeBps(); - - assertEq(platformFeeBps, 369); - } - - function test_fuzz_set_platform_fee(uint256 platformFeeBps) public { - vm.assume(platformFeeBps <= 369); - - vm.prank(wizard); - MintraDirectListingsLogicStandalone(marketplace).setPlatformFeeBps(platformFeeBps); - - uint256 expectedPlatformFeeBps = MintraDirectListingsLogicStandalone(marketplace).platformFeeBps(); - - assertEq(expectedPlatformFeeBps, platformFeeBps); - } - - function test_set_platform_fee_fail() public { - vm.prank(wizard); - vm.expectRevert("Fee not in range"); - MintraDirectListingsLogicStandalone(marketplace).setPlatformFeeBps(1000); - } -}