Dria

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

Round Manipulation Through Purchase-Relist Race Condition

Summary

Swan protocol allows manipulation of market rounds through a race condition between purchase and relist functions. This enables bypassing intended round progression and phase separation mechanisms.

Vulnerability Details

The vulnerability exists in two key functions:

function purchase(address _asset) external {
AssetListing storage listing = listings[_asset];
// @bug detected: No round validation
// Allows purchases without verifying round validity
if (listing.status != AssetStatus.Listed) {
revert InvalidStatus(listing.status, AssetStatus.Listed);
}
// ... continues
}
function relist(address _asset, address _buyer, uint256 _price) external {
// @bug detected: Insufficient round check
// Only checks oldRound <= asset.round
(uint256 oldRound,,) = BuyerAgent(asset.buyer).getRoundPhase();
if (oldRound <= asset.round) {
revert RoundNotFinished(_asset, asset.round);
}
}

The lack of round validation in purchase() combined with weak validation in relist() creates a race condition allowing rapid cycling through rounds.

Expected Behavior

  • Assets should remain locked to their listed round

  • Round transitions should follow strict Sell->Buy->Withdraw phase progression

  • Relisting should only be possible after full round completion

This creates a potential race condition where:

  1. An asset is listed in round N

  2. A buyer can purchase it in round N

  3. The seller could immediately relist it in round N+1

  4. This allows cycling through rounds faster than intended

Impact

  • Market manipulation through accelerated round progression

  • Breaking of phase separation (Sell->Buy->Withdraw)

  • Oracle system gaming via rapid round transitions

  • Economic exploitation through fee collection manipulation

  • Disruption of price discovery mechanisms

Tools Used

Manual Review

Recommendations

  1. Implement strict round validation in purchase(

function purchase(address _asset) external {
AssetListing storage listing = listings[_asset];
+ (uint256 currentRound, BuyerAgent.Phase phase,) = BuyerAgent(listing.buyer).getRoundPhase();
+ if (currentRound != listing.round || phase != BuyerAgent.Phase.Buy) {
+ revert InvalidRoundOrPhase(currentRound, listing.round, phase);
+ }
if (listing.status != AssetStatus.Listed) {
revert InvalidStatus(listing.status, AssetStatus.Listed);
}
// ...
}
  1. Strengthen relist() round checks

function relist(address _asset, address _buyer, uint256 _price) external {
(uint256 currentRound,,) = BuyerAgent(asset.buyer).getRoundPhase();
require(currentRound > asset.round + 1, "Round not fully completed");
// ...
}
Updates

Lead Judging Commences

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

Support

FAQs

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