20,000 USDC
View results
Submission Details
Severity: high

Malicious user can receive `loanToken` without posting any `collateralToken`, essentially stealing funds from other users

Summary

Some ERC20 tokens return false instead of reverting when transfer/transferFrom is called. This is not properly handled by the protocol and can lead to loss of funds.

Vulnerability Details

In many areas of the protocol, ERC20's transfer/transferFrom is called but the return value is not checked. Since some tokens return false upon failed transfer instead of reverting, this means that the transfer may fail while the contract believes it was successful. This allows malicious users to steal funds from others.

Impact

In Lender, if the collateralToken for a loan is vulnerable to silent failures, a borrower may call borrow and the transfer of collateral from the borrower to the Lender may fail. In this case, the borrower will receive the loanToken without providing any collateral, stealing all funds from the lender at zero cost.

File: src\Lender.sol
232: function borrow(Borrow[] calldata borrows) public {
233: for (uint256 i = 0; i < borrows.length; i++) {
234: bytes32 poolId = borrows[i].poolId;
235: uint256 debt = borrows[i].debt;
236: uint256 collateral = borrows[i].collateral;
237: // get the pool info
238: Pool memory pool = pools[poolId];
239: // make sure the pool exists
240: if (pool.lender == address(0)) revert PoolConfig();
241: // validate the loan
242: if (debt < pool.minLoanSize) revert LoanTooSmall();
243: if (debt > pool.poolBalance) revert LoanTooLarge();
244: if (collateral == 0) revert ZeroCollateral();
245: // make sure the user isn't borrowing too much
246: uint256 loanRatio = (debt * 10 ** 18) / collateral;
247: if (loanRatio > pool.maxLoanRatio) revert RatioTooHigh();
248: // create the loan
249: Loan memory loan = Loan({
250: lender: pool.lender,
251: borrower: msg.sender,
252: loanToken: pool.loanToken,
253: collateralToken: pool.collateralToken,
254: debt: debt,
255: collateral: collateral,
256: interestRate: pool.interestRate,
257: startTimestamp: block.timestamp,
258: auctionStartTimestamp: type(uint256).max,
259: auctionLength: pool.auctionLength
260: });
261: // update the pool balance
262: _updatePoolBalance(poolId, pools[poolId].poolBalance - debt);
263: pools[poolId].outstandingLoans += debt;
264: // calculate the fees
265: uint256 fees = (debt * borrowerFee) / 10000;
266: // transfer fees
267: IERC20(loan.loanToken).transfer(feeReceiver, fees);
268: // transfer the loan tokens from the pool to the borrower
269: IERC20(loan.loanToken).transfer(msg.sender, debt - fees);
270: // transfer the collateral tokens from the borrower to the contract
271: IERC20(loan.collateralToken).transferFrom( // @audit if this transfer fails, then the caller steals the lenders tokens
272: msg.sender,
273: address(this),
274: collateral
275: );
276: loans.push(loan);
277: emit Borrowed(
278: msg.sender,
279: pool.lender,
280: loans.length - 1,
281: debt,
282: collateral,
283: pool.interestRate,
284: block.timestamp
285: );
286: }
287: }

Tools Used

Manual review

Recommendations

Use OpenZeppelin's SafeERC20 library for token transfers.

Support

FAQs

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