You are on page 1of 8

[CRITICAL] Market.

sol - Remove collection address when


remove a collection ID
Description
When an admin uses the closeCollectionForTradingAndListing() function to close a collection for
trading and listing, the function unintentionally removes the entire _collection instead of just deleting
the collection ID. Consequently, this leads to the loss of all other collection IDs that have the same address.

function closeCollectionForTradingAndListing(address _collection,uint256 _collectionId)


external onlyAdmin {
   require(_collectionAddressSet.contains(_collection), "Operations: Collection not
listed");

   _collections[_collection][_collectionId].status = CollectionStatus.Close;
   _collectionAddressSet.remove(_collection); // ISSUE AT THIS LINE

   emit CollectionClose(_collection, _collectionId);


}

Recommendation
The code can be fixed as below:

function closeCollectionForTradingAndListing(address _collection,uint256 _collectionId)


external onlyAdmin {
   require(_collections[_collection][_collectionId].status == CollectionStatus.Open,
"Operations: Collection not listed"); // FIXED

   _collections[_collection][_collectionId].status = CollectionStatus.Close;
   // _collectionAddressSet.remove(_collection); <-- REMOVE THIS
   
   emit CollectionClose(_collection, _collectionId);
}

Discussion: We have noticed that the code allows for the creation of multiple collections at a single collection
address. However, if each collection address only has a single collection ID, then the aforementioned issue can be
considered a false positive. Nevertheless, we have also identified another issue with the _collections mapping
and identified best practices to address this problem.

[CRITICAL] Market.sol - Modify ask order with arbitrary


tokenBuy
Description
After creating an ask order, the token owner can modify it using the modifyAskOrder() function. This
function accepts both the _newTokenBuy and _tokenBuy parameters. However, a flaw in the code causes
the function to first check if the buy token is allowed using the _tokenBuy parameter, and then it assigns
the new buy token using _newTokenBuy . This mistake can enable the owner to modify an ask order with an
arbitrary tokenBuy .

Scenario impact:

Owner list a NFT on market that token buy is BNB (allowed) and price is 1 BNB.
Owner edit token buy to ETH.
Front-end display 1 unknown token and user click buy then approve quickly.
NFT was sold with 1 ETH.

Recommendation
The code can be fixed as below:

function modifyAskOrder(
   address _collection,
   uint256 _tokenId,
   uint256 _newPrice,
   address _newTokenBuy,
   //address _tokenBuy, <-- REMOVE THIS LINE
   uint256 _collectionId
) external nonReentrant {
   // Verify new price is not too low/high
   require(_newPrice >= minimumAskPrice && _newPrice <= maximumAskPrice, "Order: Price
not within range");

   // Verify collection is accepted


   require(_collections[_collection][_collectionId].status == CollectionStatus.Open,
"Collection: Not for listing");

   // Verify the sender has listed it


   require(_tokenIdsOfSellerForCollection[msg.sender][_collection].contains(_tokenId),
"Order: Token not listed");

   // require(allowBuyTokens[_tokenBuy] == true, "Order: token buy not allow"); //


REMOVE THIS LINE
   require(allowBuyTokens[_newTokenBuy] == true, "Order: token buy not allow");
//FIXED

   require(askHistory[_collection][_tokenId].collectionId == _collectionId, "wrong


collection");

   // Adjust the information


   _askDetails[_collection][_tokenId].price = _newPrice;
   _askDetails[_collection][_tokenId].tokenBuy = _newTokenBuy;

   // Emit event


   emit AskUpdate(_collection, msg.sender, _tokenId, _newPrice, _newTokenBuy,
_collectionId);
}

[CRITICAL] Market.sol - Front-Running: seller can change


tokenBuy before buyer buy
Description
If someone buys an attacker's NFT and then initiates a transaction to purchase a token, the attacker may be
able to execute a front-running transaction. This could involve modifying the token purchase to a different
token that was previously approved by the user, and that has a higher price than the original token being
purchased.

Example: Front-running change 1 BNB to 1 ETH.

Recommendation
The code can be fixed as below:

function buyToken(
   address _collection,
   uint256 _tokenId,
   uint256 _price,
   address _tokenBuy, // ADD THIS
   uint256 _collectionId
) public nonReentrant {
   Ask memory askOrder = _askDetails[_collection][_tokenId];

   require(_tokenBuy == askOrder.tokenBuy, "Buy: Incorrect Token"); // FIXED


   
   IERC20(askOrder.tokenBuy).safeTransferFrom(address(msg.sender), address(this),
_price);

   _buyToken(_collection, _tokenId, _price, _collectionId);


}

[HIGH] Market.sol - owner can transfer any ERC20 tokens


in contract, including creator and trading fee
Description
If the owner of a contract calls the recoverFungibleTokens() function, they should be able to withdraw
all the ERC20 tokens balance in the contract, including those that were originally sent as creator and trading
fees.

Recommendation
Calculate carefully before transfer. We suggest have a global mapping to store total pending revenue
( totalPendingRevenue ) for each token. And then, the code can be fixed as below:

function recoverFungibleTokens(address _token) external onlyOwner {


   uint256 amountToRecover = IERC20(_token).balanceOf(address(this)) -
totalPendingRevenue[_token]; // FIXED
   require(amountToRecover != 0, "Operations: No token to recover");

   IERC20(_token).safeTransfer(address(msg.sender), amountToRecover);

   emit TokenRecovery(_token, amountToRecover);


}

[HIGH] Market.sol - Wrong execution with comment of


recoverNonFungibleToken() function
Description
The recoverNonFungibleToken() function is expected to transfer a non-fungible token from the contract
address to the msg.sender . However, it is not doing so in its current implementation. It is important to
note that the administrator has the ability to transfer any non-fungible token by providing an incorrect
collection ID.

Recommendation
The contract must have a mapping to store all NFT listed on market (ex: tokenListed ) and then the code
can be fixed as below:

function recoverNonFungibleToken(address _token, uint256 _tokenId) external onlyOwner


nonReentrant {
   require(!tokenListed[_token].contains(_tokenId), "Operations: NFT not
recoverable");    // FIXED
   IERC721(_token).safeTransferFrom(address(this), msg.sender, _tokenId);            
     // FIXED

   emit NonFungibleTokenRecovery(_token, _tokenId, _collectionId);


}

[HIGH] Market.sol - Inconsistency in creating and modify


[HIGH] Market.sol - Inconsistency in creating and modify
collection
Description
If the admin sets the creator's address to zero while creating a collection, the modifyCollection() function
will be dissatisfied with the first requirement. Consequently, the admin will be unable to modify the
collection.

In a similar way, the admin can create collection exits again.

Recommendation

require(_collections[_collection][_collectionId].creatorAddress !=
address(0),"Operations: Collection not listed");

Replace above code with this fixed code:

require(_collections[_collection][_collectionId].collectionId != 0,"Operations:
Collection not listed");

[HIGH] Market.sol - Inconsistency in


_collectionAddressSet and _collections variables
Description
After remove collection, the collection address stored in _collectionAddressSet is deleted. But admin
still can modify that collection because modifyCollection() does not check collection in
_collectionAddressSet .

Example:

1. admin create a collection A => _collectionAddressSet has A and status A in _collections is OPEN
2. admin remove collection A => _collectionAddressSet removed A and status A in _collections is CLOSED
3. admin modify collection A => _collectionAddressSet does not have A and status A in _collections is
OPEN

Recommendation
The code can be fixed like the first issue:
function closeCollectionForTradingAndListing(address _collection,uint256 _collectionId)
external onlyAdmin {
   require(_collections[_collection][_collectionId].status == CollectionStatus.Open,
"Operations: Collection not listed"); // FIXED

   _collections[_collection][_collectionId].status = CollectionStatus.Close;
   // _collectionAddressSet.remove(_collection); <-- REMOVE THIS
   
   emit CollectionClose(_collection, _collectionId);
}

[LOW] Market.sol - _canTokenBeListed return wrong in


case collection have not added
Description
If user pass wrong collection address or collection id, _canTokenBeListed() function always return true.

Recommendation
The code can be fixed as below:

function _canTokenBeListed(address _collection, uint256 _tokenId,uint256 _collectionId)


internal view returns (bool) {
   require(_collections[_collection][_collectionId].status == CollectionStatus.Open,
"Collection is closed");
   address whitelistCheckerAddress = _collections[_collection]
[_collectionId].whitelistChecker;
   return
      (whitelistCheckerAddress == address(0)) ||
       ICollectionWhitelistChecker(whitelistCheckerAddress).canList(_tokenId);
}

[LOW] Market.sol - Creator can change trading fee after


seller ask to sell
Description
It is not transparent when the creator or admin modifies the collection's creator or trading fee after the
seller has requested to list the NFT on the market.
Recommendation
The code need check before modify collection as below:

function modifyCollection(
   address _collection,
   address _creator,
   address _whitelistChecker,
   uint256 _tradingFee,
   uint256 _creatorFee,
   uint256 _collectionId
) external {
   //require(_collectionAddressSet.contains(_collection), "Operations: Collection not
listed");
   require(_collections[_collection][_collectionId].creatorAddress !=
address(0),"Operations: Collection not listed");

   require(
      (_creatorFee == 0 && _creator == address(0)) || (_creatorFee != 0 && _creator
!= address(0)),
       "Operations: Creator parameters incorrect"
  );

   Collection storage collection = _collections[_collection][_collectionId];

   require(msg.sender == collection.creatorAddress || msg.sender == adminAddress, "not


creator or admin");
 
   if(_askTokenIds[_collection][_collectionId].length() == 0) {
       require(_tradingFee + _creatorFee <= TOTAL_MAX_FEE, "Operations: Sum of fee
must inferior to TOTAL_MAX_FEE");

       collection = Collection({
       status: CollectionStatus.Open,
       creatorAddress: _creator,
       whitelistChecker: _whitelistChecker,
       tradingFee: _tradingFee,
       creatorFee: _creatorFee,
       collectionId: _collectionId
      });
  } else {
       collection = Collection({
       status: CollectionStatus.Open,
       creatorAddress: _creator,
       whitelistChecker: _whitelistChecker,
       tradingFee: collection.tradingFee,
       creatorFee: collection.creatorFee,
       collectionId: _collectionId
      });
  }
   

   emit CollectionUpdate(_collection, _creator, _whitelistChecker, _tradingFee,


_creatorFee,_collectionId);
}

[INFO] Market.sol - TYPO reciver in batchTransfer()


function
Recommendation
Replace reciver with receiver.

[INFO] Market.sol - Missing emit event at buyToken()


function
Recommendation
Add emit at the end of buyToken() function

You might also like