20,000 USDC
View results
Submission Details
Severity: medium
Valid

Rounding error leads to borrowing loans without paying interest

Summary

There is a rounding error when calculating the interest and fees in the Lender::_calculateInterest() if the loan token (debt) has too small decimals. As a result, a borrower (or attacker) can borrow loans without paying interest.

Vulnerability Details

The _calculateInterest() calculates the interest (lenderInterest) and fees (protocolInterest) that a borrower has to pay for their loan. However, the formulas for calculating the interest and fees do not support the case that the loan token (debt) has too small decimals, leading to a rounding error.

function _calculateInterest(
Loan memory l
) internal view returns (uint256 interest, uint256 fees) {
uint256 timeElapsed = block.timestamp - l.startTimestamp;
@> interest = (l.interestRate * l.debt * timeElapsed) / 10000 / 365 days;
@> fees = (lenderFee * interest) / 10000;
interest -= fees;
}
  • interest (lenderInterest): https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L724

  • fees (protocolInterest): https://github.com/Cyfrin/2023-07-beedle/blob/658e046bda8b010a5b82d2d85e824f3823602d27/src/Lender.sol#L725

Proof of Concept

Consider the GUSD (Gemini dollar) token, which has 2 decimals.

  • l.debt: 100 GUSD

  • l.interestRate: 100 (1%) per year

  • timeElapsed: 86400 seconds (1 day)

  • lenderFee: 100 (1%)

interest = (l.interestRate * l.debt * timeElapsed) / 10000 / 365 days
= (100 * (100 * 1e2) * 86400) / 10000 / 31536000
= 8640 / 31536
= 0 (rounding down; since Solidity has no fixed-point numbers)
fees = (lenderFee * interest) / 10000
= (100 * 0) / 10000
= 0

As you can see, the resulting interest and fees will become 0 due to the rounding down since Solidity has no fixed-point numbers. Consequently, a borrower does not need to pay the lender interest or protocol interest.

Impact

As explained in the Proof of Concept section, a borrower does not need to pay the lender interest or protocol interest if the loan token (debt) has too small decimals (e.g., GUSD (Gemini dollar) has 2 decimals).

An attacker can leverage this vulnerability by borrowing a lot of short-term loan positions with a small debt. In this way, the attacker can borrow a bigger debt without paying interest.

Tools Used

Manual Review

Recommendations

I recommend verifying that the loan token (p.loanToken) must have sufficient decimals when creating a pool, as shown below. Please set the MIN_LOAN_TOKEN_DECIMALS constant with an appropriate value.

+ uint8 public constant MIN_LOAN_TOKEN_DECIMALS = 3; //@audit -- set with an appropriate value
...
function setPool(Pool calldata p) public returns (bytes32 poolId) {
// validate the pool
if (
p.lender != msg.sender ||
p.minLoanSize == 0 ||
p.maxLoanRatio == 0 ||
p.auctionLength == 0 ||
p.auctionLength > MAX_AUCTION_LENGTH ||
p.interestRate > MAX_INTEREST_RATE
) revert PoolConfig();
+ if (IERC20Metadata(p.loanToken).decimals() < MIN_LOAN_TOKEN_DECIMALS)
+ revert PoolConfig();
// check if they already have a pool balance
poolId = getPoolId(p.lender, p.loanToken, p.collateralToken);
...
}

Support

FAQs

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

Give us feedback!