Dria

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

Silent Royalty Distribution Failures Lead to Protocol Revenue Loss

Summary

The Swan protocol's royalty distribution mechanism can silently fail during asset listing, allowing sellers to list assets without paying required royalties. This breaks the core economic model where every listing should generate royalty payments to both the buyer agent and platform.

Vulnerability Details

The transferRoyalties function handles fee distribution but lacks proper validation and error handling:

function transferRoyalties(AssetListing storage asset) internal {
// calculate fees
uint256 buyerFee = (asset.price * asset.royaltyFee) / 100;
// @Issue - Integer division truncation can lead to zero fees
uint256 driaFee = (buyerFee * getCurrentMarketParameters().platformFee) / 100;
// @Issue - No validation that buyerFee > driaFee
// @Issue - No checks for successful token transfers
token.transferFrom(asset.seller, address(this), buyerFee);
token.transfer(asset.buyer, buyerFee - driaFee);
token.transfer(owner(), driaFee);
}

The bug is that the royalty distribution can fail silently if any of the token transfers fail, but the asset listing state would still be updated. This breaks the invariant that royalties must always be distributed when an asset is listed.

Proof of Concept:

  1. Seller lists asset without sufficient token approval

  2. transferFrom fails silently

  3. Asset gets listed but no royalties are distributed

  4. Violates the specification assertion

This is a problem for the protocol for several reasons:

  1. Integer Division Risk:

  • If asset.price * royaltyFee < 100, buyerFee becomes 0

  • If buyerFee * platformFee < 100, driaFee becomes 0

  • This can lead to royalty payments being completely skipped for low-value transactions

  1. Fee Calculation Order:

  • No validation that buyerFee > driaFee

  • Could cause underflow in buyerFee - driaFee calculation

  • Potential revert that leaves system in inconsistent state

  1. Transfer Safety:

  • No checks for successful token transfers

  • Failed transfers could leave funds stuck in contract

  • State changes happen before ensuring transfers succeed

Impact

  • Allows bypassing royalty payments on small transactions

  • Can lead to locked funds

  • Creates accounting inconsistencies between actual transfers and recorded state

Recommendations

function transferRoyalties(AssetListing storage asset) internal {
uint256 buyerFee = (asset.price * asset.royaltyFee) / 100;
uint256 driaFee = (buyerFee * getCurrentMarketParameters().platformFee) / 100;
- token.transferFrom(asset.seller, address(this), buyerFee);
- token.transfer(asset.buyer, buyerFee - driaFee);
- token.transfer(owner(), driaFee);
+ require(token.transferFrom(asset.seller, address(this), buyerFee), "Royalty transfer failed");
+ require(token.transfer(asset.buyer, buyerFee - driaFee), "Buyer fee transfer failed");
+ require(token.transfer(owner(), driaFee), "Platform fee transfer failed");
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

[KNOWN] - Low-35 Unsafe use of transfer()/transferFrom() with IERC20

Support

FAQs

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