Dria

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

Asset Count Limit Bypass

Summary

Asset Count Limit Bypass

Vulnerability Details

/// @notice Keeps track of assets per buyer & round.
mapping(address buyer => mapping(uint256 round => address[])) public assetsPerBuyerRound;
// asset count must not exceed `maxAssetCount`
if (getCurrentMarketParameters().maxAssetCount == assetsPerBuyerRound[_buyer][round].length) {
revert AssetLimitExceeded(getCurrentMarketParameters().maxAssetCount);
}
// buyer must not have more than `maxAssetCount` many assets
uint256 count = assetsPerBuyerRound[_buyer][round].length;
if (count >= getCurrentMarketParameters().maxAssetCount) {
revert AssetLimitExceeded(count);
}

maxAssetCount can be changed by owner after assets are created, however there is no check for total assets across all rounds. This could lead to buyer accumulating more assets than intended. The current implementation only checks asset count per round, not globally. Market parameters (like maxAssetCount) can be changed after assets are created and there's no relationship enforcement between rounds. A malicious buyer could accumulate more assets than intended by:

  • Exploiting parameter changes

  • Spreading assets across rounds

  • Using multiple rounds to bypass limits

Impact

Potential market manipulation. Circumvention of economic models

Tools Used

Manual Review

Recommendations

Should track total assets and implement invariant checks across rounds

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

[INVALID] List unlimited items

SwanManager::setMarketParameters pushes the new parameters `marketParameters.push(_marketParameters);` After that, when user calls list the protocol computes the round and the phase `(uint256 round, BuyerAgent.Phase phase,) = buyer.getRoundPhase();` Inside the getRoundPhase function you have this if statement on top: `if (marketParams.length == marketParameterIdx + 1) {`. The setMarketParameters call changed the `marketParams` length, thing which will case the first case to be false and run the else statement. At the end of that statement we see there is a new round. So the second element of this check `(getCurrentMarketParameters().maxAssetCount == assetsPerBuyerRound[_buyer][round].length` is zero, because the [round] is fresh.

Support

FAQs

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