Summary
The RToken::mint
function returns amountToMint
(unscaled value) in its second return parameter while the ReserveLibrary::deposit
function processing this value assuming it's amountScaled
. This mismatch creates an accounting inconsistency since the system internally works with scaled values to handle interest-bearing tokens correctly.
Vulnerability Details
function mint(
address caller,
address onBehalfOf,
uint256 amountToMint,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256, uint256) {
... omitted code
_mint(onBehalfOf, amountToMint.toUint128());
emit Mint(caller, onBehalfOf, amountToMint, index);
@> return (isFirstMint, amountToMint, totalSupply(), amountScaled);
}
function deposit(ReserveData storage reserve,ReserveRateData storage rateData,uint256 amount,address depositor) internal returns (uint256 amountMinted) {
...omitted code
@> (bool isFirstMint, uint256 amountScaled, uint256 newTotalSupply, uint256 amountUnderlying) = IRToken(reserve.reserveRTokenAddress).mint(
address(this),
depositor,
amount,
reserve.liquidityIndex
);
amountMinted = amountScaled;
updateInterestRatesAndLiquidity(reserve, rateData, amount, 0);
emit Deposit(depositor, amount, amountMinted);
return amountMinted;
}
Impact
The RToken::mint
function returns amountToMint
(unscaled value) in its second return parameter while the ReserveLibrary::deposit
function processing this value assuming it's amountScaled
. This value is assigned to a variable and returned to Lendingpool::deposit
. This leads to incorrect accounting of user shares in the protocol.
Tools Used
Manual review
Recommendations
Modify the parameters order as:
function deposit(ReserveData storage reserve,ReserveRateData storage rateData,uint256 amount,address depositor) internal returns (uint256 amountMinted) {
...omitted code
// Mint RToken to the depositor (scaling handled inside RToken)
- (bool isFirstMint, uint256 amountScaled, uint256 newTotalSupply, uint256 amountUnderlying) = IRToken(reserve.reserveRTokenAddress).mint(
+ (bool isFirstMint, uint256 amountUnderlying, uint256 newTotalSupply, uint256 amountScaled) = IRToken(reserve.reserveRTokenAddress).mint(
address(this), // caller
depositor, // onBehalfOf
amount, // amount
reserve.liquidityIndex // index
);
amountMinted = amountScaled;
// Update the total liquidity and interest rates
updateInterestRatesAndLiquidity(reserve, rateData, amount, 0);
emit Deposit(depositor, amount, amountMinted);
return amountMinted;
}