Omniscia Tangible Audit
MarketplaceV2 Manual Review Findings
MarketplaceV2 Manual Review Findings
MV2-01M: Inexistent Restriction of Storage Payment Amount
Type | Severity | Location |
---|---|---|
Logical Fault | MarketplaceV2.sol:L373-L378 |
Description:
The MarketplaceV2::_payStorage
function utilized throughout the marketplace's purchase workflows does not permit the buyer to actually validate the amount of funds they will expense to pay for the storage of a Tangible NFT.
Impact:
While less impactful than the peer-to-peer price hike identified in another exhibit of the MarketplaceV2
contract, the manager of a Tangible NFT can still manipulate its configuration to impact marketplace trades of it negatively and force buyers to pay an unexpected amount of funds for rent that may not even have existed when they initialized their transaction.
Example:
364function _payStorage(365 ITangibleNFT nft,366 IERC20Metadata paymentToken,367 uint256 tokenId,368 uint256 _years369) internal {370 require(nft.storageRequired(), "STNR");371 require(_years > 0, "YZ");372
373 uint256 amount = IFactory(factory()).adjustStorageAndGetAmount(374 nft,375 paymentToken,376 tokenId,377 _years378 );379 //we take in default USD token380 IERC20(address(paymentToken)).safeTransferFrom(381 msg.sender,382 IFactory(factory()).categoryOwnerWallet(nft),383 amount384 );385 emit StorageFeePaid(address(nft), tokenId, address(paymentToken), msg.sender, amount);386}
Recommendation:
We advise an upper bound to be enforced on the amount the buyer is willing to pay for storage, ensuring that users cannot arbitrarily toggle their ITangibleNFT::storageRequired
configurations and / or set incredibly high rent prices before a sale takes place.
Alleviation (2ad448279d9e8e4b6edd94bcd2eb22129b6f7357):
A _maxAmount
argument has been introduced in MarketplaceV2::_payStorage
and its call chain, permitting users to control the maximum price they are willing to pay for a token's storage.
As such, we consider this exhibit fully alleviated.
MV2-02M: Insecure Validation of Tangible NFT Existence
Type | Severity | Location |
---|---|---|
Logical Fault | MarketplaceV2.sol:L603, L607 |
Description:
The MarketplaceV2::_onERC721Received
function will validate that the caller contract is a valid Tangible NFT by evaluating that the ITangibleNFT::name
is a category of the contract's factory
, however, this is an incorrect approach as a malicious nft
can yield any value in its ITangibleNFT::name
function.
Impact:
It is presently possible for any NFT, including malicious implementations, to set sales and update the TNFT trackers via the MarketplaceV2::onERC721Received
function that is insecurely guarded.
Example:
606require(607 address(IFactory(factory()).category(ITangibleNFT(nft).name())) != address(0),608 "Not TNFT"609);
Recommendation:
We advise the code to instead ensure that the caller nft
is part of the Tangible ecosystem by mandating that the result of the IFactory::category
call is equal to the nft
rather than not equal to the zero-address per the FactoryV2::newCategory
implementation.
Alleviation (2ad448279d9e8e4b6edd94bcd2eb22129b6f7357):
The require
conditional was revised as advised, ensuring that the output of the IFactory::category
lookup matches the nft
itself rather than resulting in a non-zero entry.
As such, we consider this exhibit properly alleviated.
MV2-03M: Inexistent Validation of Lot Token & Price
Type | Severity | Location |
---|---|---|
Logical Fault | MarketplaceV2.sol:L237-L238, L319 |
Description:
The MarketplaceV2::buy
function offers no form of validation for the buyer in relation to the paymentToken
as well as price
they will ultimately pay for a Tangible NFT. Coupled with the fact that it is trivial for a seller to update these values via the MarketplaceV2::sellBatch
function, it is possible for a malicious seller to change their payment token and / or price at the last minute before a buyer's purchase operation goes through.
Impact:
It is trivial to maliciously update the price and / or payment token of a Tangible NFT right before it is purchased to extract more funds than expected by the NFT's buyer.
Example:
319function buy(ITangibleNFT nft, uint256 tokenId, uint256 _years) external nonReentrant {320 //pay for storage321 if ((!nft.isStorageFeePaid(tokenId) || _years > 0) && nft.storageRequired()) {322 require(_years > 0, "YZ");323 _payStorage(324 nft,325 IERC20Metadata(address(marketplaceLot[address(nft)][tokenId].paymentToken)),326 tokenId,327 _years328 );329 }330 // if initial sale is not done, and whitelitsing is required, check if buyer is whitelisted331 if (332 IFactory(factory()).onlyWhitelistedForUnmintedCategory(nft) &&333 !initialSaleCompleted[nft][tokenId]334 ) {335 require(IFactory(factory()).whitelistForBuyUnminted(nft, msg.sender), "NW");336 }337 //buy the token338 _buy(nft, tokenId, true);339}
Recommendation:
We advise the MarketplaceV2::buy
function to accept two more arguments indicating the paymentToken
they wish to pay with as well as the maximum price they are willing to fulfil for the NFT, ensuring that buyers can control the payment method as well as amount they expense for a particular Tangible NFT purchase.
Alleviation (2ad448279d9e8e4b6edd94bcd2eb22129b6f7357):
The innermost MarketplaceV2::_buy
function was refactored to accept a _paymentToken
as well as _price
argument that denotes the token that the user wishes to pay in as well as the maximum price they are willing to expend.
All call chains that lead to the MarketplaceV2::_buy
function's invocation have been revised to correctly relay these values, thereby alleviating this exhibit in full.