Dria

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

Relisting Mechanism Allows for Unauthorized Purchases Across Rounds

Summary

The Swan contract's asset relisting mechanism allows assets to be transferred from one buyer to another across different market rounds. However, due to a lack of cleanup in the listing status of assets for previous buyers, an asset relisted to a new buyer can still be accessed and purchased by the previous round's buyer, thereby circumventing the intended exclusivity of asset offers per round.

Vulnerability Details

The vulnerability arises from the way assets are managed across different rounds in the Swan contract:

  1. When an asset is not sold in one round, it can be relisted for another round or even another buyer. This process updates the asset's buyer and round information.

  2. Despite the update, the original listing record in the previous round is not deleted or invalidated, leaving the asset purchasable by the old buyer.

function relist(address _asset, address _buyer, uint256 _price) external {
AssetListing storage asset = listings[_asset];
// only the seller can relist the asset
if (asset.seller != msg.sender) {
revert Unauthorized(msg.sender);
}
// asset must be listed
if (asset.status != AssetStatus.Listed) {
revert InvalidStatus(asset.status, AssetStatus.Listed);
}
// relist can only happen after the round of its listing has ended
// we check this via the old buyer, that is the existing asset.buyer
//
// note that asset is unlisted here, but is not bought at all
//
// perhaps it suffices to check `==` here, since buyer round
// is changed incrementially
(uint256 oldRound,,) = BuyerAgent(asset.buyer).getRoundPhase();
if (oldRound <= asset.round) {
revert RoundNotFinished(_asset, asset.round);
}
// now we move on to the new buyer
BuyerAgent buyer = BuyerAgent(_buyer);
(uint256 round, BuyerAgent.Phase phase,) = buyer.getRoundPhase();
// buyer must be in sell phase
if (phase != BuyerAgent.Phase.Sell) {
revert BuyerAgent.InvalidPhase(phase, BuyerAgent.Phase.Sell);
}
// buyer must not have more than `maxAssetCount` many assets
uint256 count = assetsPerBuyerRound[_buyer][round].length;
if (count >= getCurrentMarketParameters().maxAssetCount) {
revert AssetLimitExceeded(count);
}
// create listing
listings[_asset] = AssetListing({
createdAt: block.timestamp,
royaltyFee: buyer.royaltyFee(),
price: _price,
seller: msg.sender,
status: AssetStatus.Listed,
buyer: _buyer,
round: round
});
// add this to list of listings for the buyer for this round
assetsPerBuyerRound[_buyer][round].push(_asset);
// transfer royalties
transferRoyalties(listings[_asset]);
emit AssetRelisted(msg.sender, _buyer, _asset, _price);
}

Impact

Previous buyers can continue to purchase assets that have been relisted to others, potentially disrupting the new buyer's opportunity to acquire these assets.

Tools Used

Manual Review

Recommendations

Implement a mechanism to mark old listings as invalid or remove them once an asset is relisted.

Updates

Lead Judging Commences

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

Support

FAQs

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