Summary
The mint functions in the DebtToken and RToken contracts incorrectly scale the balance increase, leading to incorrect calculations. The balanceOf function already returns the scaled balance of the user, so scaling it again is incorrect.
Vulnerability Details
The current mint function in the DebtToken contract is:
function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
...
uint256 scaledBalance = balanceOf(onBehalfOf);
...
uint256 balanceIncrease = 0;
if (
_userState[onBehalfOf].index != 0 &&
_userState[onBehalfOf].index < index
) {
balanceIncrease =
scaledBalance.rayMul(index) -
scaledBalance.rayMul(_userState[onBehalfOf].index);
}
...
return (scaledBalance == 0, amountToMint, totalSupply());
}
The balanceIncrease is incorrectly scaled because balanceOf already returns the scaled balance.
The balanceOf function in 2 contracts is same i.e
function balanceOf(
address account
) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account);
return
scaledBalance.rayMul(
ILendingPool(_reservePool).getNormalizedDebt()
);
}
The same issue is present in the RToken contract's mint functions:
function mint(
address caller,
address onBehalfOf,
uint256 amountToMint,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256, uint256) {
...
uint256 scaledBalance = balanceOf(onBehalfOf);
...
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}
...
return (isFirstMint, amountToMint, totalSupply(), amountScaled);
}
The balanceIncrease is incorrectly scaled because balanceOf already returns the scaled balance.
Links to the issues:
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RToken.sol#L126
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/DebtToken.sol#L150
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/DebtToken.sol#L224
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/RToken.sol#L195
Impact
This issue can lead to incorrect calculations of the balance increase, potentially causing issues with token accounting and user balances within the protocol.
Tools Used
Manual code review.
Recommendations
Use the scaledBalanceOf function to get the non-scaled balance and avoid incorrect scaling. The corrected functions should be:
Corrected mint Function in DebtToken
function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
...
uint256 scaledBalance = scaledBalanceOf(onBehalfOf);
...
uint256 balanceIncrease = 0;
if (
_userState[onBehalfOf].index != 0 &&
_userState[onBehalfOf].index < index
) {
balanceIncrease =
scaledBalance.rayMul(index) -
scaledBalance.rayMul(_userState[onBehalfOf].index);
}
...
return (scaledBalance == 0, amountToMint, totalSupply());
}
Corrected mint Function in RToken
function mint(
address caller,
address onBehalfOf,
uint256 amountToMint,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256, uint256) {
...
uint256 scaledBalance = scaledBalanceOf(onBehalfOf);
...
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}
...
return (isFirstMint, amountToMint, totalSupply(), amountScaled);
}
This ensures that the correct balance is used in the calculations, leading to accurate token accounting and user balances.