Submitted by Infect3d
Ditto is a decentralized CDP protocol allowing users to get stablecoins (called dUSD here) against LST (Liquid Staking Tokens like stETH or rETH) collateral.
Depositing LST grant dETH, a shares representing the value deposited in ETH.
Users can then open different kind of positions: bids, asks or shorts, each one having its own orderbook.
Ditto implemented a mechanism called Redemption, idea borrowed from Liquity. Redemption allows anyone to redeem dUSD for a value of exactly 1 USD by calling out shorts with very bad CR (collateral ratio) and closing them.
This helps the protocol to keep a healthy global market CR, shorters to not be liquidated, and dUSD holders to redeem dUSD with no loss in case of dUSD trading below 1 USD.
Users can propose multiple shorts to be redeemed at once if they meet certain conditions (see Constraints in doc).
The shorts must be sorted from lowest CR to highest CR, and all CRs must be below a CR of 2.
If a proposal do not properly follow these rules, anyone can dispute the proposal by showing a proof-short: all proposal shorts with a CR higher than the proof become invalid.
For each invalid short, a penalty is applied to the redeemer (and awarded to the disputer)
The penalty calculation is based on the CR difference between the disputeCR (proof) and the the currentProposal.CR (lowest invalid CR):
Note: please see scenario in wardens original submission.
The issue is located in this part of the mechanism and is pretty easy to understand.
Lets imagine 4 shorts in a list of shorts:   < CR1 < CR2 < CR3 < CR4 < 
CR1 = 1.1, CR2 = 1.2, CR3 = 1.3, CR4 = 1.4, and all have an ercDebt of 1 ETH.
Redeemer propose [CR2, CR3, CR4] to redeem.
Disputer sees that there is in fact CR1 that is lower than all the proposed shorts.
Disputer could dispute the proposal by giving CR1 as a proof, and CR2 as the invalid CR, which would also invalid all higher CRs in the proposal. This would give (lets take callerFeePct value from tests, 0.005 or 0.5%):
The vulnerability lies in the fact that the disputer can dispute this proposal 3 times:
By doing this, the disputer will get penalty applied 3 times, and in some case, the total penalty using this trick will be higher than the penalty when disputing the whole proposal at once, as shown in the coded proof of concept.
For CR1:
For CR2:
For CR3:
sum of penaltyAmt = 0.447 ETH.
We can see here how more beneficial it to adopt the second strategy.
Higher penalty than expected for redeemer, higher reward for disputer.
Copy this gist to test/Redemption.t.sol and run the tests by executing forge test -vvv --mt AuditDisputeRedemption.
Youll get that output, showing that disputing redemptions one by one give a higher penalty:
If a user gives N as the incorrectIndex in the disputed proposal, knowing that the proposal is sorted from lowest to highest CR, ensure that proposal[N-1].CR <= disputeCR (revert in that case).
Math
ditto-eth (DittoETH) confirmed, but disagreed with severity and commented:
hansfriese (judge) decreased severity to Medium and commented:
