xanoutas/solidity-security-findings huggingface dataset:
Borrowers can sidestep paying the needed APR for some supported assets
Audit: 2024-08-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-wildcat/blob/fe746cc0fbedc4447a981a50e6ba4c95f98b9fe1/README.md#L262


# Vulnerability details


### Proof of Concept
First, per the readMe, we should assume rebasing tokens are in scope, see https://github.com/code-423n4/2024-08-wildcat/blob/fe746cc0fbedc4447a981a50e6ba4c95f98b9fe1/README.md#L262

```markdown
| [Balance changes outside of transfers](https://github.com/d-xo/weird-erc20?tab=readme-ov-file#balance-modifications-outside-of-transfers-rebasingairdrops) | In scope    |
```

Where as this integration is welcome as the WIldMarketTokens themselves are somewhat rebasing in nature, this then means that users could pay lesser APR, which is because if they _(rebasing tokens)_ are used as underlying assets for markets, when the borrower/market contracts hold these tokens while they are lent, the newly accrued tokens may either be credited to the borrower, or inside the market itself, which in our case would count as the borrower adding liquidity. And result in the borrower _needing_ to pay a lower Annual Percentage Rate (APR) than initially set.

This also means that a tech savvy user can even do this in a morer sophisticated way, assume the rebasing stETH is used as the asset, the stETH token normally rebases at 12pm, so the malicious user can just frontrun the rebase if it'd massively affect their APR, alternatively they can even front/back run the rebase if the new APR would be lower.

### Impact
Users would pay lesser APR for some to-be supported borrowable assets.
### Recommended Mitigation Steps
Since disallowing rebasing assets is not an option, either track the balance change for these assets or heavily document this behaviour to users.


## Assessed type

Context
`AccessControlHooks::onQueueWithdrawal` Missing Access Control Check
Audit: 2024-08-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-wildcat/blob/fe746cc0fbedc4447a981a50e6ba4c95f98b9fe1/src/access/AccessControlHooks.sol#L812


# Vulnerability details

