Dria

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

If the payment transfer fails after the asset transfer, the buyer would have received the asset without paying

Summary

The Swan protocol's purchase mechanism executes asset transfers before receiving payment, creating a vulnerability where buyers can acquire assets without paying by triggering payment failures after asset transfer completion.

Vulnerability Details

The purchase function executes transfers in an unsafe sequence: https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/swan/Swan.sol#L276-L299

function purchase(address _asset) external {
// ... status and authorization checks ...
// @Issue - Status updated before payment verification
listing.status = AssetStatus.Sold;
// @Issue - Asset transferred before payment
SwanAsset(_asset).transferFrom(listing.seller, address(this), 1);
SwanAsset(_asset).transferFrom(address(this), listing.buyer, 1);
// @Issue - Payment occurs after asset transfer
token.transferFrom(listing.buyer, address(this), listing.price);
token.transfer(listing.seller, listing.price);
}

The bug occurs because:

  1. The purchase function transfers tokens but doesn't update the buyer's round spending tracker

  2. Without tracking, a buyer could exceed their round spending limit

Proof of Concept:

  1. Buyer has spending limit of 100 tokens per round

  2. Buyer purchases asset for 60 tokens

  3. Spending not tracked

  4. Buyer can purchase another 60 token asset

  5. Total spent (120) exceeds limit (100) due to lack of tracking

  1. The function transfers the asset before receiving payment, violating the Checks-Effects-Interactions pattern

  2. During the asset transfer, the buyer could reenter the contract through callbacks

  3. If the payment transfer fails after the asset transfer, the buyer would have received the asset without paying

  4. The asset status is updated to Sold before ensuring payment, creating a potential state inconsistency

The correct sequence should be:

  1. Check conditions

  2. Take payment

  3. Update state

  4. Transfer asset

  5. Emit event

Impact

  • Buyers can obtain assets without payment

  • Sellers can lose assets without compensation

Recommendations

function purchase(address _asset) external {
AssetListing storage listing = listings[_asset];
// ... validation checks ...
token.transferFrom(listing.buyer, address(this), listing.price);
token.transfer(listing.seller, listing.price);
+
+ // Track spending for the round
+ BuyerAgent buyer = BuyerAgent(msg.sender);
+ (uint256 round,,) = buyer.getRoundPhase();
+ roundSpending[msg.sender][round] += listing.price;
emit AssetSold(listing.seller, msg.sender, _asset, listing.price);
}
Updates

Lead Judging Commences

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

Support

FAQs

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