Dria

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

Missing asset price validation in list()/relist()

Summary

Asset prices can be made very small (0 to dust amounts) to avoid paying any buyerFees. At the same time if it will be high enough it will cause an overflow and fail while trying to calculate the buyerFee.

Vulnerability Details

Swan list()/relist() functions do not validate price in anyway, therefor it's possible to pass both 0 or dust amounts and uint256 max values.

The former (0 to dust amounts) allows to create assets without paying any fees and still requiring the buyerAgent to pay dust amounts on purchase. At the same time, because there is no fee it allows to pretty much DoS single, few or all buyerAgents with unwanted assets. The gas cost will depend on max allowed assetsPerBuyerRound in our measurements when this is set to 3 it would cost:

  • ~320k gas to fill all single BuyerAgent round assets using list()

  • ~250k gas to fill all single BuyerAgent round assets using relist()

At the time of writing the cost would be:

  • ~320k gas -> ~0.016USD

  • ~250k gas -> ~0.0124USD

Which isn't that much to block for a single buyerAgent per round.

Impact

This lack of validation can cause multiple problems, but none of them really benefit the attacker that much and at most causes disruption to the protocol and allows receiving dust amounts from buyer agents - so low.

Tools Used

Manual review + hardhat tests

it("Seller can clog buyer agents using list()/relist() with useless assets preventing more options", async function () {
const agentParams = {
name: NAME,
description: DESC,
royaltyFee: ROYALTY_FEE,
amountPerRound: AMOUNT_PER_ROUND,
owner: buyer,
};
const [buyerAgent] = await createBuyers(swan, [agentParams]);
// fund seller with very small amount
await transferTokens(token, [[await seller.getAddress(), 1000n]]);
// seller approves all own balance
await token.connect(seller).approve(swan, await token.balanceOf(seller));
const balanceBefore = await token.balanceOf(seller);
// seller spams lists asset with exactly 99 dust amount
await swan.connect(seller).list(NAME, SYMBOL, DESC, 99, await buyerAgent.getAddress());
await swan.connect(seller).list(NAME, SYMBOL, DESC, 99, await buyerAgent.getAddress());
await swan.connect(seller).list(NAME, SYMBOL, DESC, 99, await buyerAgent.getAddress());
await swan.connect(seller).list(NAME, SYMBOL, DESC, 99, await buyerAgent.getAddress());
await swan.connect(seller).list(NAME, SYMBOL, DESC, 99, await buyerAgent.getAddress());
await expect(swan.connect(seller).list(NAME, SYMBOL, DESC, 99, await buyerAgent.getAddress()))
.to.be.revertedWithCustomError(swan, "AssetLimitExceeded")
.withArgs(5);
const balanceAfter = await token.balanceOf(seller);
// in the next round the seller can relist if the buyerAgent didn't choose to buy the useless assets
expect(balanceBefore).to.be.equal(balanceAfter, "Seller didn't pay any tokens");
});

Recommendations

Validate prices - don't allow very low or very high values.

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 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.