Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: high
Invalid

Royalty manipulation via reversion attack in transferRoyalties()

Summary

The Swan contract’s purchase() function is vulnerable to a royalty manipulation exploit. The flaw occurs because the transferRoyalties() function is executed before verifying that the buyer has sufficient funds to complete the payment. This sequence allows a malicious buyer to trigger royalty payments and subsequently induce a payment failure by reducing their token balance, allowing them to receive royalties without completing the NFT purchase.

Vulnerability Details

Root Cause: The purchase() function calls transferRoyalties() before confirming payment.

  • Attack Strategy:

    • The attacker lists an NFT and initiates a purchase.

    • The purchase() function calls transferRoyalties() to transfer royalties, but before payment verification.

    • The attacker intentionally reduces their balance to induce a reversion at the token.transferFrom() stage, allowing them to receive the royalty while preventing the purchase.

  • Outcome: The attacker receives royalty payments without completing the transaction.

Impact

This vulnerability can lead to:

Unauthorized Royalty Payments: Malicious buyers can extract royalties without purchasing the NFT.

Financial Losses: Repeated attacks could drain contract funds, resulting in significant financial losses for the marketplace and asset sellers.

Market Disruption: By exploiting this flaw, attackers can create false purchase activity to repeatedly extract royalties, damaging trust and functionality.

Tools Used

Tests

Recommendations

To prevent this exploit:

  • Move the transferRoyalties() move from list and relist functions to purchase function.

  • This ensures royalties are only distributed after payment completion, blocking the exploit.

Updated purchase() Code

function purchase(address _asset) external nonReentrant {
AssetListing storage listing = listings[_asset];
if (listing.status != AssetStatus.Listed) revert InvalidStatus(listing.status, AssetStatus.Listed);
if (listing.buyer != msg.sender) revert Unauthorized(msg.sender);
// Complete payment transfer before calling transferRoyalties()
token.transferFrom(listing.buyer, address(this), listing.price);
token.transfer(listing.seller, listing.price);
// Mark asset as sold before transferring the NFT and distributing royalties
listing.status = AssetStatus.Sold;
SwanAsset(_asset).transferFrom(listing.seller, address(this), 1);
SwanAsset(_asset).transferFrom(address(this), listing.buyer, 1);
+ transferRoyalties(listing);
emit AssetSold(listing.seller, msg.sender, _asset, listing.price);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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