## Proof of Concept
`AccessControlHooks::onQueueWithdrawal` is missing access verification. To exploit this call the function and pass the address of any valid `lender address`. For the `hooksData` the first 20 bytes have to be a valid `provider` address. However, it is possible to call the provider address with any calldata as seen [here](https://github.com/code-423n4/2024-08-wildcat/blob/fe746cc0fbedc4447a981a50e6ba4c95f98b9fe1/src/access/AccessControlHooks.sol#L553)
[Here is the code](https://github.com/code-423n4/2024-08-wildcat/blob/fe746cc0fbedc4447a981a50e6ba4c95f98b9fe1/src/access/AccessControlHooks.sol#L812-L825)
```solidity
  function onQueueWithdrawal(
    address lender,
    uint32 /* expiry */,
    uint /* scaledAmount */,
    MarketState calldata /* state */,
    bytes calldata hooksData
  ) external override {
    LenderStatus memory status = _lenderStatus[lender];
    if (
      !isKnownLenderOnMarket[lender][msg.sender] && !_tryValidateAccess(status, lender, hooksData)
    ) {
      revert NotApprovedLender();
    }
  }
```

Please see the similar code in `FixedTermLoanHooks::onQueueWithdrawal` which does include access verification [here](https://github.com/code-423n4/2024-08-wildcat/blob/fe746cc0fbedc4447a981a50e6ba4c95f98b9fe1/src/access/FixedTermLoanHooks.sol#L855-L856)
```solidity
  function onQueueWithdrawal(
    address lender,
    uint32 /* expiry */,
    uint /* scaledAmount */,
    MarketState calldata /* state */,
    bytes calldata hooksData
  ) external override {
    HookedMarket memory market = _hookedMarkets[msg.sender];
    if (!market.isHooked) revert NotHookedMarket();
    if (market.fixedTermEndTime > block.timestamp) {
      revert WithdrawBeforeTermEnd();
    }
    LenderStatus memory status = _lenderStatus[lender];
    if (market.withdrawalRequiresAccess) {
      if (
        !isKnownLenderOnMarket[lender][msg.sender] && !_tryValidateAccess(status, lender, hooksData)
      ) {
        revert NotApprovedLender();
      }
    }
  }
```

## Impact
Anyone can call valid any valid provider address from the `AccessControlHooks` with crafted calldata. The impact is partly dependent on what visible functions the provider address has.


## Recommended Mitigation Steps
Make the following changes to `AccessControlHooks::onQueueWithdrawal`
```diff
  function onQueueWithdrawal(
    address lender,
    uint32 /* expiry */,
    uint /* scaledAmount */,
    MarketState calldata /* state */,
    bytes calldata hooksData
  ) external override {
+  @> HookedMarket memory market = _hookedMarkets[msg.sender];
+  @> if (!market.isHooked) revert NotHookedMarket();
    LenderStatus memory status = _lenderStatus[lender];
    if (
      !isKnownLenderOnMarket[lender][msg.sender] && !_tryValidateAccess(status, lender, hooksData)
    ) {
      revert NotApprovedL
`FixedTermLoanHooks` allow Borrower to update Annual Interest before end of the "Fixed Term Period"
Audit: 2024-08-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-wildcat/blob/main/src/access/FixedTermLoanHooks.sol#L857-L859
https://github.com/code-423n4/2024-08-wildcat/blob/main/src/access/FixedTermLoanHooks.sol#L960-L978


# Vulnerability details

## Summary
While the documentation states that in case of 'fixed term' market the APR cannot be changed until the term ends, nothing prevents this in `FixedTermLoanHooks`.

## Vulnerability details
In Wildcat markets, lenders know in advance how much `APR` the borrower will pay them. In order to allow lenders to exit the market swiftly, the market must always have at least a `reserve ratio` of the lender funds ready to be withdrawn.
If the borrower decide to [reduce the `APR`](https://docs.wildcat.finance/using-wildcat/day-to-day-usage/borrowers#reducing-apr), in order to allow lenders to 'ragequit', a new `reserve ratio` is calculated based on the variation of the APR as described in the link above.
Finally, is a market implement a fixed term (date until when withdrawals are not possible), it shouldn't be able to reduce the APR, as this would allow the borrower to 'rug' the lenders by reducing the APR to 0% while they couldn't do anything against that.

The issue here is that while lenders are (as expected) prevented to withdraw before end of term:
https://github.com/code-423n4/2024-08-wildcat/blob/main/src/access/FixedTermLoanHooks.sol#L857-L859
```solidity
File: src/access/FixedTermLoanHooks.sol
848:   function onQueueWithdrawal(
849:     address lender,
850:     uint32 /* expiry */,
851:     uint /* scaledAmount */,
852:     MarketState calldata /* state */,
853:     bytes calldata hooksData
854:   ) external override {
855:     HookedMarket memory market = _hookedMarkets[msg.sender];
856:     if (!market.isHooked) revert NotHookedMarket();
857:     if (market.fixedTermEndTime > block.timestamp) {
858:       revert WithdrawBeforeTermEnd();
859:     }
```

this is not the case for the borrower setting the annual interest:
https://github.com/code-423n4/2024-08-wildcat/blob/main/src/access/FixedTermLoanHooks.sol#L960-L978
```solidity
File: src/access/FixedTermLoanHooks.sol
960:   function onSetAnnualInterestAndReserveRatioBips(
961:     uint16 annualInterestBips,
962:     uint16 reserveRatioBips,
963:     MarketState calldata intermediateState,
964:     bytes calldata hooksData
965:   )
966:     public
967:     virtual
968:     override
969:     returns (uint16 updatedAnnualInterestBips, uint16 updatedReserveRatioBips)
970:   {
971:     return
972:       super.onSetAnnualInterestAndReserveRatioBips(
973:         annualInterestBips,
974:         reserveRatioBips,
975:         intermediateState,
976:         hooksData
977:       );
978:   }
979: 
```

## Impact
Borrower can rug the lenders by reducing the APR while they cannot quit the market

## Proof of Concept

Add this test to `test/access/FixedTermLoanHooks.t.sol`

```
  function testAudit_SetAnnualInterestBeforeTermEnd() external {
    DeployMarketInpu
Inconsistent Interest Accrual After Withdrawal Batch Expiry
Audit: 2024-08-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-wildcat/blob/fe746cc0fbedc4447a981a50e6ba4c95f98b9fe1/src/market/WildcatMarketBase.sol#L476


# Vulnerability details

## Summary

The Wildcat protocol manages lending markets, tracking their state using the MarketState struct (). A key aspecat of this state is the scaleFactor, which reflects accrued interest over time. When lenders deposit or withdraw funds, their balances are scaled according to the scaleFactor to accurately represent their share of the market's assets (). The protocol also handles withdrawals in batches, allowing for delayed processing and potential partial payments if liquidity is insufficient ().

A critical issue exists in handling interest accrual when a withdrawal batch expires. The protocol aims to apply accrued interest up to the batch expiry time before processing the batch. However, the logic for updating the scaleFactor and processing expired batches can lead to inaccurate interest calculations and, consequently, incorrect balances for lenders.

## Details

The protocol uses a pendingWithdrawalExpiry timestamp in the MarketState to track the expiry of a withdrawal batch (). When this timestamp is reached, the protocol attempts to process the batch, burning market tokens and paying out underlying assets to lenders who requested withdrawals.

To ensure accuracy, the protocol intends to calculate and apply any accrued interest up to the pendingWithdrawalExpiry timestamp before processing the expired batch (). This is crucial to reflect the interest earned by lenders up to the point of their withdrawal.

The current implementation in the _calculateCurrentState function, responsible for calculating the updated market state, first handles the expired withdrawal batch by updating the scaleFactor and applying any pending interest (). However, this update doesn't consider that the batch has expired, potentially leading to interest accrual beyond the intended expiry time.

After processing the expired batch, the _calculateCurrentState function proceeds to update the scaleFactor again, this time using the current block timestamp (). While seemingly correct, this second update, combined with the earlier update that didn't consider batch expiry, results in an inconsistent scaleFactor and, consequently, inaccurate balance calculations for lenders.

## Impact

This inconsistency in interest accrual can lead to:

Lender Disadvantage: If the scaleFactor is updated incorrectly, lenders might receive a lower payout than what they are entitled to based on the accrued interest up to the withdrawal batch expiry.

Borrower Advantage: Conversely, borrowers might benefit from this discrepancy by effectively paying less interest than they should have, given the actual time elapsed.

Accounting Errors: The inconsistent scaleFactor can propagate to other parts of the protocol that rely on the MarketState for calculations, leading to cascading errors in accounting and reporting.

## Scenario
`addRoleProvider` will revert if `providerAddress` is an EOA, while documentation state provider can be an EOA
Audit: 2024-08-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-wildcat/blob/main/src/access/AccessControlHooks.sol#L220-L220
https://github.com/code-423n4/2024-08-wildcat/blob/main/src/access/FixedTermLoanHooks.sol#L257-L257


# Vulnerability details

## Summary
`addRoleProvider` will revert if `providerAddress` is an EOA, while documentation state provider can be an EOA.

## Vulnerability details
Role providers can grant credentials to lenders by calling the `grantRole` function.

The project [documentation describe](https://docs.wildcat.finance/technical-overview/security-developer-dives/hooks/access-control-hooks#role-providers) the role provider interface, and says:
> Role providers can "push" credentials to the hooks contract by calling `grantRole`:
> \[...\]
> **Role providers do not have to implement any of these functions - a role provider can be an EOA.**

But if we try to set an EOA as a `providerAddress` when calling `addRoleProvider`, the call will revert as `isPullProvider` is not implemented on `providerAddress` (which is an EOA).


```solidity
File: src/access/AccessControlHooks.sol
217:   function addRoleProvider(address providerAddress, uint32 timeToLive) external onlyBorrower {
218:     RoleProvider provider = _roleProviders[providerAddress];
219:     if (provider.isNull()) {
220:❌    bool isPullProvider = IRoleProvider(providerAddress).isPullProvider();
221:       // Role providers that are not pull providers have `pullProviderIndex` set to
222:       // `NotPullProviderIndex` (max uint24) to indicate they do not refresh credentials.
223:       provider = encodeRoleProvider(
224:         timeToLive,
225:         providerAddress,
226:         isPullProvider ? uint24(_pullProviders.length) : NotPullProviderIndex
227:       );
```

Also, `_tryValidateCredential` throw error if `validateCredential` return data < 32 bites, which means that if last provider for a user was an EOA, he will revert before being able to go through the pullProviders list
Might want to cover this case by checking if address has code too


## Impact
EOA cannot be role providers, breaking a core functionality of the protocol.

## Tools Used
Manual review

## Recommended Mitigation Steps
Perform a low level call to cover the case where the providerAddress has no code.
```diff
diff --git a/src/access/AccessControlHooks.sol b/src/access/AccessControlHooks.sol
index 1124e5b..0fbc0d9 100644
--- a/src/access/AccessControlHooks.sol
+++ b/src/access/AccessControlHooks.sol
@@ -217,7 +217,13 @@ contract AccessControlHooks is MarketConstraintHooks {
   function addRoleProvider(address providerAddress, uint32 timeToLive) external onlyBorrower {
     RoleProvider provider = _roleProviders[providerAddress];
     if (provider.isNull()) {
-      bool isPullProvider = IRoleProvider(providerAddress).isPullProvider();
+      bool isPullProvider;
+      (bool success, bytes memory data) = providerAddress.call(
+                               abi.encodeWithSelector(IRoleProvider.isPullPr
`repayDeliquentDebt` is not effective, as the market will become delinquent again on next block
Audit: 2024-08-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-wildcat/blob/main/src/market/WildcatMarket.sol#L172-L172
https://github.com/code-423n4/2024-08-wildcat/blob/main/src/market/WildcatMarketBase.sol#L541-L542


# Vulnerability details

## Summary
`repayDelinquentDebt` allow a borrower to repay the exact amount necessary to cover the delinquency, i.e make the `totalAssets` in the market equal to the `liquidityRequired`. 
But as fees accrues every seconds (and new block), the market will be delinquent again on next block.

## Vulnerability details

The `repayDelinquentDebt` works that way:
1. The market state is updated in `_getUpdatedState()`. During this call, as the market is delinquent, delinquency is applied (either by applying fees or increasing the delinquent timer) 
2. Then the exact amount to make the market not delinquent is computed as `delinquentDebt`
3. [The amount is repaid](https://github.com/code-423n4/2024-08-wildcat/blob/main/src/market/WildcatMarket.sol#L172-L172) (a transfer of the `delinquentDebt` is made from the borrower to the market)
4. Finally, the `state.isDelinquent` variable is [set to false](https://github.com/code-423n4/2024-08-wildcat/blob/main/src/market/WildcatMarketBase.sol#L541-L542) in `_writeState` as `state.liquidityRequired() == totalAssets()` now.

```solidity
File: src/market/WildcatMarket.sol
186:   function repayDelinquentDebt() external nonReentrant sphereXGuardExternal {
187:     MarketState memory state = _getUpdatedState();
188:     uint256 delinquentDebt = state.liquidityRequired().satSub(totalAssets());
189:     _repay(state, delinquentDebt, 0x04);
190:     _writeState(state);
191:   }
```

```solidity
File: src/market/WildcatMarketBase.sol
540:   function _writeState(MarketState memory state) internal {
541:     bool isDelinquent = state.liquidityRequired() > totalAssets();
542:     state.isDelinquent = isDelinquent; 
543:	// .... 
544: // ....	
```

The issue here, is that on the next block that will occur, any stateful interaction (starts with `_getUpdatedState()` and ends with `_writeState`) with the market [will increase the fees and reserve ratio](https://github.com/code-423n4/2024-08-wildcat/blob/main/src/libraries/MarketState.sol#L87-L98) in `_getUpdatedState()`, increasing `state.liquidityRequired()`, making the market delinquent again when `_writeState` is called at the end.

## Impact
This will cause the market to still accrue delinquency fees, causing a loss for the borrower.
The `repayDelinquentDebt` purpose is defeated by the fact this only remove the delinquency of the market for one block, which is not effective to help a borrower solve the issue effectively, who will see the delinquency continue to affect its market, causing additional cost that could have been avoided by adding a buffer of repayment.

## Proof of Concept
Add this test to `test/market/WildcatMarket.t.sol`:

```solidity
  function testAudit_repayDelinquentDebtNotEffective() external {
    _depositBorrowWithdraw(ali
Invalid `hooks templates` could be used for market deployments in the `HooksFactory.deployMarket` function
Audit: 2024-08-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-wildcat/blob/main/src/HooksFactory.sol#L506-L515
https://github.com/code-423n4/2024-08-wildcat/blob/main/src/HooksFactory.sol#L530-L544


# Vulnerability details

## Vulnerability Details

The `HooksFactory.deployMarketAndHooks` function is used to deploy a hooks instance and a new market using the deployed hooks instance. In this function when the `hooks instance is deployed` the following check is performed on the `HooksTemplate` to check whether it `exists`.

```solidity
    HooksTemplate memory templateDetails = _templateDetails[hooksTemplate]; //@audit-info - get the HooksTemplate struct
    if (!templateDetails.exists) {
      revert HooksTemplateNotFound(); 
    }
    hooksInstance = _deployHooksInstance(hooksTemplate, hooksTemplateArgs);
```

The above check ensures that the provided `hooksTemplate` is valid and does exist to be used in the market deployment.

The `HooksFactory.deployMarket` function is also used for the market deployment. But the issue is that the `template details` of the `hooksTemplate` used for the market deployment in this function is not checked for `templateDetails.exists` flag.

## Impact

As a result of the above vulnerability, even when the `templateDetails.exists == false` the market deployment will proceed without any revert, thus enabling invalid `hooks templates` to be used for the market deployments. Hence this vulnerability breaks the intended behavior of the protocol.

## Proof of Concept

```solidity
    HooksTemplate memory templateDetails = _templateDetails[hooksTemplate];
    market = _deployMarket(
      parameters,
      hooksData,
      hooksTemplate,
      templateDetails,
      salt,
      originationFeeAsset,
      originationFeeAmount
    );
```

https://github.com/code-423n4/2024-08-wildcat/blob/main/src/HooksFactory.sol#L506-L515

```solidity
    HooksTemplate memory templateDetails = _templateDetails[hooksTemplate];
    if (!templateDetails.exists) {
      revert HooksTemplateNotFound();
    }
    hooksInstance = _deployHooksInstance(hooksTemplate, hooksTemplateArgs);
    parameters.hooks = parameters.hooks.setHooksAddress(hooksInstance);
    market = _deployMarket(
      parameters,
      hooksData,
      hooksTemplate,
      templateDetails,
      salt,
      originationFeeAsset,
      originationFeeAmount
    );
```

https://github.com/code-423n4/2024-08-wildcat/blob/main/src/HooksFactory.sol#L530-L544

## Recommended Mitigation Steps

Hence it is recommended to update the `HooksFactory.deployMarket` function to check for the `!templateDetails.exists` flag before the `_deployMarket` is called, and revert the transaction if the `templateDetails.exists == false`. This will ensure that invalid `hooks templates` are not used for the market deployments.



## Assessed type

Other
Borrowers could still deploy market while the template is diabled
Audit: 2024-08-wildcat-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-wildcat/blob/main/src/HooksFactory.sol#L506


# Vulnerability details

## Impact
Borrowers could still deploy market while the template is diabled, since HooksFactory.sol:deployMarket() doesn't check if HooksTemplate is existed and enbled.

## Proof of Concept

ArchControllerOwner could disable hookstemplate with disableHooksTemplate(address hooksTemplate). While disabled, deployMarket to this template should get reverted. 

   	function deployMarket(
		DeployMarketInputs calldata parameters,
		bytes calldata hooksData,
		bytes32 salt,
		address originationFeeAsset,
		uint256 originationFeeAmount
	  ) external override nonReentrant returns (address market) {
		if (!IWildcatArchController(_archController).isRegisteredBorrower(msg.sender)) {
		  revert NotApprovedBorrower();
		}
		address hooksInstance = parameters.hooks.hooksAddress();
		address hooksTemplate = getHooksTemplateForInstance[hooksInstance];
		if (hooksTemplate == address(0)) {
		  revert HooksInstanceNotFound();
		}
		HooksTemplate memory templateDetails = _templateDetails[hooksTemplate];
		market = _deployMarket(
		  parameters,
		  hooksData,
		  hooksTemplate,
		  templateDetails,
		  salt,
		  originationFeeAsset,
		  originationFeeAmount
		);
	  }

## Tools Used
manually reviewed

## Recommended Mitigation Steps
add the following checks before _deployMarket():

		if (!templateDetails.exists) {
		  revert HooksTemplateNotFound();
		}
		if (!templateDetails.enabled) {
		  revert HooksTemplateNotAvailable();
		}







## Assessed type

Access Control