Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: low
Valid

Incorrect parameter value emitted when repaying borrowed tokens in the lending pool

Summary

Users can repay their debt by calling the LendingPool::repay function. At the end of the function, the Repay event is emitted. However, the amount reported in the event is not the actual repaid amount returned by the burn function, which results in a wrong value emitted.

Vulnerability Details

The Repay event definition is the following:

> ILendingPool.sol
/**
* @notice Emitted when a user repays borrowed assets
* @param user The address of the user calling the repay function
* @param onBehalfOf The address of the user being repaid on behalf of
* @param amount The amount repaid
*/
event Repay(address indexed user, address indexed onBehalfOf, uint256 amount);

However, when emitted at the end of the LendingPool::_repay function, the amount reported is the actualRepayAmount and in most cases it is different than the real repaid amount, making the event not reliable.

This can be seen when an user decide to repay all of his DebtToken balance. actualRepayAmount is then capped at userScaledDebt = userDebt/reserve.usageIndex, which is a lower value than the input amount. Nevertheless, the actual amount repaid is contained in the amountScaled variable returned by the DebtToken.burn function, that coincides with the original amount provided by the user and is different than the actualRepayAmount value.

> LendingPool.sol
function _repay(uint256 amount, address onBehalfOf) internal {
if (amount == 0) revert InvalidAmount();
if (onBehalfOf == address(0)) revert AddressCannotBeZero();
UserData storage user = userData[onBehalfOf];
// Update reserve state before repayment
ReserveLibrary.updateReserveState(reserve, rateData);
// Calculate the user's debt (for the onBehalfOf address)
uint256 userDebt = IDebtToken(reserve.reserveDebtTokenAddress).balanceOf(onBehalfOf);
uint256 userScaledDebt = userDebt.rayDiv(reserve.usageIndex);
// If amount is greater than userDebt, cap it at userDebt
@> uint256 actualRepayAmount = amount > userScaledDebt ? userScaledDebt : amount;
uint256 scaledAmount = actualRepayAmount.rayDiv(reserve.usageIndex);
// Burn DebtTokens from the user whose debt is being repaid (onBehalfOf)
// is not actualRepayAmount because we want to allow paying extra dust and we will then cap there
// @audit - amountScaled is the actual repaid amount
@> (uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
// Transfer reserve assets from the caller (msg.sender) to the reserve
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
reserve.totalUsage = newTotalSupply;
user.scaledDebtBalance -= amountBurned;
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
// @audit - in most cases, actualRepayAmount is different than the real repaid amount
@> emit Repay(msg.sender, onBehalfOf, actualRepayAmount);
}

Impact

Impact: Low

Multiple associated services may display incorrect values, potentially misleading protocol administrators and consumers.

Likelihood: Medium

Tools Used

Manual Review

Recommendations

It is recommended to emit the correct value in the event.

> LendingPool.sol
function _repay(uint256 amount, address onBehalfOf) internal {
... snip
// Burn DebtTokens from the user whose debt is being repaid (onBehalfOf)
// is not actualRepayAmount because we want to allow paying extra dust and we will then cap there
(uint256 amountScaled, uint256 newTotalSupply, uint256 amountBurned, uint256 balanceIncrease) =
IDebtToken(reserve.reserveDebtTokenAddress).burn(onBehalfOf, amount, reserve.usageIndex);
// Transfer reserve assets from the caller (msg.sender) to the reserve
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
reserve.totalUsage = newTotalSupply;
user.scaledDebtBalance -= amountBurned;
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
- emit Repay(msg.sender, onBehalfOf, actualRepayAmount);
+ emit Repay(msg.sender, onBehalfOf, amountScaled);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_repay emits Repay event with capped actualRepayAmount instead of the real amountScaled value that was transferred, causing misleading event data

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_repay emits Repay event with capped actualRepayAmount instead of the real amountScaled value that was transferred, causing misleading event data

Support

FAQs

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

Give us feedback!