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

Wrong LTV maths would make borrowing always fail for a pool of tokens with different decimals

Summary

A pool that is created with tokens of different decimals won’t allow users to borrow.

Vulnerability Details

Proof of concept

POC would work in test.Lender.t.sol

function test_Wrong_LTV_Maths() public {
uint256 forkId = vm.createFork("https://eth-mainnet.g.alchemy.com/v2/NsixqnW3NZrQWYxL7uqxtv36t3t6LlfY");
vm.selectFork(forkId);
Lender beedleLend = new Lender();
address usdc = address(0x7EA2be2df7BA6E54B1A9C70676f668455E329d29); // loan token 6 decimals
address wbtc = address(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); // collateral token 8 decimals
vm.label(beedleLend.feeReceiver(), "Fee Receiver");
address _lender = makeAddr("_lender");
address _borrower = makeAddr("_borrower");
deal(usdc, _lender, (100_000*1e6));
deal(wbtc, _borrower, (50*1e8));
Pool memory poolParams = Pool(
_lender,
usdc,
wbtc,
uint256(10_000*1e6),
uint256(100_000*1e6),
uint256(0.5*1e18),
beedleLend.MAX_AUCTION_LENGTH(),
0,
uint256(0)
);
vm.startPrank(_lender);
IERC20(usdc).approve(address(beedleLend), type(uint256).max);
bytes32 poolId1 = beedleLend.setPool(poolParams);
beedleLend.updateInterestRate(poolId1, 1000);
vm.stopPrank();
vm.startPrank(_borrower);
Borrow[] memory borrows = new Borrow[](1);
IERC20(wbtc).approve(address(beedleLend), type(uint256).max);
// collateral
borrows[0] = Borrow(poolId1,30_000*1e6,2*1e8);
vm.expectRevert();
beedleLend.borrow(borrows);
}

LTV is the ratio of the value of your loan to the value of your collateral.

Below is the formula used to calculate the Loan-to-Value ratio generally.

LTV = Loan Amount / Collateral Amount x 100%.

Beedle Lending pools use the same but with a twist of adding decimals formulae.
uint256 loanRatio = (debt * 10 ** 18) / collateral

In our case our lender creates an overcollaterized pool with a maximumLTV of 0.5, i.e with if 1 WBTC is 30k usd, the borrower must provide (2 * 30k usd) == 60k usd worth of USDC (maximum)

  1. Using a WBTC/USDC pool from a mainnet fork in our poc , WBTC has a decimal 8. while usdc has a decimal of 6.

  2. Lender creates a pool with setPool(poolParams), with 100_000*1e6 (100K usdc) poolBalance and minimum loan size of 10k usdc (10_000*1e6)

  3. Borrower wants to borrow 30k usdc (30k usd)with 2 WBTC(60K usd) (overcollaterized postion), wont be able to because in the division, the numerator is always significantly higher (6 decimals * 1e18) and denominator is lower (8 decimals), which means LTV is always higher than usual from the borrowers parameters, which is not even scaled up in calculations, this would always fail as LTV would always be too high

  • Even altering our own POC to use 0.5*1e8 or 0.5*1e6 (WBTC and USDC decimas) as loan to value ratio, a revert still occurs.

Impact

  • Borrower unable to borrow

Tools Used

  • manual review

Recommendations

using this scaled decimals formulae should work

  • token A and B

  • **To convert valueA to valueB: (8 dp -> 6 dp)

    • valueB = valueA / 10**(A.decimals - B.decimals)

    • valueB = valueA / (10**(8-6))

diff --git a/src/Lender.sol b/src/Lender.sol
index d04d14f..271798d 100644
--- a/src/Lender.sol
+++ b/src/Lender.sol
@@ -244,6 +244,14 @@ contract Lender is Ownable {
if (collateral == 0) revert ZeroCollateral();
// make sure the user isn't borrowing too much
uint256 loanRatio = (debt * 10 ** 18) / collateral;
+ // changes
+ if (IERC20(pool.collateralToken).decimals() > IERC20(pool.loanToken).decimals()) {
+ loanRatio = (debt * 10 ** (IERC20(pool.collateralToken).decimals() - IERC20(pool.loanToken).decimals())/ collateral);
+ }
+ if (IERC20(pool.loanToken).decimals() > IERC20(pool.collateralToken).decimals()) {
+ loanRatio = (debt * 10 ** (IERC20(pool.loanToken).decimals() - IERC20(pool.collateralToken).decimals())/ collateral);
+ }
+ //changes
if (loanRatio > pool.maxLoanRatio) revert RatioTooHigh();
// create the loan
Loan memory loan = Loan({
diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol
index e7f79ff..ab32abf 100644
--- a/src/interfaces/IERC20.sol
+++ b/src/interfaces/IERC20.sol
@@ -10,4 +10,5 @@ interface IERC20 {
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
+ function decimals() external returns(uint256);
:

Support

FAQs

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

Give us feedback!