xanoutas/solidity-security-findings huggingface dataset:
[H-2] Fee manager's funds gets stuck in contract
Audit: 2024-02-wise-lending-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## Description:
Fee manager's funds get stuck in smart contract after the first transfer.

## Impact:
Loss of funds meant to be paid as fees to the `feeManager`

## Tools Used
Manual Review

## Proof Of Concept:
Upon declaration the `feeManger` address is equal to zero, which passes the "if" check and sends the `feeManager` a `FEE_MANAGER_NFT`. subsequent transfer will fail the "if" check and revert "NotPermitted()" because the `feeManager` address will now be initialized with the `_feeManagerContract` which is a dynamic address passed as a parameter to the `forwardFeeManagerNFT.

```javascript
    function forwardFeeManagerNFT(
        address _feeManagerContract
    )
        external
        onlyMaster
    {
        if (feeManager > ZERO_ADDRESS) {
            revert NotPermitted();
        }

        feeManager = _feeManagerContract;

        _transfer(
            address(this),
            _feeManagerContract,
            FEE_MANAGER_NFT
        );
    }

```

## Recommended Mitigation Steps:
Re configure the logic and set `feeManager` address back to zero after every successfull `_transfer()`.
```diff
    function forwardFeeManagerNFT(
        address _feeManagerContract
    )
        external
        onlyMaster
    {
        if (feeManager > ZERO_ADDRESS) {
            revert NotPermitted();
        }

        feeManager = _feeManagerContract;

        _transfer(
            address(this),
            _feeManagerContract,
            FEE_MANAGER_NFT
        );

+       feeManager = address(0);

    }
