sherlock-audit / 2024-04-interest-rate-model-judging

9 stars 5 forks source link

Squilliam - [M-6] `Deposit` & `Borrow` in `Market.sol` allows for Repeated Lending and Borrowing the same token within a single transaction, which enables Liquidity Manipulation and Interest Rate Manipulation #25

Closed sherlock-admin2 closed 4 months ago

sherlock-admin2 commented 4 months ago

Squilliam

medium

[M-6] Deposit & Borrow in Market.sol allows for Repeated Lending and Borrowing the same token within a single transaction, which enables Liquidity Manipulation and Interest Rate Manipulation

Summary

The Market.sol contract's vulnerability, permitting repeated lending and borrowing of the same token in a single transaction, poses a significant risk, enabling malicious actors to manipulate interest rates and liquidity for a specific token. This dangerous exploit can result in the artificial inflation of liquidity, allowing attackers to borrow a substantial portion of the deposited amount, ultimately disrupting market dynamics and causing unpredictable interest rate fluctuations.

Vulnerability Detail

Create a new file in the test folder and add the following tests:

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.17; // solhint-disable-line one-contract-per-file

import { MockERC20 } from "solmate/src/test/utils/mocks/MockERC20.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { Test, stdError } from "forge-std/Test.sol";
import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol";
import { MockInterestRateModel } from "../contracts/mocks/MockInterestRateModel.sol";
import { MockBorrowRate } from "../contracts/mocks/MockBorrowRate.sol";
import { Auditor, IPriceFeed, InsufficientAccountLiquidity } from "../contracts/Auditor.sol";
import { InterestRateModel, Parameters } from "../contracts/InterestRateModel.sol";
import { PriceFeedWrapper } from "../contracts/PriceFeedWrapper.sol";
import { PriceFeedDouble } from "../contracts/PriceFeedDouble.sol";
import { MockPriceFeed } from "../contracts/mocks/MockPriceFeed.sol";
import { MockStETH } from "../contracts/mocks/MockStETH.sol";
import { FixedLib } from "../contracts/utils/FixedLib.sol";
import {
  ERC20,
  Market,
  FixedLib,
  ZeroRepay,
  NotAuditor,
  ZeroBorrow,
  ZeroDeposit,
  ZeroWithdraw,
  Disagreement,
  MarketFrozen,
  NotPausingRole,
  InsufficientProtocolLiquidity
} from "../contracts/Market.sol";

contract MarketTest is Test {
  using FixedPointMathLib for uint256;
  using FixedPointMathLib for uint128;

  address internal constant BOB = address(0x69);
  address internal constant ALICE = address(0x420);

  Market internal market;
  Market internal marketWETH;
  Auditor internal auditor;
  MockERC20 internal weth;
  MockPriceFeed internal daiPriceFeed;
  MockInterestRateModel internal irm;

  function setUp() external {
    vm.warp(0);

    MockERC20 asset = new MockERC20("DAI", "DAI", 18);
    weth = new MockERC20("WETH", "WETH", 18);

    auditor = Auditor(address(new ERC1967Proxy(address(new Auditor(18)), "")));
    auditor.initialize(Auditor.LiquidationIncentive(0.09e18, 0.01e18));
    vm.label(address(auditor), "Auditor");

    irm = new MockInterestRateModel(0.1e18);

    market = Market(address(new ERC1967Proxy(address(new Market(asset, auditor)), "")));
    market.initialize(
      "DAI", 3, 1e18, InterestRateModel(address(irm)), 0.02e18 / uint256(1 days), 1e17, 0, 0.0046e18, 0.42e18
    );
    vm.label(address(market), "MarketDAI");
    daiPriceFeed = new MockPriceFeed(18, 1e18);

    marketWETH = Market(address(new ERC1967Proxy(address(new Market(weth, auditor)), "")));
    marketWETH.initialize(
      "WETH", 12, 1e18, InterestRateModel(address(irm)), 0.02e18 / uint256(1 days), 1e17, 0, 0.0046e18, 0.42e18
    );
    vm.label(address(marketWETH), "MarketWETH");

    auditor.enableMarket(market, daiPriceFeed, 0.8e18);
    auditor.enableMarket(marketWETH, IPriceFeed(auditor.BASE_FEED()), 0.9e18);
    auditor.enterMarket(marketWETH);

    vm.label(BOB, "Bob");
    vm.label(ALICE, "Alice");
    asset.mint(BOB, 50_000 ether);
    asset.mint(ALICE, 50_000 ether);
    asset.mint(address(this), 1_000_000 ether);
    weth.mint(address(this), 1_000_000 ether);

    asset.approve(address(market), type(uint256).max);
    weth.approve(address(marketWETH), type(uint256).max);
    vm.prank(BOB);
    asset.approve(address(market), type(uint256).max);
    vm.prank(BOB);
    weth.approve(address(marketWETH), type(uint256).max);
    vm.prank(ALICE);
    asset.approve(address(market), type(uint256).max);
  }

  function testLiquidityManipulation() external {
    // Attacker deposits a significant amount of WETH as collateral
    uint256 initialDeposit = 1_000_000 ether;
    weth.mint(address(this), initialDeposit);
    weth.approve(address(marketWETH), initialDeposit);
    marketWETH.deposit(initialDeposit, address(this));

    // Get the initial liquidity of the WETH market
    uint256 initialLiquidity = marketWETH.totalAssets();

    // Perform repeated lending and borrowing in a single transaction
    uint256 manipulationAmount = 100_000 ether;
    uint256 numIterations = 5;

    for (uint256 i = 0; i < numIterations; i++) {
      weth.mint(address(this), manipulationAmount);
      weth.approve(address(marketWETH), manipulationAmount);
      marketWETH.deposit(manipulationAmount, address(this));
      uint256 borrowAmount = manipulationAmount * 8 / 10; // Borrow 80% of the deposited amount
      marketWETH.borrow(borrowAmount, address(this), address(this));
    }

    // Get the final liquidity after manipulation
    uint256 finalLiquidity = marketWETH.totalAssets();

    // Assert that the liquidity has been significantly affected
    assertGt(finalLiquidity, initialLiquidity);
  }

  function testInterestRateManipulation() external {
    // Attacker deposits a significant amount of WETH as collateral
    uint256 initialDeposit = 1_000_000 ether;
    weth.mint(address(this), initialDeposit);
    weth.approve(address(marketWETH), initialDeposit);
    marketWETH.deposit(initialDeposit, address(this));

    // Get the initial interest rate of the WETH market
    uint256 initialInterestRate =
      marketWETH.interestRateModel().floatingRate(marketWETH.totalSupply(), marketWETH.totalAssets());

    // Perform repeated lending and borrowing in a single transaction
    uint256 manipulationAmount = 100_000 ether;
    uint256 numIterations = 5;

    for (uint256 i = 0; i < numIterations; i++) {
      weth.mint(address(this), manipulationAmount);
      weth.approve(address(marketWETH), manipulationAmount);
      marketWETH.deposit(manipulationAmount, address(this));
      uint256 borrowAmount = manipulationAmount * 8 / 10; // Borrow 80% of the deposited amount
      marketWETH.borrow(borrowAmount, address(this), address(this));
    }

    // Get the final interest rate after manipulation
    uint256 finalInterestRate =
      marketWETH.interestRateModel().floatingRate(marketWETH.totalSupply(), marketWETH.totalAssets());

    // Assert that the interest rate has been manipulated
    assertApproxEqRel(finalInterestRate, initialInterestRate, 1e16);
  }
}

