Dria

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

Sellers don't get the royalties they paid to the buyers back

Summary

Sellers don't get the royalties they paid to the buyers back.

Vulnerability Details

The royalties sellers pay buyers is a punishment they get for not selling asset at a round. They pay it each time they list/relist an asset and get it back once they sell the asset.

However, sellers do not retrieve the royalties they paid to buyers when the manage to sell assets. Buyers only pay the asset price as a payment for puchasing the asset.

File: contracts/swan/Swan.sol#L276-L302
function purchase(address _asset) external {
AssetListing storage listing = listings[_asset];
// asset must be listed to be purchased
if (listing.status != AssetStatus.Listed) {
revert InvalidStatus(listing.status, AssetStatus.Listed);
}
// can only the buyer can purchase the asset
if (listing.buyer != msg.sender) {
revert Unauthorized(msg.sender);
}
// update asset status to be sold
listing.status = AssetStatus.Sold;
// transfer asset from seller to Swan, and then from Swan to buyer
// this ensure that only approval to Swan is enough for the sellers
@--> SwanAsset(_asset).transferFrom(listing.seller, address(this), 1);
@--> SwanAsset(_asset).transferFrom(address(this), listing.buyer, 1);
// transfer money
token.transferFrom(listing.buyer, address(this), listing.price);
token.transfer(listing.seller, listing.price);
emit AssetSold(listing.seller, msg.sender, _asset, listing.price);
}

This makes sellers lose a part of the asset price.

Impact

Sellers may lose part of the asset's price.

Tools Used

Manual Review.

Recommendations

Ensure that sellers get back the royalties the paid to buyers when the sell an asset.
For that I recommend tracking the royalties paid by a seller to a buyer each time an asset is (re)listed. Consider the following ajustments.

File: contracts/swan/Swan.sol#L80-L88
struct AssetListing {
uint256 createdAt;
uint96 royaltyFee;
uint256 price;
address seller; // TODO: we can use asset.owner() instead of seller
address buyer;
uint256 round;
AssetStatus status;
++ uint256 buyerFee;
}
File: contracts/swan/Swan.sol#L172-L182
// all is well, create the asset & its 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,
++ buyerFee: 0
});
File: contracts/swan/Swan.sol#L237-L246
// create listing
listings[_asset] = AssetListing({
createdAt: block.timestamp,
royaltyFee: buyer.royaltyFee(),
price: _price,
seller: msg.sender,
status: AssetStatus.Listed,
buyer: _buyer,
round: round,
++ buyerFee: asset.buyerFee
});
File: contracts/swan/Swan.sol#L258-L272
function transferRoyalties(AssetListing storage asset) internal {
// calculate fees
uint256 buyerFee = (asset.price * asset.royaltyFee) / 100;
uint256 driaFee = (buyerFee * getCurrentMarketParameters().platformFee) / 100;
// first, Swan receives the entire fee from seller
// this allows only one approval from the seller's side
token.transferFrom(asset.seller, address(this), buyerFee);
// send the buyer's portion to them
++ asset.buyerFee += buyerFee - driaFee;
token.transfer(asset.buyer, buyerFee - driaFee);
// then it sends the remaining to Swan owner
token.transfer(owner(), driaFee);
}
File: contracts/swan/Swan.sol#L276-L302
function purchase(address _asset) external {
AssetListing storage listing = listings[_asset];
// asset must be listed to be purchased
if (listing.status != AssetStatus.Listed) {
revert InvalidStatus(listing.status, AssetStatus.Listed);
}
// can only the buyer can purchase the asset
if (listing.buyer != msg.sender) {
revert Unauthorized(msg.sender);
}
// update asset status to be sold
listing.status = AssetStatus.Sold;
// transfer asset from seller to Swan, and then from Swan to buyer
// this ensure that only approval to Swan is enough for the sellers
SwanAsset(_asset).transferFrom(listing.seller, address(this), 1);
SwanAsset(_asset).transferFrom(address(this), listing.buyer, 1);
// transfer money
-- token.transferFrom(listing.buyer, address(this), listing.price);
-- token.transfer(listing.seller, listing.price);
++ token.transferFrom(listing.buyer, address(this), listing.price + listing.buyerFee);
++ token.transfer(listing.seller, listing.price + listing.buyerFee);
emit AssetSold(listing.seller, msg.sender, _asset, listing.price);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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