Core Contracts

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

Double-counting of accrued debt interest in `DebtToken::mint` overstates the borrower’s actual liability.

Vulnerability Details

As stated in in-line docs, the DebtToken::mint aligns with AaveV3's VariableDebtToken implementation. Aave's VariableDebtToken implementation increases debt balance of a user by scaled amount via amount.rayDiv(index). It also calculates balanceIncrease, but uses it solely for event emission.

Snippet from Aave's _mintScaled:

uint256 amountScaled = amount.rayDiv(index);
...
...
...
// scaled amount minted here since aave didn't override _update
@> _mint(onBehalfOf, amountScaled.toUint128());
@> uint256 amountToMint = amount + balanceIncrease;
@> emit Transfer(address(0), onBehalfOf, amountToMint);
@> emit Mint(caller, onBehalfOf, amountToMint, balanceIncrease, index);

In current DebtToken::mint implementation, the balanceIncrease calculated is added to the amount that's being minted before calling _mint.

@> uint256 amountToMint = amount + balanceIncrease;
@> _mint(onBehalfOf, amountToMint.toUint128()); // amount provided will be scaled in _update

The compounded interest is accounted for in DebtToken::_update so adding it here results in inflated debt of user on each borrow. This can have the following consequences,

  1. User would have to unnecessarily repay more than what they borrowed, even after accounting for interest.

  2. User's debt is overstated which can lead to liquidation of user's collateral even when they have collateral
    that's worth enough.

Impact

Current implementation a significant deviation from Aave V3’s debt token model that overstates user's debt leading to unnecessary liquidations and loss of assets.

Tools Used

Manual Review + Hardhat Testing

Proof-Of-Code

The following custom function was added to DebtToken for testing only,

// @ custom-added-function
function rayDivOperation(uint256 amount, uint256 _index) external pure returns (uint256) {
return amount.rayDiv(_index);
}

Place the following test under Borrow and Repay in LendingPool.test.js

it("borrow and repay with inflated amount after change in usageIndex", async() => {
// borrow
const borrowAmount = ethers.parseEther("20");
const borrowAmount2 = ethers.parseEther("30");
const borrowAmount3 = ethers.parseEther("10");
await lendingPool.connect(user2).borrow(borrowAmount2);
await debtToken.balanceOf(user2.address);
await lendingPool.connect(user3).borrow(borrowAmount3);
await debtToken.balanceOf(user3.address);
const reserve = await lendingPool.reserve();
reserve.usageIndex; // 1000000e21
await lendingPool.connect(user1).borrow(borrowAmount);
// CURRENT BALANCE OF USER1: 20 debtToken
// update time
await ethers.provider.send("evm_increaseTime", [5 * 24 * 60 * 60]); // 3 days
await ethers.provider.send("evm_mine", []);
await lendingPool.updateState();
const reserve2 = await lendingPool.reserve();
const currentUsageIndex = reserve2.usageIndex; // 1000381e21
// NOTE: debtToken.scaledBalanceOf should be used here and in the mint function
// but balanceOf is used for testing since that's what's erroneously used
// in DebtToken::mint.
const scaledBalanceOfUser = await debtToken.balanceOf(user1.address);
const scaledAmountMinted = await debtToken.rayDivOperation(borrowAmount, currentUsageIndex);
// EXPECTED BALANCE = userBalance + amount.rayDiv(index)
const expectedBalance = scaledBalanceOfUser + scaledAmountMinted;
await lendingPool.connect(user1).borrow(borrowAmount);
const actualBalance = await debtToken.balanceOf(user1.address);
console.log("expected balance of user1: ", expectedBalance); // 40000002903115652494n
console.log("actual balance of user1: ", actualBalance); // 40015245533733669214n
expect(actualBalance).to.gt(expectedBalance);
})

Recommendations

Remove balanceIncrease from amount being minted and use scaledBalanceOf for balanceIncrease calculation,

- uint256 scaledBalance = balanceOf(onBehalfOf);
+ uint256 scaledBalance = scaledBalanceOf(onBehalfOf);
...
- uint256 amountToMint = amount + balanceIncrease;
- _mint(onBehalfOf, amountToMint.toUint128());
+ _mint(onBehalfOf, amount.toUint128());
Updates

Lead Judging Commences

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

DebtToken::mint miscalculates debt by applying interest twice, inflating borrow amounts and risking premature liquidations

Support

FAQs

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