xanoutas/solidity-security-findings huggingface dataset:
Liquidating chaining can be achieved by liquidating token collateral with the highest `collateralFactor`
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseLending.sol#L1250


# Vulnerability details

## Impact
The liquidation mechanism is intended as follows:
- If a user has more borrowed value than weighted collateral, but it does not surpass the 89% of the unweighted collateral, he can be liquidated up to 50% of his borrowed shares.
- When the borrowed amount surpass the 89% of the unweighted collateral, then it is considered bad debt and the position can be fully liquidated

This feature is programmed [here](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurityHelper.sol#L876-L900)

However, since the liquidator can select which collateral he will receive, he can intentionally liquidate the highest `collateralFactor` tokens in order to make the overall position's `collateralFactor` to go down and being able to keep liquidating the other tokens. Since the intended maximum amount to liquidate when the position has no bad debt is 50%, if a user can intentionally create a sequence of liquidation that leads to a greater percentage it can be considered a high impact vulnerability.

## Written proof of concept
Imagine the following situation:

The protocol supports these 4 tokens, A, B, C and D with these `collateralFactors`

| Token         | Collateral factor |
| :---          |  ---: 			|
| A         	| 0.85 				|
| B       		| 0.65 				|
| C        		| 0.50 				|
| D        		| 0.70 				|

The initial prices for these tokens are as follows:

| Token         | Price (in ETH)|
| :---          |  ---: 		|
| A         	| 1 			|
| B        		| 0.2 			|
| C        		| 0.5 			|
| D 			| 1			 	|

Alice deposits these 3 amounts of token A, B and C as collateral

| Token         | Value | Amount 		| Unweighted value 	| Weighted value|
| :---          |  ---: |  ---:			| ---:				| ---:			|
| A        		| 1		| 10 			| 10				| 8.5			|
| B        		| 0.2	| 50 			| 10				| 6.5			|
| C        		| 0.5	| 20 			| 10				| 5				|
|  				| 		| Total values 	| 30	 			| 20	 		|

Alice can borrow up to 20 * 0.95 = 19 worth of ETH, for the sake of simplicity, since token D is valued 1 ETH, she can borrow up to 19 of token D. However, she decides to borrow 18.9 to have a tiny healthy zone.
Unfortunately for Alice, the price of token C drops to 0.25. And the situation continues as follows:

| Token         | Value | Amount 		| Unweighted value 	| Weighted value|
| :---          |  ---: |  ---:			| ---:				| ---:			|
| A        		| 1		| 10 			| 10				| 8.5			|
| B        		| 0.2	| 50 			| 10				| 6.5			|
| C        		| 0.25	| 20 			| 5					| 2.5			|
|  				| 		| Total values 	| 25	 			| 17.5	 		|

In this stage, Alice can be liquidated up to 50% because her borrowed amount (18.9 ETH) is greater than her weighted value (17.5 ETH). Just 50% is liquidable because the 89% of her weighted value is greater than her borrowed amount.
Let's now demonstrate that if the liquidator receives the token with
A token quickly losing value will block liquidations
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/OracleHelper.sol#L131-L174


# Vulnerability details

## Impact
The protocol uses the `WiseOracleHub` contract to compute the value of each token by calling the `getTokensInETH` function. Inside there, the [high overview](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/OracleHelper.sol#L131-L174) is that it takes the Chainlink price feed for the token in terms of ETH and a TWAP computation of the price of the asset. Having these 2 results, the [difference](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/OracleHelper.sol#L216-L233) is calculated and if it is [greater](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/OracleHelper.sol#L235-L244) than [2.5%](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/Declarations.sol#L112) the call is reverted.

The TWAP mechanism is used in order to mitigate the price manipulation of AMMs by computing the price over a certain amount of time. This feature is also risky when a quick increase or decrease of value happens because since it returns the average price over time, it will need some time to reach the new price. That means that for a certain interval of time, the value returned by the TWAP will be outdated. As happened recently with the [Uniswap token](https://www.coinlive.com/news/compound-incurred-bad-debts-of-us-660-000-due-to-uni-s-instant), it increased really quickly in price due to enabling the fees into the protocol. That lead to protocols like Compound to borrow this asset to a really low price compared to the actual one due to the TWAP result.

In the particular case of WiseLending, since it checks for the difference of the result of Chainlink price feed and TWAP, it disable this scenario of a huge increase in a short amount of time because when someone would try to borrow from this asset, the function would revert due to the price difference. However, for a huge decrease of value like a depeg it can lead to a really bad situation for the protocol and for users.

1. During a huge decrease of price of an asset, the liquidation for positions that borrowed this asset will revert due to this discrepancy of price. That could lead to bad debt for the protocol because nobody would be able to liquidate the position.

2. During a huge decrease of price of an asset, withdrawals of this same asset would be disabled for positions that have some amount borrowed of any asset. That is because this function will check for the `healthState` by calculating the value of this asset and reverting. If the user wants to withdraw any of his collateral assets, he will be forced to repay all his borrowed amounts before being able to withdraw any asset. This can be really bad for the user because he could get this asset back and swap it to stablecoins for example before losing t
Fees will be lost when operations are executed often
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/MainHelper.sol#L500-L577


# Vulnerability details

## Impact
Protocol fee loss.

## Proof of Concept
Function `MainHelper._updatePseudoTotalAmounts()` to be used with every pool modification operation (borrow, lend, etc). Currently, fees for fee manager are dependent on calculations based on time since last update. In case of Ethereum it's 12 secs, in case of Arbitrum < 1 sec. The problem is that trigger to redistribute fees is reaching `1e18 * (60 * 60 * 24 * 365)`, while the calculations go throug multiple truncations, until finally shares are calculated. Additional factor is that it will behave differently for different tokens, e.g. for USDC it's almost guaranteed to never work, because of 6 decimals, while for ETH it may work sometimes, and it's working on bare amount, not considering decimal precision. If the function is executed often, no fee shares will be distributed to fee manager - those will be considered lost.

```javascript
    function _updatePseudoTotalAmounts(
        address _poolToken
    )
        private
    {
        uint256 currentTime = block.timestamp;

        // @audit dependent on last update
        uint256 bareIncrease = borrowPoolData[_poolToken].borrowRate
            * (currentTime - timestampsPoolData[_poolToken].timeStamp)
            * borrowPoolData[_poolToken].pseudoTotalBorrowAmount
            + bufferIncrease[_poolToken];

        if (bareIncrease < PRECISION_FACTOR_YEAR) { // @info PRECISION_FACTOR_YEAR is 1e18 * (60 * 60 * 24 * 365) 1year in secs
            bufferIncrease[_poolToken] = bareIncrease;

            _setTimeStamp(
                _poolToken,
                currentTime
            );

            return;
        }

        delete bufferIncrease[_poolToken];

        uint256 amountInterest = bareIncrease
            / PRECISION_FACTOR_YEAR; // @audit PRECISION_FACTOR_YEAR is 1e18 * (60 * 60 * 24 * 365) 1year in secs. If called often, it will be small, like 1 or 2

        uint256 feeAmount = amountInterest
            * globalPoolData[_poolToken].poolFee //@audit poolFee is set to 2e17 in code, so combining the 2, it will be 0 for the most part
            / PRECISION_FACTOR_E18;

        _increasePseudoTotalBorrowAmount(
            _poolToken,
            amountInterest
        );

        _increasePseudoTotalPool(
            _poolToken,
            amountInterest
        );

        if (feeAmount == 0) {
            _setTimeStamp(
                _poolToken,
                currentTime
            );
            return;
        }

        // @audit if feeAmount is smaller than difference between shares and total pool, it will be 0, so fees will be 0
        uint256 feeShares = feeAmount
            * lendingPoolData[_poolToken].totalDepositShares
            / (lendingPoolData[_poolToken].pseudoTotalPool - feeAmount);

        if (feeShares == 0) {
            _setT
Ether sent to `PendlePowerManager` will be lost forever
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarm/PendlePowerManager.sol#L17-L29


# Vulnerability details

## Impact
In all other contracts in the protocol, we can see that the receive function looks like this:
```
    /**
     * @dev Standard receive functions forwarding
     * directly send ETH to the master address.
     */
    receive()
        external
        payable
    {
        if (msg.sender == WETH_ADDRESS) {
            return;
        }

        _sendValue(
            master,
            msg.value
        );
    }
```
This function gathers the ether sent to the contract without data and sends it to the master address.
However, if we look at the `PendlePowerManager`, the comment says exactly the same but it only emits an event.
```
    /**
     * @dev Standard receive functions forwarding
     * directly send ETH to the master address.
     */
    receive()
        external
        payable
    {
        emit ETHReceived(
            msg.value,
            msg.sender
        );
    }
```
In this case the money is kept in the contract but there is no function to transfer it out.
I guess that the contract needed the receive function open for the `WETH` contract interaction and some other things but it could be implemented just as other contract do. Check the sender to be one of the whitelisted contracts, if not, transfer the funds out to the master address or to the sender.

## Tools Used
Manual review

## Recommended Mitigation Steps
```
    /**
     * @dev Standard receive functions forwarding
     * directly send ETH to the master address.
     */
    receive()
        external
        payable
    {
        if (msg.sender == WETH_ADDRESS) {
            return;
        }

        _sendValue(
            master,
            msg.value
        );
    }
```


## Assessed type

ETH-Transfer
2.5% price discrepancy combined with chainlink price feeds deviation threshold can often lead to DoS of pools
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/OracleHelper.sol#L131-L174


# Vulnerability details

## Impact
The protocol uses the `WiseOracleHub` contract to compute the value of each token by calling the `getTokensInETH` function. Inside there, the [high overview](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/OracleHelper.sol#L131-L174) is that it takes the Chainlink price feed for the token in terms of ETH and a TWAP computation of the price of the asset. Having these 2 results, the [difference](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/OracleHelper.sol#L216-L233) is calculated and if it is [greater](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/OracleHelper.sol#L235-L244) than [2.5%](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/Declarations.sol#L112) the call is reverted.

Chainlink price feeds have a parameter called deviation threshold, depending on the asset and its volatility, it is greater or smaller. Normal values are 0.5% - 2%. This parameter determines the threshold in which the price feed will update his current price or not. For example, if a price feed has a deviation threshold of 1%, it means that if the current price is 100 ETH, if the price does not go higher than 101 or lower than 99, the price will not be updated. From the assets in scope, there is the [BTC/ETH](https://data.chain.link/feeds/ethereum/mainnet/btc-eth) price feed which has a 2% deviation threshold.

This feature can collude with the 2.5% price discrepancy and bring an asset pool into a DoS during a whole hearthbeat. That is because the price feed will not update if the price has not change in a range of [-2%, +2%]. For price feeds with a 1% deviation threshold, a 2.5% discrepancy price fits good, however, for higher deviation thresholds can often lead to a denial of service of the token pool.

## Tools Used
Manual review

## Recommended Mitigation Steps
From my point of view, the discrepancy price should be variable depending on the asset. For low volatility assets, it is good if it is low, however, for more volatile assets it can often lead to DoS. For this reason, I would recommend to adjust this parameter on pool creation time depending on the asset. For the price feeds with a deviation threshold of 2% I would recommend a minimum of 5% price discrepancy. 


## Assessed type

Oracle
Validity checks in `OracleHelper#_validateAnswer` are not sufficient and the function can return a stale price
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/OracleHelper.sol#L155
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/OracleHelper.sol#L168
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseOracleHub/Declarations.sol#L109


# Vulnerability details

The price retrieved from the Chainlink price feed in OracleHub undergoes two validity checks . 

### First One :

The price is compared against `minAnswer` and `maxAnswer` values extracted from the token aggregator 

```jsx
    function _compareMinMax(IAggregator _tokenAggregator, int192 _answer) internal view {
        int192 maxAnswer = _tokenAggregator.maxAnswer();
        int192 minAnswer = _tokenAggregator.minAnswer();

        if (_answer > maxAnswer || _answer < minAnswer) {
            revert OracleIsDead();
        }
    }
```

and if the price is under `minAnswer` or over `maxAnswer` the transactions revert preventing the operation to be executed with a stale price .
However, we can read from [chainlink docs](https://docs.chain.link/data-feeds#check-the-latest-answer-against-reasonable-limits) that those values are no longer used in most data Feeds, and the team on [chainlink discorde](https://discord.com/channels/592041321326182401/605768708266131456/1129035651749257226) suggest refraining from using them any longer, so they are likely not to exposed in the next aggregators versions .

### Second One :

Getting the difference between the Chainlink price and a Uniswap TWAP and if its above a certain value the transaction will revert protecting the protcol .

```jsx
    function _compareDifference(uint256 _relativeDifference) internal view {
        if (_relativeDifference > ALLOWED_DIFFERENCE) {
            revert OraclesDeviate();
        }
    }
```

But when the Uniswap pool oracle is not added to this token, this makes the fetchTwapValue to be 0 and the difference check will be skipped .

Also, a high volatility market event of an asset is not considered in `TWAP_PERIOD` as it cannot be changed after deployment of the contract. In fact 30 minutes window can result in getting a stale TWAP in sudden movement of the asset market .

## **Proof of Concept**

consider an asset with the following particularities : 

1. its chainlink aggregator does not contain a `maxAnswer` so **the first verification is bypassed .**
2. Its Uniswap TWAP Oracle is not set for any particular reason so fetchTwapValue will be 0 and **the second verification is bypassed** .

### OR

1. the market is in high activity, so the 30 minutes TWAP Window will be too high compared to the asset volatiliy thus the retrieved TWAP is stale. Despite its staleness, it may align  enough closely with the Chainlink price **making the second verification pass even if both of them are outdated.**

## Impact

Users can exploit this by borrowing against an inflated price asset .

## Tool Used

Manual review

## **Recommended
Not enforcing full liquidation accrues bad debt in the system
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1250-L1309
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurityHelper.sol#L876-L900


# Vulnerability details

## Impact
Not enforcing full liquidation leads to bad debt in the system and gives on incentive to liquidate small positions.

## Proof of Concept
When liquidations are made, in `WiseCore._coreLiquidation()`, the percentage of reward for the liquidator is calculated and checked if it doesn't go over 1e18 (100%):
```javascript
        uint256 collateralPercentage = WISE_SECURITY.calculateWishPercentage(
            _data.nftId,
            _data.tokenToRecieve,
            WISE_ORACLE.getTokensInETH(
                _data.tokenToPayback,
                _data.paybackAmount
            ),
            _data.maxFeeETH,
            _data.baseRewardLiquidation
        );

        _validateParameter(
            collateralPercentage,
            PRECISION_FACTOR_E18
        );
```

later, the amount to receive is calculated based on this percentage:

```javascript
        receiveAmount = _calculateReceiveAmount(
            _data.nftId,
            _data.nftIdLiquidator,
            _data.tokenToRecieve,
            collateralPercentage
        );
```

```javascript
    function _calculateReceiveAmount(
        uint256 _nftId,
        uint256 _nftIdLiquidator,
        address _receiveTokens,
        uint256 _removePercentage
    )
        private
        returns (uint256 receiveAmount)
    {
        if (pureCollateralAmount[_nftId][_receiveTokens] > 0) {
            receiveAmount = _withdrawPureCollateralLiquidation(
                _nftId,
                _receiveTokens,
                _removePercentage
            );
        }

        uint256 potentialPureExtraCashout;
        uint256 userShares = userLendingData[_nftId][_receiveTokens].shares;
        uint256 pureCollateral = pureCollateralAmount[_nftId][_receiveTokens];

        if (pureCollateral > 0 && userShares > 0) {
            potentialPureExtraCashout = _calculatePotentialPureExtraCashout(
                userShares,
                _receiveTokens,
                _removePercentage
            );
        }
// [...]
            return receiveAmount + potentialPureExtraCashout;
        }

        if (userShares == 0) {
            return receiveAmount;
        }

        if (userLendingData[_nftId][_receiveTokens].unCollateralized == true) {
            return receiveAmount;
        }

        // @audit it should return what is there to be withdrawn. if we add receiveAmount, it may go over what's available, reverting the transaction
        return _withdrawOrAllocateSharesLiquidation(
            _nftId,
            _nftIdLiquidator,
            _receiveTokens,
            _removePercentage
        ) + receiveAmount;
```

The problem is that there is check if the po
User can DoS liquidations for extended period of time
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurityHelper.sol#L876-L900
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1250-L1309


# Vulnerability details

The `WiseSecurity.sol` has a glitch in the `checksLiquidation` and `checkMaxShares` functions, where a user can prevent liquidation by front-running the liquidator. This is achieved by topping up their position with a minimal amount (e.g., 1 share) just before the liquidation transaction is processed. This method exploits the liquidation logic that determines the maximum shares a liquidator can acquire based on the current debt and collateral level. If the liquidator attempts to liquidate the maximum possible value to drive the price to healthly state or up to 50% for liquidations that don't have enough bad debt, the user can make a small top-up to their position, causing the liquidator's transaction to revert due to the `TooManyShares` error. Moreover, on Layer 2 networks where transaction costs are relatively low, users can repeatedly exploit this vulnerability with minimal expense, further driving their positions into bad debt.

## Impact

Unhealthly positions can delay being liquidated by making minimal top-ups to their accounts.

## Proof of Concept

1. A user's account is on the verge of liquidation.
2. A liquidator attempts to liquidate this account by calculating the maximum shares they can acquire, as defined in the `checkMaxShares` function.
```javascript
    function checkMaxShares(
        uint256 _nftId,
        address _tokenToPayback,
        uint256 _borrowETHTotal,
        uint256 _unweightedCollateralETH,
        uint256 _shareAmountToPay
    )
        public
        view
    {
        uint256 totalSharesUser = WISE_LENDING.getPositionBorrowShares(
            _nftId,
            _tokenToPayback
        );

        uint256 maxShares = checkBadDebtThreshold(_borrowETHTotal, _unweightedCollateralETH)
            ? totalSharesUser
            : totalSharesUser * MAX_LIQUIDATION_50 / PRECISION_FACTOR_E18;

        if (_shareAmountToPay <= maxShares) {
            return;
        }

        revert TooManyShares();
    }
```

3. Before the liquidator's transaction is mined, the user sends a transaction to slightly increase their collateral (by as little as 1 share or min amount that will revert).
4. The liquidator's transaction then checks if the share amount to pay exceeds the maximum allowed shares, which now includes the user's tiny top-up.
5. Because of this top-up, the liquidation transaction exceeds the maximum allowed shares and is reverted by the `TooManyShares` error, preventing the liquidation.

The user can pay back any amount of shares, as low as 1, because there is no check for min value:
```javascript
    function manuallyPaybackShares(
        uint256 _keyId,
        uint256 _paybackShares
setAaveTokensBulk misses one token while setting Token addresses
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WrapperHub/AaveHub.sol#L71


# Vulnerability details

## Impact
The incorrect logic leaves one token unattended while setting token 
## Proof of Concept
The loop below only picks i < _underlyingAssets.length and this would leave an unattended token on [line 71](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WrapperHub/AaveHub.sol#L71)

## Tools Used
Manual Analysis
## Recommended Mitigation Steps
Use i <= _underlyingAssets.length for this logic instead of i < _underlyingAssets.length 


## Assessed type

Context