Moonwell

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

Users can avoid being liquidated.

Summary

Users can avoid being liquidated.

Vulnerability Details

principal comes from borrowBalanceStored(), fetch user's current borrow balance.
liquidated comes from accountTokens[user], get current amount for a user that will transfer to the liquidator.
borrowBalanceStored() is obtained through borrowBalanceStoredInternal() in MToken.sol.

function borrowBalanceStored(address account) public view returns (uint) {
(MathError err, uint result) = borrowBalanceStoredInternal(account);
require(err == MathError.NO_ERROR, "borrowBalanceStored: borrowBalanceStoredInternal failed");
return result;
}

The borrow balance of the account is calculated using the value of accountBorrows[user], and then returned.
liquidated is taken from accountTokens[user].
In MToken.sol, accountTokens[user] is affected by functions like mintFresh, redeemFresh, transferTokens, etc.
Consider this scenario :
a user is about to be liquidated by an admin using fixUser().
The user can front-run and use transfer() ,
transferring the balance of accountTokens[user] to avoid being liquidated.
transfer > transferTokens

function transferTokens(address spender, address src, address dst, uint tokens) internal returns (uint) {
/* Fail if transfer not allowed */
uint allowed = comptroller.transferAllowed(address(this), src, dst, tokens);
if (allowed != 0) {
return failOpaque(Error.COMPTROLLER_REJECTION, FailureInfo.TRANSFER_COMPTROLLER_REJECTION, allowed);
}
/* Do not allow self-transfers */
if (src == dst) {
return fail(Error.BAD_INPUT, FailureInfo.TRANSFER_NOT_ALLOWED);
}
/* ... */
(mathErr, srcTokensNew) = subUInt(accountTokens[src], tokens);
if (mathErr != MathError.NO_ERROR) {
return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_NOT_ENOUGH);
}
(mathErr, dstTokensNew) = addUInt(accountTokens[dst], tokens);
if (mathErr != MathError.NO_ERROR) {
return fail(Error.MATH_ERROR, FailureInfo.TRANSFER_TOO_MUCH);
}
/////////////////////////
// EFFECTS & INTERACTIONS
// (No safe failures beyond this point)
accountTokens[src] = srcTokensNew;
accountTokens[dst] = dstTokensNew;
/* ... */
return uint(Error.NO_ERROR);
}

user's current borrow balance become 0
accountBorrows[user].principal = 0
even if user's liquidated's value == 0

Impact

Users won't lose any tokens, the borrow balance will be zero.

Tools Used

Recommendations

Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-front-run-fixUser

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other
Assigned finding tags:

finding-front-run-fixUser

Support

FAQs

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