20,000 USDC
View results
Submission Details
Severity: high

loanToken and collateralToken must be the exact same price for borrowing to work effectively

Summary

The protocol doesn't use any oracle to derive any prices. As such, the loanToken and collateralToken must be carefully taken into consideration, otherwise the Lender may lose all of his loanTokens.

Vulnerability Details

For the protocol to work, the Lender must first set up a pool and deposit a certain amount of loanToken into the contract. Next, a borrower will attempt to borrow a certain amount of loanToken in the pool by depositing a relevant amount of collateralToken.

function borrow(Borrow[] calldata borrows) public {
for (uint256 i = 0; i < borrows.length; i++) {
bytes32 poolId = borrows[i].poolId;
uint256 debt = borrows[i].debt;
uint256 collateral = borrows[i].collateral;
// get the pool info
Pool memory pool = pools[poolId];
// make sure the pool exists
if (pool.lender == address(0)) revert PoolConfig();
// validate the loan
if (debt < pool.minLoanSize) revert LoanTooSmall();
if (debt > pool.poolBalance) revert LoanTooLarge();
if (collateral == 0) revert ZeroCollateral();
// make sure the user isn't borrowing too much
uint256 loanRatio = (debt * 10 ** 18) / collateral;
if (loanRatio > pool.maxLoanRatio) revert RatioTooHigh();
// create the loan
Loan memory loan = Loan({
lender: pool.lender,
borrower: msg.sender,
loanToken: pool.loanToken,
collateralToken: pool.collateralToken,
debt: debt,
collateral: collateral,
interestRate: pool.interestRate,
startTimestamp: block.timestamp,
auctionStartTimestamp: type(uint256).max,
auctionLength: pool.auctionLength
});
// update the pool balance

This interaction is extremely hard to pull off without the use of any oracles to determine any sort of prices. Let's imagine that loanToken is WETH and collateral token is DAI. If a lender deposits 1000 WETH in the contract, a borrower can put up 1000 DAI to withdraw 1000 WETH because the prices are assumed to be the same, which is not good for the lender. If loanToken is SHIB and collateral token is WETH, then the borrower can only borrow 1 SHIB for every WETH if LTV is 100%, which doesn't make any sense.

In the test suit, the loanToken and collateralToken is the same, which also doesn't make a lot of sense. Imagine both loanToken and collateralToken is USDT. This means that the borrower intends to put up 1000 USDT as collateral to borrow 1000 USDT.

Attached is the original test suite which shows the problem. Both loanToken and collateralToken are TERC20 tokens, and the borrower deposits 100 TERC20 token as collateral to borrow 100 TERC20 tokens.

function test_borrow() public {
vm.startPrank(lender1);
Pool memory p = Pool({
lender: lender1,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100*10**18,
poolBalance: 1000*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 days,
interestRate: 1000,
outstandingLoans: 0
});
bytes32 poolId = lender.setPool(p);
(,,,,uint256 poolBalance,,,,) = lender.pools(poolId);
assertEq(poolBalance, 1000*10**18);
vm.startPrank(borrower);
Borrow memory b = Borrow({
poolId: poolId,
debt: 100*10**18, //@audit Here, the borrower borrows 100 TERC20 token
collateral: 100*10**18 //@audit The borrower puts 100 TERC20 token as collateral
});
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = b;
lender.borrow(borrows);
assertEq(loanToken.balanceOf(address(borrower)), 995*10**17);
assertEq(collateralToken.balanceOf(address(lender)), 100*10**18);
(,,,,poolBalance,,,,) = lender.pools(poolId);
assertEq(poolBalance, 900*10**18);
}

Run the following test with: forge test --match-test test_borrow -vv. The test passes, indicating that the borrower has indeed borrowed 100 TERC20 tokens with 100 TERC20 tokens as collateral.

Impact

If tokens have different prices, loanToken being more expensive than collateralToken, then the borrower can trade the lower value token for a higher value token, and the lender loses out. If tokens are the same, then there is no point to borrow or lend tokens.

Tools Used

Foundry, Manual Review

Recommendations

Recommend changing the way borrow works.

Support

FAQs

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