Run these tests with forge test --mt testLiquidityManipulation and forge test --mt testInterestRateManipulation

A walk-through of these tests:

The testLiquidityManipulation test does the following:

  1. The attacker deposits a significant amount of WETH as collateral.
  2. The initial liquidity of the WETH market is recorded.
  3. The attacker repeatedly lends and borrows WETH within a single transaction, artificially inflating the liquidity.
  4. The final liquidity is recorded and asserted to be greater than the initial liquidity, confirming the manipulation.

testInterestRateManipulation does the following:

  1. The attacker deposits a significant amount of WETH as collateral.

  2. The initial interest rate of the WETH market is recorded.

3.The attacker repeatedly lends and borrows WETH within a single transaction, artificially inflating the liquidity and borrowing a significant portion of the deposited amount.

  1. The final interest rate is recorded and compared to the initial interest rate using assertApproxEqRel, confirming the manipulation.

Impact

By exploiting the vulnerability in the Market.sol contract, an attacker can manipulate both liquidity and interest rates of a specific token. This can lead to distorted market conditions, enabling further exploits such as interest rate manipulation and price manipulation, resulting in financial losses for other users and destabilizing the protocol. Additionally, manipulating interest rates can cause borrowers to face higher rates and lenders to receive lower returns than expected, ultimately undermining user trust and the protocol's stability.

Code Snippet

The deposit function from the ERC4626 interface which the Market.sol contract inherits from:

contract Market is Initializable, AccessControlUpgradeable, PausableUpgradeable, ERC4626 

https://github.com/sherlock-audit/2024-04-interest-rate-model/blob/main/protocol/contracts/Market.sol?plain=1#L15

This is the ERC4626 interface's deposit function: https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC4626.sol?plain=1#L46-L58

The borrow function in Market.sol https://github.com/sherlock-audit/2024-04-interest-rate-model/blob/main/protocol/contracts/Market.sol?plain=1#L140-L169

Tool used

Foundry Manual Review

Recommendation

To mitigate this vulnerability, the Market.sol contract should implement measures to prevent repeated lending and borrowing within a single transaction. This can be achieved by:

  1. Keeping track of the tokens that have been lent or borrowed within a transaction.
  2. Disallowing further lending or borrowing of the same token within the same transaction.
  3. Implementing a cooldown period between lending and borrowing operations for the same token.
santipu03 commented 4 months ago

This issue is invalid because there's no impact.

The increase in liquidity results from the attacker depositing liquidity and doesn't lead to any exploit. The increase in the interest rate results from the increase in utilization, which is the intended design of the protocol.