Not considering liquidityThresholdPercent will make pool utilization ratio be wrongly computed
Summary
The pool’s utilization ratio does not consider the liquidityThresholdPercent . This will make users capable of paying loans with a smaller interest rate than the expected one.
Vulnerability Detail
When the acceptFundsForAcceptBid function in the lender commitment contract is called, a minimum interest rate will be enforced to match the current market demand:
// LenderCommitmentGroup_Smart.sol
function acceptFundsForAcceptBid(
address _borrower,
uint256 _bidId,
uint256 _principalAmount,
uint256 _collateralAmount,
address _collateralTokenAddress,
uint256 _collateralTokenId,
uint32 _loanDuration,
uint16 _interestRate
) external onlySmartCommitmentForwarder whenNotPaused {
...
//the interest rate must be at least as high has the commitment demands. The borrower can use a higher interest rate although that would not be beneficial to the borrower.
require(_interestRate >= getMinInterestRate(), "Invalid interest rate");
The minimum interest rate will be obtained from the getMinInterestRate function, which will perform the following computation to check the interest rate:
// LenderCommitmentGroup_Smart.sol
function getMinInterestRate() public view returns (uint16) {
return interestRateLowerBound + uint16( uint256(interestRateUpperBound-interestRateLowerBound).percent(getPoolUtilizationRatio()) );
}
As we can see, the current pool utilization ratio will serve as a percentage applied to the difference between interestRateUpperBound and interestRateLowerBound. For example, if interestRateLowerBound is 15%, interestRateUpperBound is 25% and getPoolUtilizationRatio returns 80%, then the minimum interest rate to pay will be 15% + (10% * 80%) = 23%.
The utilization ratio is then obtained in the following way:
// LenderCommitmentGroup_Smart.sol
function getPoolUtilizationRatio() public view returns (uint16) {
if (getPoolTotalEstimatedValue() == 0) {
return 0;
}
return uint16(
Math.min(
getTotalPrincipalTokensOutstandingInActiveLoans() * 10000
/ getPoolTotalEstimatedValue(),
10000
)
);
}
The idea is to check how many of the borrowable funds have actually been borrowed, and extract a ratio to see the total utilization of the pool. To achieve this, getTotalPrincipalTokensOutstandingInActiveLoans is divided by getPoolTotalEstimatedValue.
This calculation is wrong because getPoolTotalEstimatedValue returns the total amount of assets in the pool without considering the liquidityThresholdPercent parameter.
The liquidityThresholdPercent is a pool configuration used to limit the amount of funds that can be borrowed in the pool. When set to 100% , the entire pool can be drawn for lending. When set to 80% instead, only 80% of the pool can be drawn for lending.
This is not considered in the minimum interest rate calculation, which leads to computations always being performed with the entire amount of assets held in the pool (essentially, not considering this value is the same as assuming that the liquidityThresholdPercent is set to 100%).
The attached proof of concept shows how this issue allows users to borrow funds at a lower interest rate than the expected.
Proof of Concept
Consider the following scenario, where a new pool has been created with the following configuration parameters:
interestRateLowerBound: 15%
interestRateUpperBound: 25%
liquidityThresholdPercent: 55%
Let’s say that getTotalPrincipalTokensOutstandingInActiveLoans (the total amount borrowed) returns 100, and let’s also say that the total amount of pooled funds is 300.
With the current code, the minimum interest computation would be given by:
As we can see, the minimum interest rate that should be paid changes when the liquidity threshold is actually considered, with the current approach allowing borrowers to take loans with a lower interest rate.
0xadrii
medium
Not considering
liquidityThresholdPercent
will make pool utilization ratio be wrongly computedSummary
The pool’s utilization ratio does not consider the liquidityThresholdPercent . This will make users capable of paying loans with a smaller interest rate than the expected one.
Vulnerability Detail
When the
acceptFundsForAcceptBid
function in the lender commitment contract is called, a minimum interest rate will be enforced to match the current market demand:The minimum interest rate will be obtained from the
getMinInterestRate
function, which will perform the following computation to check the interest rate:As we can see, the current pool utilization ratio will serve as a percentage applied to the difference between
interestRateUpperBound
andinterestRateLowerBound
. For example, ifinterestRateLowerBound
is 15%,interestRateUpperBound
is 25% andgetPoolUtilizationRatio
returns 80%, then the minimum interest rate to pay will be 15% + (10% * 80%) = 23%.The utilization ratio is then obtained in the following way:
The idea is to check how many of the borrowable funds have actually been borrowed, and extract a ratio to see the total utilization of the pool. To achieve this,
getTotalPrincipalTokensOutstandingInActiveLoans
is divided bygetPoolTotalEstimatedValue
.This calculation is wrong because
getPoolTotalEstimatedValue
returns the total amount of assets in the pool without considering theliquidityThresholdPercent
parameter.The
liquidityThresholdPercent
is a pool configuration used to limit the amount of funds that can be borrowed in the pool. When set to 100% , the entire pool can be drawn for lending. When set to 80% instead, only 80% of the pool can be drawn for lending.This is not considered in the minimum interest rate calculation, which leads to computations always being performed with the entire amount of assets held in the pool (essentially, not considering this value is the same as assuming that the
liquidityThresholdPercent
is set to 100%).The attached proof of concept shows how this issue allows users to borrow funds at a lower interest rate than the expected.
Proof of Concept
Consider the following scenario, where a new pool has been created with the following configuration parameters:
Let’s say that
getTotalPrincipalTokensOutstandingInActiveLoans
(the total amount borrowed) returns 100, and let’s also say that the total amount of pooled funds is 300.With the current code, the minimum interest computation would be given by:
utilization ratio = 100/300 = 33.333…%
minimum interest rate = 15% + ((25% - 15%) * (33,333%)) = 18.333…% approximately
However, considering the
liquidityThresholdPercent
configuration, the following calculation should be applied:utilization ratio = 100 / (300 * 75%) = 60.06…% —> (The utilization ratio increases, as we can’t borrow the totality of the pool)
minimum interest rate = 15% + ((25% - 15%) * (60.06%)) = 21.006…% approximately
As we can see, the minimum interest rate that should be paid changes when the liquidity threshold is actually considered, with the current approach allowing borrowers to take loans with a lower interest rate.
getPoolTotalEstimatedValue() )).percent(liquidityThresholdPercent)
Impact
Medium. Borrowers will always be able to pay a smaller interest rate than the expected one. This will lead to lenders obtaining less funds.
Code Snippet
https://github.com/sherlock-audit/2024-04-teller-finance/blob/main/teller-protocol-v2-audit-2024/packages/contracts/contracts/LenderCommitmentForwarder/extensions/LenderCommitmentGroup/LenderCommitmentGroup_Smart.sol#L765
Tool used
Manual Review
Recommendation
Consider the
liquidityThresholdPercent
when computing the pool’s utilization:Duplicate of #70