calculateDustAmount returns amount of tokens, which is not owned by the users. So it could be transfered out, but because usdCSV amount is multiplied twice by normalizedIncome. Protocol assumes users have more tokens that they actually do.
Contract is calculating contractBalance which is equal to amount of CRVusd tokens owned by the contract. And this is calculated correctly, because CRVusd amount in first multiplied by normalizedIncome inside balanceOf, so here it is divided by the same value, to get back original CRVusd amount.
But the problem is in calculating totalRealBalance, it calls totalSupply which is also multiplies CRVusd amount by normalizedIncome, so it includes the income. And later in line 17 of provided code snippet it multplies that value again by normalizedIncome, which is wrong.
Probably protocol also wanted to use division there to get back original amount of CRVusd token so it can compare this value to contractBalance, but wrote multiply by mistake.
This function is used by transferAccruedDust, so it actually calculates how many tokens can be transferred from the contract. Because of cap in line 358 (line 10 in code snippet below), ReservePool wouldn't be able to transfer more tokens, than calculateDustAmount allows to
Because normalizedIncome will always be greater than or equal to 1. totalRealBalance will be bigger than it should be, so some tokens would end up locked inside the contract.
Manual Review
One way of soliving it is to replace rayMul with rayDiv for totalRealBalance calculation
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.