xanoutas/solidity-security-findings huggingface dataset:
Interest accumulation linked to state updates may leak value
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L26-L29


# Vulnerability details

## Impact
The protocol compounds interest on every call that updates the state. This is an intentional design choice. However, this does mean that the total return for the lender, and, conversely, the cost of debt for the borrower, can be influenced by the frequency of calls that update the state and by extension compound the interest.  

The APR-based nature of the debt is made clear in the documentation, but what isn't explicitly stated is that for lenders to maximize their return they should endeavour to update the state through a call to `WildcatMarket::updateState()` weekly. 

In the absence of a lender that regularly interacts with the protocol to compound interest, a borrower should aim to interact as little as possible with the protocol; this may be the difference between quarterly and weekly compounding.

This is stated in the whitepaper, but not in the Gitbook or other documentation (that the reviewer is aware of). The implication of this for the lender is not made clear. The below is the relevant excerpt from the [whitepaper](https://github.com/wildcat-finance/wildcat-whitepaper/blob/main/whitepaper_v0.2.pdf):

> Fixed yield rates are chosen at deployment, specified in an APR that
compounds each time someone interacts with the vault in a successful
non-static call. Rates can be adjusted upwards by the borrower in order
to incentivise lenders to participate (and downwards, subject to reserve
ratio requirements),

As this can cause leakage of value (from the lender and protocol's perspective) if `updateState()` is not called regularly, this has been evaluated to medium.

## Proof of Concept

The below PoC shows how two markets with the same characteristics can have different costs of debt to the borrower (and different returns for the lender). 

The first case, `test_borrowPoC` has a weekly call to `updateState`. The second case, `testBorrowPoCControl` has a quarterly update. 

Both these cases assume:  
- 5 million total market supply
- Interest at 1000 bps
- Delinquency fee at 1000 bps  
- the borrower borrows 80% immediately and does not repay it back
- 240 weeks timeframe  

Setup: 
In the `TestConstants.sol` file modify the `DefaultMaximumSupply` to:  
```
uint128 constant DefaultMaximumSupply = 5_000_000e18;
```

Place the below code in the `WildcatMarket.t.sol` file. 

The test can be run with `forge test --match-test test_borrowPoC -vvv`, this will run both the control and PoC tests.

```
  function test_borrowPoC() external {
    uint256 availableCollateral = market.borrowableAssets();
    assertEq(availableCollateral, 0, 'borrowable should be 0');

    vm.prank(alice);
    market.depositUpTo(5_000_000e18);
    assertEq(market.borrowableAssets(), 4000_000e18, 'borrowable should be 40k');
    vm.prank(borrower);
    market.borrow(4000_000e18);
    assertEq(ass
A borrower cannot redeploy a controller if their previous controller was removed 
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketControllerFactory.sol#L282-L301


# Vulnerability details

## Impact
A controller can be removed after it was initially deployed in cases where it was created with wrong parameters. However, in the case that a borrower's controller is removed they would be unable to redeploy another controller constraining them to use the initial controller with incorrect parameters.

## Proof of Concept
Due to the following check in the `deployController` a borrower is only ever able to deploy one controller from a `WildcatMarketControllerFactory.sol` contract:
```
 if (controller.codehash != bytes32(0)) {
    revert ControllerAlreadyDeployed();
 }
```

## Tools Used
Manual review

## Recommended Mitigation Steps
One solution to the issue could be including a `nonce` variable when creating a `WildcatMarketController.sol` contract only if a previous controller has been removed.


## Assessed type

Other
Permanent DoS on Market Creation Failure
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/libraries/LibStoredInitCode.sol#L76


# Vulnerability details

## Impact
Permanent DoS of creating a market if anything fails in the market constructor.

Within a create2 call, instead of failing when a revert occurs in the constructor being run, it just returns address(0).

If this occurs within a market that's being created the transaction will not revert in the controller and instead will register the address as a valid market, despite nothing having been created.

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

Once this is registered, it will block any future attempts at registration, so any more attempts at creating the market will fail despite it never having been created. 

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatArchController.sol#L193

## Proof of Concept
contract FactoryAssembly {
    event Deployed(address addr, uint salt);

    // 1. Get bytecode of contract to be deployed
    // NOTE: _owner and _foo are arguments of the TestContract's constructor
    function getBytecode(address _owner, uint _foo) public pure returns (bytes memory) {
        bytes memory bytecode = type(TestContract).creationCode;

        return abi.encodePacked(bytecode, abi.encode(_owner, _foo));
    }

    // 2. Compute the address of the contract to be deployed
    // NOTE: _salt is a random number used to create an address
    function getAddress(
        bytes memory bytecode,
        uint _salt
    ) public view returns (address) {
        bytes32 hash = keccak256(
            abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode))
        );

        // NOTE: cast last 20 bytes of hash to address
        return address(uint160(uint(hash)));
    }

    // 3. Deploy the contract
    // NOTE:
    // Check the event log Deployed which contains the address of the deployed TestContract.
    // The address in the log should equal the address computed from above.
    function deploy(bytes memory bytecode, uint _salt) public payable {
        address addr;

        /*
        NOTE: How to call create2

        create2(v, p, n, s)
        create new contract with code at memory p to p + n
        and send v wei
        and return the new address
        where new address = first 20 bytes of keccak256(0xff + address(this) + s + keccak256(mem[p…(p+n)))
              s = big-endian 256-bit value
        */
        assembly {
            addr := create2(
                callvalue(), // wei sent with current call
                // Actual code starts after skipping the first 32 bytes
                add(bytecode, 0x20),
                mload(bytecode), // Load the size of code contained in the first 32 bytes
                _salt // Salt from function arguments
            )

            if iszero(ex
Incompatibility with Rebase tokens
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Borrowers can choose whatever token they want to be the underlying token for a market. The problem comes when those tokens are Rebasing tokens such as Ampleforth. The balances of those tokens are changed (rebased) by a certain algorithm depending on the purpose of the Rebasing token and the market conditions. When lenders initially deposit Rebase tokens in the Wildcat protocol, the amount of that deposit will be stored within the protocol. Based on the internal rebasing of Wildcat the ```scaledAmound``` will change based on ```scaledFactor``` which increases in order to add APR to the lender deposits, but that doesn't account for rebasing in the underlying token. At a later stage this may result in wrong user balance accounting. In the case of Ampleforth if the price_exchange_rate of AMPL > 1 2019 USD the market is indicating there is more demand than supply. The Ampleforth protocol automatically and proportionally increases the quantity of tokens in user wallets. This will result in users being able to withdraw more tokens that they are supposed to and eventually the last user to withdraw won't be able to, as there won't be enough tokens left in the Wildcat protocol. Similar scenario is presented in the POC

## Proof of Concept
Add the following test to ```WildcatMarketController.t.sol```
```
  function test_rebaseToken() public {
    setUpContracts();

    /// INFO: Mint Rebase token to Alice and Bob
    vm.startPrank(rebaseTokenOwner);
    rebaseToken.transfer(alice, 10e9);
    rebaseToken.transfer(bob, 10e9);
    console.log("");
    console.log("Mint Rebase token to Alice and Bob");
    console.log("Alice rebase token balance: ", rebaseToken.balanceOf(alice));
    console.log("Alice rebase token balance: ", rebaseToken.balanceOf(bob));
    console.log("");
    vm.stopPrank();

    /// INFO: Approve Lenders
    vm.startPrank(borrower);
    address[] memory lenders = new address[](2);
    lenders[0] = alice;
    lenders[1] = bob;
    wildcatMarketController.authorizeLenders(lenders);
    address[] memory markets = new address[](1);
    markets[0] = marketAddress;
    wildcatMarketController.updateLenderAuthorization(alice, markets);
    wildcatMarketController.updateLenderAuthorization(bob, markets);
    vm.stopPrank();

    vm.startPrank(alice);
    rebaseToken.approve(marketAddress, 10e9);
    wildcatMarket.depositUpTo(10e9);
    vm.stopPrank();

    vm.startPrank(bob);
    rebaseToken.approve(marketAddress, 10e9);
    wildcatMarket.depositUpTo(10e9);
    vm.stopPrank();

    /// INFO: Rebase the token
    vm.startPrank(rebaseTokenOwner);
    rebaseToken.rebase(-49_000_000e8);
    vm.stopPrank();

    vm.startPrank(alice);
    wildcatMarket.queueWithdrawal(10e9);
    uint32 expiry = wildcatMarket.currentState().pendingWithdrawalExpiry;
    skip(3601);
    wildcatMarket.executeWithdrawal(alice
Sanctionned funds keep earning APR, and protocol earning fees on these funds
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
When a user is sanctioned, if he has a `scaledBalance` (not in the withdrawal queue), calling the `nukeFromOrbit` function will send sanctioned funds to an escrow contract, and these funds will keep earning APR.

This is because when a deposit is executed, the deposited amount is converted to a `scaledBalance`, by dividing amount by the `scaleFactor` (which can be seen as an exchange rate between the underlying token and the market shares). And only when withdrawn, the `scaleBalance` is multiplied back by the scaleFactor, which will have grown in regard to the APR and other fees owed by the borrower.

But the protocol itself will also earn fees on the sanctionned balance, as the `ProtocolFee` is calculated from `state.scaledTotalSupply` (L47):
```solidity
File: src\libraries\FeeMath.sol
40:   function applyProtocolFee(
41:     MarketState memory state,
42:     uint256 baseInterestRay,
43:     uint256 protocolFeeBips
44:   ) internal pure returns (uint256 protocolFee) {
45:     // Protocol fee is charged in addition to the interest paid to lenders. //note baseInterestRay: interest rate accrued to lenders
46:     uint256 protocolFeeRay = protocolFeeBips.bipMul(baseInterestRay);
47:     protocolFee = uint256(state.scaledTotalSupply).rayMul(
48:       uint256(state.scaleFactor).rayMul(protocolFeeRay)
49:     );
50:     state.accruedProtocolFees = (state.accruedProtocolFees + protocolFee).toUint128();
51:   }
```

And this variable isn't decreased when escrowing the sanctionned funds.
This means both borrower and protocol will still interact with the sanctionned address and funds, even if it has been blocked by the market.

## Proof of Concept

## Tools Used
Code review

## Recommended Mitigation Steps
When blocking an account, automatically queue, apply and execute sanctionned lender balance so that it is normalized back, removed from the total supply and thus stop earning fees and paying APR on it.
This will ensure that no sanctioned funds leaks after being blocked.

Another way to do this would be to save the `scaleFactor` in a specific variable (like `scaleFactorAtSanction`) and use this scale factor rather than the current one to convert the market shares to the underlying asset on withdraw.


## Assessed type

Other
Solmate `safetransfer` and `safeTransferFrom` do not check the code size of the token address, which may lead to loss of funds
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketWithdrawals.sol#L9
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L5
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L18


# Vulnerability details

## Impact
`WildcatMarketWithdrawals`, `WildcatMarketController`, `WildcatMarket` contracts use Solmate `safetransfer` and `safeTransferFrom` functions. However, these functions don't check the existence of code at the token address. This is a known issue while using `solmate's` libraries. Hence this may lead to miscalculation of funds and may lead to loss of funds, because if `safeTransfer()` and `safeTransferFrom()` are called on a token address that doesn't have a contract in it, it will always return success, bypassing the return value check. Due to this protocol will think that funds have been transferred successfully, and records will be accordingly calculated, but in reality, funds were never transferred. So this will lead to miscalculation and possibly loss of funds.

## Proof of Concept
Contract `WildcatMarketWithdrawals.sol`
[`asset.safeTransfer(escrow, normalizedAmountWithdrawn);`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketWithdrawals.sol#L171)
[`asset.safeTransfer(accountAddress, normalizedAmountWithdrawn);`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketWithdrawals.sol#L179)

Contract `WildcatMarketController.sol`
[`originationFeeAsset.safeTransferFrom(borrower, parameters.feeRecipient, originationFeeAmount);`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L346C7-L346C101)

Contract `WildcatMarket.sol`
[`asset.safeTransferFrom(msg.sender, address(this), amount);`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L60)
[`asset.safeTransfer(feeRecipient, withdrawableFees);`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L107)
[`asset.safeTransfer(msg.sender, amount);`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L129)
[`asset.safeTransferFrom(borrower, address(this), totalDebts - currentlyHeld);`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L154)
[`asset.safeTransfer(borrower, currentlyHeld - totalDebts);`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarket.sol#L157)

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use openz
Borrower can borrow assets with zero interest
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L487
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketConfig.sol#L156
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L67
https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L68


# Vulnerability details

The `WildcatMarketController::setAnnualInterestBips()` applies the `annualInterestBips` parameter (lending interest rate) set by a borrower without asserting it with the controller's `MinimumAnnualInterestBips`/`MaximumAnnualInterestBips` constraints, allowing a borrower to set a zero interest for their borrowing.

## Proof of Concept

A borrower can execute the [`WildcatMarketController::setAnnualInterestBips()`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L468-L488) to modify their market's `annualInterestBips` parameter (lending interest rate). Upon execution, the function will [invoke the `WildcatMarketConfig::setAnnualInterestBips()`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L487) on the target market to [adjust the `annualInterestBips`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/market/WildcatMarketConfig.sol#L156).

However, the `annualInterestBips` inputted by the borrower will be applied to the target market without asserting with the controller's [`MinimumAnnualInterestBips`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L67) and [`MaximumAnnualInterestBips`](https://github.com/code-423n4/2023-10-wildcat/blob/c5df665f0bc2ca5df6f06938d66494b11e7bdada/src/WildcatMarketController.sol#L68) constraints.

Hence, the borrower can set a zero interest for their borrowing. In other words, all lenders will receive 0% lending interest.

```solidity
    // FILE: https://github.com/code-423n4/2023-10-wildcat/blob/main/src/WildcatMarketController.sol
    function setAnnualInterestBips(
      address market,
      uint16 annualInterestBips
    ) external virtual onlyBorrower onlyControlledMarket(market) {
      // If borrower is reducing the interest rate, increase the reserve
      // ratio for the next two weeks.
      if (annualInterestBips < WildcatMarket(market).annualInterestBips()) {
        TemporaryReserveRatio storage tmp = temporaryExcessReserveRatio[market];

        if (tmp.expiry == 0) {
          tmp.reserveRatioBips = uint128(WildcatMarket(market).reserveRatioBips());

          // Require 90% liquidity coverage for the next 2 weeks
          WildcatMarket(market).setReserveRatioBips(9000);
When a batch of withdrawals expires, that batch is often underpaid their owed interest
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L361-L376
https://github.com/code-423n4/2023-10-wildcat/blob/main/src/market/WildcatMarketBase.sol#L466-L492


# Vulnerability details

## Impact

Until the exact timestamp that a payment is processed for a given withdrawal batch (`batch.scaledAmountBurned` incremented, `batch.normalizedAmountPaid` incremented), the underlying amount of that batch should continue to be earning interest. It's only when the payment is actually made that the underlying amount of the batch should stop earning interest - at this point users in that batch are actually able to withdraw funds. This is clear with the general flow of logic of how `_applyWithdrawalBatchPayment` is being called.

This flow is not being followed when a batch of withdrawals first expires however. Instead of getting paid interest up until the current block.timestamp, they are only paid interest up to the timestamp of the expiry. This means that (assuming theres enough assets currently in the market to pay off that batch of withdrawals) that users in that batch are unfairly losing out on (block.timestamp - expiry) duration of interest. As this duration increases, those lenders are losing increasing amounts of interest.

## Proof of Concept

The logic for handling a withdrawal batch when it first expires occurs in the `WildcatMarketBase:_getUpdatedState` function, which is defined as follows:
```solidity
function _getUpdatedState() internal returns (MarketState memory state) {
  state = _state;
  // Handle expired withdrawal batch
  if (state.hasPendingExpiredBatch()) {
    uint256 expiry = state.pendingWithdrawalExpiry;
    // Only accrue interest if time has passed since last update.
    // This will only be false if withdrawalBatchDuration is 0.
    if (expiry != state.lastInterestAccruedTimestamp) {
      (uint256 baseInterestRay, uint256 delinquencyFeeRay, uint256 protocolFee) = state
        .updateScaleFactorAndFees(
          protocolFeeBips,
          delinquencyFeeBips,
          delinquencyGracePeriod,
          expiry // @issue
        );
      emit ScaleFactorUpdated(state.scaleFactor, baseInterestRay, delinquencyFeeRay, protocolFee);
    }
    _processExpiredWithdrawalBatch(state);
  }
  ...
}
```

Effectively, the market parameters related to the accrued interest owed to lenders (most important of which is `state.scaleFactor`) is updated in the `state.updateScaleFactorAndFees(..)` call, which we can see updates the interest up to the `expiry` timestamp. Let's consider a scenario where `state.lastInterestAccruedTimestamp` = N, `expiry`=N, and `block.timestamp`=N+86_400 (one day). At this point in time, all lenders in this market (including lenders in this expiring withdrawal batch) are owed interest for N+86_400-N = 86_400, which is the duration from the current block.timestamp to the previous timestamp that interest was accrued.

Considering this, in the above function call, `(
Sanction-override should be automatically removed inside `releaseEscrow()` else lender can bypass checks if he is re-flagged as sanctioned by Chainanalysis oracle
Audit: 2023-10-wildcat-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
A lender who was once flagged as sanctioned by Chainanalysis oracle but his sanction overridden by the borrower, can continue to enjoy unintended override for future sanctions too.

## Proof of Concept
Imagine the following events:
1. Bob is flagged as sanctioned by Chainanalysis oracle.
2. Borrower decides to `overrideSanction()` since this is the first time this has happened to Bob and the borrower would want to give Bob this benefit. He makes it clear that in case this happens again in the future, he won't be helped.
3. Bob can now `releaseEscrow()` and also can have a `stunningReversal()`.
4. After a few days, Chainanalysis removes the flag against Bob's account. Bob is officially "clean" for now.
5. Few days later, Chainanalysis re-flags Bob as sanctioned.
6. Someone notices this and decides to `nukeFromOrbit()`. However they can't since `isSanctioned()` still returns false because `removeSanctionOverride()` was never explicitly called by the borrower. 
7. Bob can continue to perform actions as usual, including `executeWithdrawal()`.

## Tools Used
Manual inspection.

## Recommended Mitigation Steps
Inside `releaseEscrow()`, set `sanctionOverrides[borrower][account] = false` so that borrower does not have to do it explicitly.


## Assessed type

Other