Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: medium
Valid

Malicious sellers can potentially DoS NFT purchase thereby impacting other sellers

Summary

A vulnerability in the NFT purchase mechanism allows malicious sellers to list tokens for sale, then transfer ownership, which causes subsequent purchase attempts to revert. This is because the purchase() function expects the listing.seller to hold the token.

If the seller transfers the NFT to another address, the purchase operation will fail, potentially blocking the purchase of other listed assets in the same transaction.

Vulnerability Details

The list() function allows a seller to list an NFT without enforcing ownership restrictions, leaving the asset transferable post-listing:

address asset = address(swanAssetFactory.deploy(_name, _symbol, _desc, msg.sender));
listings[asset] = AssetListing({
createdAt: block.timestamp,
royaltyFee: buyer.royaltyFee(),
price: _price,
>> seller: msg.sender,
status: AssetStatus.Listed,
buyer: _buyer,
round: round
});

In the BuyerAgent contract, the purchase() function begins by checking the current round, phase and retrieves a list of assets eligible for purchase from the oracle. It then iterates through each asset, attempting to buy it by calling the purchase() function in Swan.

// Iterate through each asset and attempt to purchase it
for (uint256 i = 0; i < assets.length; i++) {
address asset = assets[i];
// Verify spending limit per round
uint256 price = swan.getListingPrice(asset);
spendings[round] += price;
if (spendings[round] > amountPerRound) {
revert BuyLimitExceeded(spendings[round], amountPerRound);
}
// Add asset to inventory
inventory[round].push(asset);
// **Attempt to purchase the asset in Swan**
>> swan.purchase(asset); // Vulnerable to DoS if seller has transferred the asset
}

In Swan, the purchase() function tries to transfer the NFT from listing.seller:

// Transfer NFT from seller to Swan, then from Swan to buyer
SwanAsset(_asset).transferFrom(listing.seller, address(this), 1); // Reverts if seller no longer owns

Issue Scenario:

  • A user lists their NFT through the list() function with a specified buyer.

  • Since no price validation is enforced at the time of listing, they could set a zero price for the asset.

  • This means they’re not financially invested in the listing (they won’t lose anything if the asset fails to be sold).

  • After listing the asset, the user transfers the NFT to another address, possibly even to a different wallet they control.

  • This action breaks the ownership link between the seller (as stored in listing.seller) and the NFT itself, as the NFT is no longer in the possession of the listed seller.

Impact

The purchase() function assumes that the listed token remains with the original listing.seller. If a seller transfers the NFT after listing, the purchase will revert because the transferFrom() call in purchase() will fail when trying to pull the NFT from listing.seller.

---> By listing at a zero price, malicious sellers can intentionally transfer ownership of the NFT, leading to a denial of service without incurring any financial loss.

Tools Used

Manual Review

Recommendations

Enforce a transfer lock on NFTs when they are listed, preventing the seller from transferring them while listed. This approach ensures that the listing.seller will always be the token holder during a purchase.

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Validated
Assigned finding tags:

DoS in BuyerAgent::purchase

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.