xanoutas/solidity-security-findings huggingface dataset:
FeeManager.addPoolTokenAddress() function unable to be called due to wrong expected caller
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
According to the dev note
```
/**
     * @dev Function adding new pool token to pool token list.
     * Called during pool creation and only callable by wiseLending
     * contract.
     */
```
FeeManager.addPoolTokenAddress() is expected to be called during the pool creation and only by the WiseLending contract.

While, indeed the function is called during pool creation, this call is actually an external call from the PoolManager contract in [line260 of PoolManager contract](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/PoolManager.sol#L260-L262)

However, the modifier `onlyWiseLending()` checks `msg.sender == address(WISE_LENDING)`, if false the call reverts.

Since the call is from the PoolManager, `FeeManager.addPoolTokenAddress()` would always revert.


## Proof of Concept
addPoolTokenAddress() function expected to be called by WiseLending contract only
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/FeeManager/FeeManager.sol#L390C5-L406C6
```
function addPoolTokenAddress(
        address _poolToken
    )
        external
        onlyWiseLending
    {
        poolTokenAddresses.push(
            _poolToken
        );

        poolTokenAdded[_poolToken] = true;

        emit PoolTokenAdded(
            _poolToken,
            block.timestamp
        );
    }
```


Call to FeeManager.addPoolTokenAddress() from PoolManager contract
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/PoolManager.sol#L260-L262
```
FEE_MANAGER.addPoolTokenAddress(
            _params.poolToken
        );
```


onlyWiseLending() modifier checks if caller is WiseLending contract
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/FeeManager/DeclarationsFeeManager.sol#L181-L183
```
 modifier onlyWiseLending() {
        _onlyWiseLending();
        _;
    }
```
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/FeeManager/DeclarationsFeeManager.sol#L213-L222C6
```
function _onlyWiseLending()
        private
        view
    {
        if (msg.sender == address(WISE_LENDING)) {
            return;
        }

        revert NotWiseLending();
    }
```


## Tools Used
Manual

## Recommended Mitigation Steps
the function should be called by the PoolManager contract rather than WiseLending.


## Assessed type

Other
The protocol allows borrowing small positions that can create bad debt
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurity.sol#L306-L330
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseCore.sol#L260-L263
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurity.sol#L1100-L1102
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurity.sol#L1104-L1106
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurity.sol#L237-L270


# Vulnerability details

## Impact

The `Wise Lending` protocol allows users to borrow small positions. Even if the protocol has a minimum deposit (collateral) amount check to mitigate the small borrowing position from creating bad debt, this protection can be bypassed.

With a small borrowing position, there is no incentive for a liquidator to liquidate the position, as the liquidation profit may not cover the liquidation cost (gas). As a result, small liquidable positions will not be liquidated, leaving bad debt to the protocol.

## Proof of Concept

The protocol allows users to borrow small positions since no minimum borrowing amount is checked in the [`WiseSecurity::checksBorrow()`](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurity.sol#L306-L330).

```solidity
// FILE: https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurity.sol
function checksBorrow(
    uint256 _nftId,
    address _caller,
    address _poolToken
)
    external
    view
    returns (bool specialCase) //@audit -- No minimum borrowing amount check
{
    _checkPoolCondition(
        _poolToken
    );

    checkTokenAllowed(
        _poolToken
    );

    if (WISE_LENDING.verifiedIsolationPool(_caller) == true) {
        return true;
    }

    if (WISE_LENDING.positionLocked(_nftId) == true) {
        return true;
    }
}
```
- `No minimum borrowing amount check`: https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseSecurity/WiseSecurity.sol#L306-L330

Even if the protocol has a [minimum deposit (collateral) amount check](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseCore.sol#L260-L263) in the `WiseCore::_checkDeposit()` to mitigate the small borrowing position from creating bad debt, this protection can be easily bypassed.

The `WiseCore::_checkMinDepositValue()` is a core function that checks a minimum deposit (collateral) amount. By default, [this deposit amount check would be overridden (disabled)](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/Wi
Off-by-one bug prevents the `_compareMinMax()` from detecting Chainlink aggregators' circuit-breaking events
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseOracleHub/OracleHelper.sol#L97


# Vulnerability details

## Impact

The `Wise Lending` protocol implements the `OracleHelper::_compareMinMax()` to detect the circuit-breaking events of Chainlink aggregators when an asset price goes outside of pre-determined min/max values. For instance, in case of a significant price drop (e.g., LUNA crash), the asset price reported by the Chainlink price feed will continue to be at the pre-determined minAnswer instead of the actual price. 

The `_compareMinMax()`'s objective is to prevent such a crash event that would allow a user to borrow other assets with the wrongly reported asset price. For more, refer to the case of [Venus Protocol and Blizz Finance in the crash of LUNA](https://rekt.news/venus-blizz-rekt/).

However, the current implementation of the `_compareMinMax()` got an off-by-one bug that would prevent the function from detecting the mentioned Chainlink aggregators' circuit-breaking events. In other words, the function will not revert the transaction if the flash crash event occurs as expected.

## Proof of Concept

In the flash crash event, the Chainlink price feed will continue to return the `_answer` at the pre-determined `minAnswer` instead of the actual price.  In other words, the possible minimum value of the `_answer` would be `minAnswer`.

Since the `_compareMinMax()` does not include the case of [`_answer` == `minAnswer`](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseOracleHub/OracleHelper.sol#L97) (also, `_answer` == `maxAnswer`), the function could not detect whether or not the crash event happens.

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

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

- https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseOracleHub/OracleHelper.sol#L97

## Tools Used

Manual Review

## Recommended Mitigation Steps

Add the cases `_answer` == `minAnswer` and `_answer` == `maxAnswer` like the snippet below.

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

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


## Assessed type

Oracle
Receive functions does not forward funds to master address
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#L21-L29
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarmController/PendlePowerFarmControllerBase.sol#L125-L133


# Vulnerability details

The receive functions in PendlePowerManager:#21 and PendlePowerFarmControllerBase:#125 are not forwarding the received funds to the master address, simply emitting an ETHReceived event. The transfer to master address is implemented in other contracts (e.g WiseLending:#49).

## Impact
It locks ETH inside the affected contracts instead of sending to master address.

## Proof of Concept
1. User sends ETH by mistake to the mentioned contracts
2. There's no forwarding done so funds likely lost

## Tools Used
Manual review

## Recommended Mitigation Steps
Implement the transfer so ETH funds don't get locked inside the contracts. 
Proposed fix:
```
    receive()
        external
        payable
    {
        _sendValue(
            master,
            msg.value
        );
        
        emit ETHReceived(
            msg.value,
            msg.sender
        );
    }







## Assessed type

Other
Unchecked return value bug on `TransferHelper::_safeTransferFrom()`
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/CallOptionalReturn.sol#L21
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/CallOptionalReturn.sol#L26-L29
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/CallOptionalReturn.sol#L35-L37
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/TransferHelper.sol#L42


# Vulnerability details

## Impact

Currently, the `Wise Lending` protocol supports several ERC-20 tokens and will also support more tokens in the future:
> *ERC20 in scope: WETH, WBTC, LINK, DAI, WstETH, sDAI, USDC, USDT, WISE and may others in the future. (Also corresponding Aave tokens if existing)*

On some ERC-20 tokens, the [`transferFrom()` will return `false` on failure instead of reverting a transaction](https://github.com/d-xo/weird-erc20?tab=readme-ov-file#no-revert-on-failure). I noticed that the `TransferHelper::_safeTransferFrom()`, which is used throughout the protocol, is vulnerable to detecting the token transfer failure if the `transferFrom()` returns `false` due to the unchecked return value bug.

The following lists the functions directly invoking the vulnerable `_safeTransferFrom()`:
1. [WiseCore::_coreLiquidation()](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseCore.sol#L674-L679)
2. [WiseLending::depositExactAmount()](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L476-L481)
3. [WiseLending::solelyDeposit()](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L622-L627)
4. [WiseLending::paybackExactAmount()](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1190-L1195)
5. [WiseLending::paybackExactShares()](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L1230-L1235)
6. [FeeManager::paybackBadDebtForToken()](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/FeeManager/FeeManager.sol#L788-L793)
7. [FeeManager::paybackBadDebtNoReward()](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/FeeManager/FeeManager.sol#L860-L865)
8. [PendlePowerFarm::_manuallyPaybackShares()](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/PowerFarms/PendlePowerFarm/PendlePowerFarm.sol#L136-L141)
9. [PendlePowerManager::enterFarm()](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/con
Missing zero address check could have PendlePowerFarmToken contract re-initialized
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol#L682-L687
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarmController/PendlePowerFarmTokenFactory.sol#L102-L108
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarmController/PendlePowerFarmController.sol#L211-L229


# Vulnerability details

## Impact
When PendlePowerFarmToken contract is deployed in the PendlePowerFarmTokenFactory contract in `PendlePowerFarmTokenFactory.deploy()`, it creates a new PendlePowerFarmToken contract and calls the `initialize()` function.
```solidity
PendlePowerFarmToken(pendlePowerFarmTokenAddress).initialize(
            _underlyingPendleMarket,
            PENDLE_POWER_FARM_CONTROLLER,
            _tokenName,
            _symbolName,
            _maxCardinality
        );
```

 The initialize() function in PendlePowerFarmToken  initializes the `PENDLE_MARKET` , `PENDLE_CONTROLLER`, `MAX_CARDINALITY` , `_name`, `_symbol`, `PENDLE_POWER_FARM_CONTROLLER` , `UNDERLYING_PENDLE_MARKET`,and other state variables.
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol#L682-L732


However, due to a missing a zero address check, it may be possible to re-initialize the state variables of PendlePowerFarmToken contract.

This is possible if the master address of the PNEDLE_POWER_FARM_CONTROLLER contract mistakenly calls `PendlePowerFarmController.addPendleMarket()` with a zero address as the `_pendleMarket` parameter 
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarmController/PendlePowerFarmController.sol#L211-L229
```solidity
function addPendleMarket(
        address _pendleMarket,
        string memory _tokenName,
        string memory _symbolName,
        uint16 _maxCardinality
    )
        external
        onlyMaster
    {
        if (pendleChildAddress[_pendleMarket] > ZERO_ADDRESS) {
            revert AlreadySet();
        }

        address pendleChild = PENDLE_POWER_FARM_TOKEN_FACTORY.deploy(
            _pendleMarket,
            _tokenName,
            _symbolName,
            _maxCardinality
        );
```

This action would inadvently create an opportunity for a malicious user monitoring the network and observed the PendlePowerFarmToken contract deployment to take advantage of the error to manipulate `totalLpAssetsToDistribute` and other state variables.


## Tools Used
Manual

## Recommended Mitigation Steps
apply a zero address check to ensure the contract is not initialized with a zero address at deployment.


## Assessed type

Other
Borrowers can DoS liquidations by repaying as little as 1 share.
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurityHelper.sol#L895-L899


# Vulnerability details

## Impact
Liquidations can be DoSed which increments the risk of bad debt being generated on position.

## Proof of Concept
When a liquidator is liquidating a position in WiseLending, the liquidator needs to specify the amount of shares to be repaid. The liquidation logic checks if the positions are indeed liquidable, if so, it validates if the number of shares to be liquidated exceeds the total amount of shares that can be liquidated by using the [`WiseSecurityHelper.checkMaxShares() function`](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurityHelper.sol#L876-L900). **If the amount of shares the liquidator intends to liquidate exceeds the maximum amount of liquidable shares, the execution is reverted**
  - When the position has generated bad debt, the liquidation can liquidate all the user's borrowShares on the pool been liquidated
  - When the position has not generated bad debt, the maximum amount of liquidable shares is 50% of the existing user's borrowShares on the pool being liquidated

The problem with this approach is that borrowers can frontrun the liquidation and repay as little as 1 share of the existing debt on the same pool that the liquidator decided to liquidate the debt from. This will cause when the liquidation is executed, the total borrow shares of the user on the pool being liquidated to be lower. If the liquidator was trying to liquidate the maximum possible amount of shares, now, the maxShares that can be liquidated will be slightly less than the amount of shares that the liquidator specified, which will cause the tx to revert.

```
function checkMaxShares(
    uint256 _nftId,
    address _tokenToPayback,
    uint256 _borrowETHTotal,
    uint256 _unweightedCollateralETH,
    uint256 _shareAmountToPay
)
    public
    view
{
    //@audit-ok => total borrowShares a position owns for a _tokenToPayback pool
    uint256 totalSharesUser = WISE_LENDING.getPositionBorrowShares(
        _nftId,
        _tokenToPayback
    );

    //@audit-info => If baddebt, maxShares that can be liquidated are the totalSharesUser
    //@audit-info => If not baddebt, maxShares can be 50% of the total borrowShares
    uint256 maxShares = checkBadDebtThreshold(_borrowETHTotal, _unweightedCollateralETH)
        ? totalSharesUser
        : totalSharesUser * MAX_LIQUIDATION_50 / PRECISION_FACTOR_E18;

    if (_shareAmountToPay <= maxShares) {
        return;
    }

    //@audit-issue => reverts if the amount of shares to be repaid exceeds maxShares!
    revert TooManyShares();
}
```

For example, if there is a position in a liquidable state that has 100 borrowShares on the PoolA, and, a liquidator decides to liquidate the maximum possible amount of shares from this position it will send a tx to liquidate 50 shares from that position on the PoolA. The p
A position can have a token registered in lending tokens list with 0 amount deposited
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseSecurity/WiseSecurity.sol#L1100-L1102


# Vulnerability details

## Impact

When a deposit of tokens is executed, a check inside `WiseSecurity` is executed in order to ensure a minimum amount.

```
    function _checkMinDepositValue(
        address _token,
        uint256 _amount
    )
        private
        view
        returns (bool)
    {
        if (minDepositEthValue == ONE_WEI) {
            return true;
        }

        if (_getTokensInEth(_token, _amount) < minDepositEthValue) {
            revert DepositAmountTooSmall();
        }

        return true;
    }
```

We can notice here that when the `minDepositEthValue` is 1 wei, the check returns true regardless of the amount deposited. Hence, if the amount deposited is 0 the function call will not revert.
In the case of normal deposits, the amount of shares to mint is also checked to be above minimum, but for solely deposits, if a user selects 0 amount of tokens to deposit, he will register the token in his lending list without having any amount of this token deposited.

This error breaks the invariant of the lending token list that only have active tokens with amount greater than 0.

This vulnerability could be used to make intentionally spend way more gas that required to liquidate his position.

## Tools Used

Manual review

## Recommended Mitigation Steps

Check if the amount deposited is greater or equal to 1 when `minDepositEthValue` is set to 1 wei.

```
    function _checkMinDepositValue(
        address _token,
        uint256 _amount
    )
        private
        view
        returns (bool)
    {
        if (minDepositEthValue == ONE_WEI && _amount == 0) {
            revert DepositAmountTooSmall();
        }

        if (_getTokensInEth(_token, _amount) < minDepositEthValue) {
            revert DepositAmountTooSmall();
        }

        return true;
    }
```



## Assessed type

Error
PositionNFT can be sold to another user while it has a borrow position
Audit: 2024-02-wise-lending-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

As positions inside WiseLending are held as NFTs, a user could deposit collateral, mint the corresponding PositionNFT and then proceed to sell it on secondary markets (this happened in the past for Uniswap LPs NFT). While this in itself is not an issue for the protocol, there are no checks to ensure the position has no borrowed funds before being transferred. The seller could observe the transfer event for his PositionNFT, frontrun the transaction by borrowing funds, leading to profits by him having the price of the position (say 90% of the collateral cost) and the borrowed funds.

## Impact
Malicious can leave open positions and get rid of their PositionNFT for profit, the seller of such a position having no incentive to pay back debt.

## Proof of Concept
1. Create a PositionNFT, deposit collateral
2. Put the PositionNFT for sale, at a slight discount 
3. Listen for the transfer event, frontrun by borrowing max for the collateral
4. The buyer is left with a PositionNFT that has borrowed funds

## Tools Used
Manual review

## Recommended Mitigation Steps
Override the _beforeTokenTransfer from OZ:ERC721 and prevent PositionNFTs with open borrow positions to be transferred






## Assessed type

MEV