A logical flaw exists in the list function of the swan smart contract that arises when the market parameters are updated, specifically when the maxAssetCount parameter is decreased to a value lower than the existing number of listed assets for a particular round. This allows the addition of an unlimited number of assets in that round, leading to an uncontrolled asset listing scenario.
The issue occurs in the following condition within the list function https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/swan/Swan.sol?plain=1#L168-170 :
If the market parameter maxAssetCount changes to a value lower than the current number of assets already listed for that buyer in a particular round, this check no longer prevents additional assets from being listed. For instance, if assetsPerBuyerRound[_buyer][round].length is 5 and the maxAssetCount is subsequently decreased to 3, the equality condition will never be true. Therefore, further listings are allowed, and the number of assets for that round can exceed the new limit without bounds, effectively allowing infinite asset listings.
This logical flaw can lead to uncontrolled asset listing during a round, where the intended maximum limit (maxAssetCount) becomes ineffective due to changes in market parameters. This undermines the integrity of the maxAssetCount mechanism, resulting in a situation where an attacker or user can list an infinite number of assets in a given round, even after the maxAssetCount has been lowered.
Manual review
To resolve this issue, change the condition from == to >= to ensure that additional assets cannot be listed when the current number of assets exceeds the new maxAssetCount.
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.