A NFT Marketplace supports NFT collection trading. Owners who have NFT collections can list their NFT collections. Then, buyers can purchase the listed NFT. Normally, buyers pay royalty fee to NFT contract creater(or owner) via NFT Marketplace.
The royalty fee can be setted by NFT Marketplace or NFT contract owner. The NFT Marketplace supports the NFT contract owners to set the royalty fee. Or the NFT Marketplace can get the default royalty fee of a NFT contract implementing EIP-2981. However, there is a royalty fee limit. Thus, Anyone can’t set the royalty fee over a royalty fee limit.
This article describes how a malicious NFT contract can bypass the royalty fee limit in a NFT Marketplace.
Suppose a NFT contract is implemented as below. The contract implements EIP-2981. EIP-2981 is NFT royalty standard[1]. It is for retrieve royalty of a NFT each time the NFT is traded in a marketplace.
The constructor function in MockERC721WithRoyalty.sol sets the fee and an address to receive the fee. And, these are called when the royaltyInfo function is called.
So, the Fee Manager of the marketplace calls the calculateRoyaltyFeeandGetRecipient() to get the royalty info. If the marketplace(or the fee setter) does not set the royaltyInfo, It gets the royaltyInfo of the listed NFT.
Or, the marketplace (or the fee setter) can set the royalty fee per a collection. The owner of the RoyaltyFeeSetter calls the updateRoyaltyInfoForCollectionIfSetter, which updates the fee per a collection in the marketplace and who can receive. The fee of the RoyaltyInfo to be updated is checked whether it is over 95% or not. So, the fee under 95% will be setted.
Another way is the owner of the NFT contract sets their royalty fee in the marketplace. The contract, RoyaltyFeeSetter supports the owner of NFT contract to set their royalty fee. After, the owner of NFT contract calls updateRoyaltyInfoForCollectionIfOwner() in the RoyaltyFeeSett contract to set their royalty fee. The fee to be updated also checked whether it is over 95% or not in the royaltyFeeRegistry contract.
In the process of a royalty fee setting chapter, we figure out that if the roaylty fee does not be setted in a marketplace, the marketplace will get the default royalty fee implemented by erc2981 in the listed NFT contract. However, it can be used for ignoring the royalty fee limit in the marketplace. The exploit scenario is as below.
Before i described, the NFT contract can set the default royalty fee what the owner wants. Suppose the owner sets the royalty fee to 100%. Then, the traded amount is transfered to the royalty fee recipient.The PoC is as below in the legacy looksrare marketplace. Before trading the NFT, the NFT contract sets the royalty fee 98% over 95%, which is the max royalty fee limit.Before testing, we checked the balance of royaltyCollector has 0 ETH. First, the makerAskUser makes the order his ERC721WithRoyalty with 3 ETH. Then, the takerBidUser creats orders to buy the NFT of the maker. And, he calls the matchAskWithTakerBidUsingETHAndWETH().As the result, the traded price is transferred to the royaltyCollector except the protocol fee, 2%. That is, 3ETH*0.98 is transferred to the royaltyCollector. We checked the royaltyCollector has all traded price.
More precisely, the matchAskWithTakerBidUsingETHAndWETH executes transferring the taker’s eth to the maker in the _transferFeesAndFundsWithWETH. So, the _transferFeesAndFundsWithWETH call the function, calculateRoyaltyFeeAndGetRecipient and get the default royalty fee of the traded NFT over 95%. As the result, the traded price except the protocol fee is transferred to the royaltyCollector at ‘IERC20(WETH).safeTransfer(royaltyFeeRecipient, royaltyFeeAmount);’.
The problem is that the fee manager does not check the royalty fee, which is over the max royalty limit.
Thus, the issue can be resolved as below. The below contract get the receiver from the NFT contract. After that, it sets the royalty fee to 0.5% for each NFT contracts. As the result, even if the default royalty fee is setted to over 95%, the royalty fee is changed to 0.5% in the marketplace.
[1] EIP-2981: https://eips.ethereum.org/EIPS/eip-2981