Moonwell

Moonwell
DeFiFoundry
15,000 USDC
View results
Submission Details
Severity: medium
Invalid

Incorrect totalReserves within `repayBadDebtWithReserves()` due to missing interest accrual

Vulnerability Details

when repaying bad debt with reserves it compares the totalReserves and badDebt to determine which value to use/sufficient to repay the debt.
https://github.com/Cyfrin/2024-03-Moonwell/blob/main/src/MErc20DelegateFixer.sol#L49

function repayBadDebtWithReserves() external nonReentrant {
@> uint256 currentReserves = totalReserves;
@> uint256 currentBadDebt = badDebt;
require(currentReserves != 0, "reserves are zero");
require(currentBadDebt != 0, "bad debt is zero");
/// no reverts possible past this point
/// take the lesser of the two, subtract it from both numbers
@> uint256 subtractAmount = currentBadDebt < currentReserves
? currentBadDebt
: currentReserves;
/// bad debt -= subtract amount
@> badDebt = SafeMath.sub(currentBadDebt, subtractAmount);
/// current reserves -= subtract amount
totalReserves = SafeMath.sub(currentReserves, subtractAmount);
...SNIP
}

but totalReserves is out of sync with it actual value since the interest accrued upto that point is not updated.
Within accrueInterest():

function accrueInterest() public returns (uint) {
...SNIP//TRUNCATED for brevity
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
/* We write the previously calculated values into storage */
accrualBlockTimestamp = currentBlockTimestamp;
borrowIndex = borrowIndexNew;
totalBorrows = totalBorrowsNew;
@> totalReserves = totalReservesNew;
/* We emit an AccrueInterest event */
emit AccrueInterest(cashPrior, interestAccumulated, borrowIndexNew, totalBorrowsNew);
return uint(Error.NO_ERROR);
}

Impact

totalReserves would be out of sync with it's actual value, the actual value might be smaller or larger that the current reserves at that point.

Tools Used

Manual Review

Recommendations

call accrueInterest() before utilizing the totalReserves storage

function repayBadDebtWithReserves() external nonReentrant {
+ accrueInterest()
uint256 currentReserves = totalReserves;
uint256 currentBadDebt = badDebt;
require(currentReserves != 0, "reserves are zero");
require(currentBadDebt != 0, "bad debt is zero");
/// no reverts possible past this point
/// take the lesser of the two, subtract it from both numbers
uint256 subtractAmount = currentBadDebt < currentReserves
? currentBadDebt
: currentReserves;
/// bad debt -= subtract amount
badDebt = SafeMath.sub(currentBadDebt, subtractAmount);
/// current reserves -= subtract amount
totalReserves = SafeMath.sub(currentReserves, subtractAmount);
emit BadDebtRepayedWithReserves(
badDebt,
currentBadDebt,
totalReserves,
currentReserves
);
}
Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other
kodyvim Submitter
over 1 year ago
0xnevi Lead Judge
over 1 year ago
kodyvim Submitter
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other

Support

FAQs

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