Dria

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

Missing Approval and Ownership Check Allows Malicious Actors to Block Entire Purchase Process

Summary

In the BuyerAgent::purchase function, assets are purchased in a loop without verifying each asset’s ownership and approval status individually. This structure introduces a vulnerability where a single asset’s failed purchase (e.g., due to lack of approval or ownership change) causes the entire transaction batch to revert. This is particularly problematic if a malicious lister intentionally blocks their asset’s sale, which prevents all other valid purchases in the loop.

Vulnerability Details

The purchase function attempts to buy multiple assets in a single loop but fails to verify ownership and approval status for each individual asset. If a lister revokes approval or transfers the asset to another account, the contract will revert when attempting to purchase and transfer the unapproved or transferred asset. This presents a vulnerability where:

  • Malicious Blocking: A lister can intentionally withhold approval or transfer the asset to prevent its purchase, causing all purchases in the batch to fail. User can do this without paying any protocol fee if asset price is zero.

  • Unintentional Blocking: If a lister simply decides not to sell and removes approval, there is no cancellation mechanism to remove the asset from the listing. User could transfer his asset to different account to prevent purchase which could unintentionally block valid asset purchases from other users.

function purchase() external onlyAuthorized {
//...
// we purchase each asset returned
for (uint256 i = 0; i < assets.length; i++) {
address asset = assets[i];
// must not exceed the roundly buy-limit
uint256 price = swan.getListingPrice(asset);
spendings[round] += price;
if (spendings[round] > amountPerRound) {
revert BuyLimitExceeded(spendings[round], amountPerRound);
}
// add to inventory
inventory[round].push(asset);
// make the actual purchase
swan.purchase(asset);
}

These issues disrupt the purchasing flow and listers have to pay additional fees to relist.

Impact

The entire batch reverts even if only one asset’s transfer fails, leading to failed purchases for legitimate asset listings. Lister have to pay additional fees to relist their assets.

Tools Used

Manual

Recommendations

Before each asset purchase, verify that the asset is owned by the expected lister and check for active approval to avoid failed transactions. If an asset fails these checks, skip it and proceed with valid purchases.

for (uint256 i = 0; i < assets.length; i++) {
address asset = assets[i];
// Ownership and approval check
+address seller = swan.getListing(asset).seller;
+if (SwanAsset(asset).owner() != seller || !SwanAsset(asset).isApprovedForAll(seller, address(swan))) {
+ continue; // Skip asset if not owned or approved
+}
// Proceed with purchase if checks pass
uint256 price = swan.getListingPrice(asset);
spendings[round] += price;
if (spendings[round] > amountPerRound) {
revert BuyLimitExceeded(spendings[round], amountPerRound);
}
inventory[round].push(asset);
swan.purchase(asset);

}

Updates

Lead Judging Commences

inallhonesty Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

DoS in BuyerAgent::purchase

Support

FAQs

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