```






## Assessed type

Token-Transfer
Exploitation of the receive Function to Steal Funds
Audit: 2024-02-wise-lending-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L97
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L275
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L636
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L49
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/SendValueHelper.sol#L12
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L681
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/FeeManager/FeeManager.sol#L730


# Vulnerability details

## **Vulnerability Details:**

The WiseLending contract incorporates a reentrancy guard through its [syncPool](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L97) modifier, specifically within the [_syncPoolBeforeCodeExecution](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L275) function. This guard is meant to prevent reentrancy during external calls, such as in the [withdrawExactAmountETH](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L636) function, which processes ETH withdrawals for users.

However, there is currently a way to reset this guard, allowing for potential reentrant attacks during external calls. The WiseLending contract includes a [receive](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L49) function designed to automatically redirect all ETH sent directly to it (apart from transactions from the WETH address) to a specified master address. 

To forward the ETH the [_sendValue](https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/TransferHub/SendValueHelper.sol#L12) function is used, here the **sendingProgress** variable (which is used for reentrancy checks) is set to true to denote the start of the transfer process and subsequently reset to false following the completion of the call.

```solidity
    function _sendValue(
        address _recipient,
        uint256 _amount
    )
        internal
    {
        if (address(this).balance < _amount) {
            revert AmountTooSmall();
        }

        sendingProgress = true;

        (
            bool success
            ,
        ) = payable(_recipient).call{
            value: _amount
        }("");

        sendingProgress = false;

        if (success == false) {
            re
Strict Equality Check Missing in _callOptionalReturn (Potential Issue with Success Evaluation)
Audit: 2024-02-wise-lending-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The _callOptionalReturn function lacks a strict equality check when decoding the return data, which could lead to unexpected behavior if the called contract returns non-boolean values that are interpreted as true.

Without a strict equality check, the success of the external call may be incorrectly evaluated, leading to incorrect decisions based on the returned data.


## Proof of Concept

contract MaliciousContract {
    // Malicious contract simulating a target contract
    function maliciousFunction() external pure returns (uint256) {
        // Simulate a contract function that returns a non-boolean value
        return 1; // Non-boolean value
    }
}

contract VulnerableContract {
    event ExternalCallSuccess(bool success);

    function _callOptionalReturn(address token, bytes memory data) internal returns (bool call) {
        (bool success, bytes memory returndata) = token.call(data);
        
        // Inadequate equality check that can misinterpret non-boolean return values as 'true'
        bool results = returndata.length == 0 || abi.decode(returndata, (bool));
        
        emit ExternalCallSuccess(success && results); // Emitting the result for demonstration
        
        // No strict equality check here, which may lead to unexpected behavior
        call = success && results && token.code.length > 0;
    }

    function testCallOptionalReturn(address token) external {
        // Calling the function with a contract address that returns a non-boolean value
        _callOptionalReturn(token, abi.encodeWithSignature("maliciousFunction()"));
    }
}

Steps:

    Deploy the MaliciousContract and VulnerableContract.
    Call the testCallOptionalReturn function of VulnerableContract, passing the address of MaliciousContract.
    Observe the emitted event ExternalCallSuccess.
    Despite maliciousFunction returning a non-boolean value (1), the event may erroneously indicate success due to the missing strict equality check.

## Tools Used
Manual review
## Recommended Mitigation Steps
Implement a strict equality check when decoding the return data to ensure correct evaluation of the success of external calls. For example, compare the decoded value against the expected boolean value using 'require'



## Assessed type

Other
The amount of ETH given to the user in `AaveHub.sol#borrowExactAmountETH` function is not accurate.
Audit: 2024-02-wise-lending-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WrapperHub/AaveHub.sol#L393-L427
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WrapperHub/AaveHelper.sol#L141-L163


# Vulnerability details

## Impact
When borrowing ETH, the profit generated from Aave is not lent to the user, so the profit is locked in the contract.

## Proof of Concept
`AaveHub.sol#borrowExactAmountETH` function is as follows.
```solidity
    function borrowExactAmountETH(
        uint256 _nftId,
        uint256 _borrowAmount
    )
        external
        nonReentrant
        returns (uint256)
    {
        _checkOwner(
            _nftId
        );

405:    uint256 borrowShares = _wrapBorrowExactAmount(
406:        _nftId,
407:        WETH_ADDRESS,
408:        address(this),
409:        _borrowAmount
410:    );
411:
412:    _unwrapETH(
413:        _borrowAmount
414:    );
415:
416:    _sendValue(
417:        msg.sender,
418:        _borrowAmount
419:    );

        emit IsBorrowAave(
            _nftId,
            block.timestamp
        );

        return borrowShares;
    }
```
Because `_wrapBorrowExactAmount` is called in `L405`, `borrowshares` becomes larger than `_borrowAmount`. 
However, in `L412-L416`, only `_borrowAmount` is given to the user, so the difference between `borrowShares` and `_borrowAmount` remains as an expectation.
Meanwhile, `AaveHelper.sol#_wrapBorrowExactAmount` function is as follows.
```solidity
    function _wrapBorrowExactAmount(
        uint256 _nftId,
        address _underlyingAsset,
        address _underlyingAssetRecipient,
        uint256 _borrowAmount
    )
        internal
        returns (uint256)
    {
        uint256 borrowShares = WISE_LENDING.borrowOnBehalfExactAmount(
            _nftId,
            aaveTokenAddress[_underlyingAsset],
            _borrowAmount
        );

156:    AAVE.withdraw(
156:        _underlyingAsset,
158:        _borrowAmount,
159:        _underlyingAssetRecipient
        );

        return borrowShares;
    }
```
Here, `_underlyingAssetRecipient` is `address(this)` as seen in `L408`, so the profit generated from Aave is locked in the contract.
## Tools Used

Manual Review

## Recommended Mitigation Steps
The profits generated from Aave must be transferred to the user.


## Assessed type

Token-Transfer
`OracleHelper.sol#_validateAnswer` function may not return the exact price of the token.
Audit: 2024-02-wise-lending-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The exact token price may not be returned.In `OracleHelper.sol#_validateAnswer` function, the price of the token was obtained from chainlink and uniswap twap, but ultimately only the price of chainlink was returned.
Since `uniswap twap `was not used in the resulting price calculation, the token price may be inaccurate.

## Proof of Concept
'OracleHepler.sol#_validateAnswer'function is as follows.
```solidity
    function _validateAnswer(
        address _tokenAddress
    )
        internal
        view
        returns (uint256)
    {
        UniTwapPoolInfo memory uniTwapPoolInfoStruct = uniTwapPoolInfo[
            _tokenAddress
        ];

        uint256 fetchTwapValue;

        if (uniTwapPoolInfoStruct.oracle > ZERO_ADDRESS) {
            fetchTwapValue = latestResolverTwap(
                _tokenAddress
            );
        }
149:
150:    uint256 answer = _getChainlinkAnswer(
151:        _tokenAddress
        );

        if (tokenAggregatorFromTokenAddress[_tokenAddress] > ZERO_AGGREGATOR) {
            _compareMinMax(
                tokenAggregatorFromTokenAddress[_tokenAddress],
                int192(uint192(answer))
            );
        }

        if (fetchTwapValue > 0) {

            uint256 relativeDifference = _getRelativeDifference(
                answer,
                fetchTwapValue
            );

            _compareDifference(
                relativeDifference
            );
        }

173:    return answer;
    }
```
As you can see in 'L149-L151' and 'L173', only the chainlink price is returned in the function.

## Tools Used

Manual Review

## Recommended Mitigation Steps
Modify the `OracleHelper.sol#_validateAnswer` function as follows.

```solidity
    function _validateAnswer(
        address _tokenAddress
    )
        internal
        view
        returns (uint256)
    {
        UniTwapPoolInfo memory uniTwapPoolInfoStruct = uniTwapPoolInfo[
            _tokenAddress
        ];

        uint256 fetchTwapValue;

        if (uniTwapPoolInfoStruct.oracle > ZERO_ADDRESS) {
            fetchTwapValue = latestResolverTwap(
                _tokenAddress
            );
        }

        uint256 answer = _getChainlinkAnswer(
            _tokenAddress
        );

        if (tokenAggregatorFromTokenAddress[_tokenAddress] > ZERO_AGGREGATOR) {
            _compareMinMax(
                tokenAggregatorFromTokenAddress[_tokenAddress],
                int192(uint192(answer))
            );
        }

        if (fetchTwapValue > 0) {

            uint256 relativeDifference = _getRelativeDifference(
                answer,
                fetchTwapValue
            );

            _compareDifference(
                relativeDifference
            );
        }

--        return answer;
++        return (answer + fetchTwapValue) / 2:
    }
```


## Assessed type

O
Reentrancy Vulnerability in paybackExactAmountETH Function which lead to Loss of Funds
Audit: 2024-02-wise-lending-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
The paybackExactAmountETH function allows users to pay back a specific amount of ETH borrowed against a given NFT. It wraps the ETH and performs the payback operation. However, it is vulnerable to reentrancy attacks because it performs state changes after sending ETH to an external contract, which can then recursively call back into the vulnerable contract before the state changes are completed.

An attacker could exploit this vulnerability to repeatedly call the paybackExactAmountETH function before the state changes are finalized, effectively draining the contract's ETH balance.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
// Attacker contract
contract Attacker {
    WiseLending lendingContract;

    constructor(address _lendingContract) {
        lendingContract = WiseLending(_lendingContract);
    }

    // Function to perform reentrant attack
    function attack(uint256 _nftId) external payable {
        // Send some initial ETH to trigger the paybackExactAmountETH function
        lendingContract.paybackExactAmountETH{value: msg.value}(_nftId);
        
        // Re-enter the paybackExactAmountETH function while it's still executing
        lendingContract.paybackExactAmountETH{value: msg.value}(_nftId);
    }
}

## Tools Used

Manual Review

## Recommended Mitigation Steps

Implement the checks-effects-interactions pattern to ensure that state changes are performed before interacting with external contracts. Use reentrancy guards to prevent recursive calls from within the same function. Consider using the withdrawal pattern to separate state changes from sending funds. Additionally, consider using the nonReentrant modifier from OpenZeppelin's ReentrancyGuard library to protect vulnerable functions from reentrancy attacks.


## Assessed type

Reentrancy
PowerFarm positions that borrowed aWETH to enter the farm can't be liquidated.
Audit: 2024-02-wise-lending-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarm/PendlePowerManager.sol#L129
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarm/PendlePowerManager.sol#L172
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarm/PendlePowerFarmLeverageLogic.sol#L560-L573


# Vulnerability details

## Impact
PowerFarm positions that borrowed aWETH can't be liquidated.

## Proof of Concept
PowerFarm positions can only be liquidated through the [`PendlePowerFarm.liquidatePartiallyFromToken() function`](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarm/PendlePowerFarm.sol#L93-L110). If a PowerFarm position is attempted to be liquidated directly in WiseLending by using the [`WiseLending.liquidatePartiallyFromTokens() function`](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseLending.sol#L1250-L1309), the execution will revert because the call to the [`_checkPositionLocked() function`](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/WiseCore.sol#L193-L209), this check validates that locked functions can't be liquidated directly on WiseLending!
```
function liquidatePartiallyFromTokens(
    uint256 _nftId,
    uint256 _nftIdLiquidator,
    ...
)
    ...
{
    ...

    //@audit-ok => Allows to liquidate positions that are not locked for a farm
    //@audit-info => If the nftId IS locked for a farm, reverts
    _checkPositionLocked(
        _nftId,
        msg.sender
    );
}

function _checkPositionLocked(
    uint256 _nftId,
    address _caller
)
    internal
    view
{
    //@audit-info => If the caller is a powerFarm, returns, skipped the check of positionLocked
    //@audit-info => If the caller is anybody else, check if the position is locked!
    if (_byPassCase(_caller) == true) {
        return;
    }

    //@audit-info => If the nftId IS NOT locked for a farm, returns
    if (positionLocked[_nftId] == false) {
        return;
    }

    //@audit-info => If the nftId IS locked for a farm, reverts
    revert PositionLocked();
}
```
**Knowing that PowerFarm positions can't be liquidated directly in WiseLending**, let's explore the reason why PowerFarm positions that borrowed aWETH can't be liquidated.

[When users enter a farm](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarm/PendlePowerManager.sol#L96-L140) they have two options to select the token that will be borrowed to repay the flashloan to the balancer vault.
1. The first option is to borrow aWETH from WiseLending via the AaveHub.
2. The second option is to borrow WETH directly from WiseLending.

Users control what type of token they want to borrow (either aWETH or WETH), [by setting as true or false the parameter `_isAave` when entering a farm.](https://github.com/code-423n4/2024-02-wise-lending/blob/main/contr
User can erase their position debt for free
Audit: 2024-02-wise-lending-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/FeeManager/FeeManager.sol#L816-L866
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/MainHelper.sol#L667-L727


# Vulnerability details


When pool token stops being used in the position, `_removePositionData` function is caled. It, however assumes that poolToken that is passed as a parameter, always exist in user token array, which is not always the case. In case of function `FeeManager.paybackBadDebtNoReward()`, which indirectly calls `_removePositionData`, insufficient validation doesn't check if repay token is in user array, which results in zeroing out information about user debt.

## Impact
Free elimination of user debt.

## Proof of Concept

First, let's see how `MainHelper._removePositionData()` works:

```javascript
    function _removePositionData(
        uint256 _nftId,
        address _poolToken,
        function(uint256) view returns (uint256) _getPositionTokenLength,
        function(uint256, uint256) view returns (address) _getPositionTokenByIndex,
        function(uint256, address) internal _deleteLastPositionData,
        bool isLending
    )
        private
    {
        uint256 length = _getPositionTokenLength(
            _nftId
        );

        if (length == 1) {
            _deleteLastPositionData(
                _nftId,
                _poolToken
            );

            return;
        }

        uint8 i;
        uint256 endPosition = length - 1;

        while (i < length) {

            if (i == endPosition) {
                _deleteLastPositionData(
                    _nftId,
                    _poolToken
                );

                break;
            }

            if (_getPositionTokenByIndex(_nftId, i) != _poolToken) {
                unchecked {
                    ++i;
                }
                continue;
            }

            address poolToken = _getPositionTokenByIndex(
                _nftId,
                endPosition
            );

            isLending == true
                ? positionLendTokenData[_nftId][i] = poolToken
                : positionBorrowTokenData[_nftId][i] = poolToken;

            _deleteLastPositionData(
                _nftId,
                _poolToken
            );

            break;
        }
    }
```

So, `_poolToken` sent in parameter is not checked if:
1. The position consists of only one token. Then the token is removed, no matter if it's `_poolToken` or not.
2. No token was found during the position token iteration. In which case, last token is removed, no matter if it's `_poolToken` or not.

This function is called in `MainHelper._corePayback()`, which in turn is called in `FeeManager.paybackBadDebtNoReward() => WiseLending.corePaybackFeeManager() => WiseLending._handlePayback()`. Important factor is that `paybackBadDebtNoReward()` doesn't check if position utilizes
[H-1] Repetitive _safeTransfer call leading to loss of funds in protocol
Audit: 2024-02-wise-lending-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/PowerFarms/PendlePowerFarm/PendlePowerFarm.sol#L157-L179
https://github.com/code-423n4/2024-02-wise-lending/blob/79186b243d8553e66358c05497e5ccfd9488b5e2/contracts/WiseLending.sol#L902-L933


# Vulnerability details

## Description: 
withdrawAmount is been withdrawn twice in `PendlePowerFarm.sol::_manuallyWithdrawShares` internal function. It makes a `_safeTransfer` with a value to the msg.sender in `withdrawExactShares` and a `_safeTransfer` is being called in the same code block right after.

## Impact: 
This would Lead to a loss of funds to the protocol since users can now withdraw from their deposited shares twice in a single transaction.

## Tools Used
Manual Review

## Proof Of Concept:

```js
     function _manuallyWithdrawShares(
        uint256 _nftId,
        uint256 _withdrawShares
    )
        internal
    {
        uint256 withdrawAmount = WISE_LENDING.cashoutAmount(
            PENDLE_CHILD,
            _withdrawShares
        );

        // @audit-
        withdrawAmount = WISE_LENDING.withdrawExactShares(
            _nftId,
            PENDLE_CHILD,
            _withdrawShares
        );


        // @audit-
        _safeTransfer(
            PENDLE_CHILD,
            msg.sender,
            withdrawAmount
        );
    }

```

## Recommended Mitigation Steps:
Change the code logic to allow for only a single withdrawAmount in the `_manuallyWithdrawShares` internal function

```diff
    function _manuallyWithdrawShares(
        uint256 _nftId,
        uint256 _withdrawShares
    )
        internal
    {
        uint256 withdrawAmount = WISE_LENDING.cashoutAmount(
            PENDLE_CHILD,
            _withdrawShares
        );

        withdrawAmount = WISE_LENDING.withdrawExactShares(
            _nftId,
            PENDLE_CHILD,
            _withdrawShares
        );

-        _safeTransfer(
-            PENDLE_CHILD,
-            msg.sender,
-            withdrawAmount
-        );
    }

```









## Assessed type

ETH-Transfer