xanoutas/solidity-security-findings huggingface dataset:
GiantMevAndFeesPool.withdrawETH distributes too many rewards
Audit: 2022-11-stakehouse-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
GiantMevAndFeesPool.withdrawETH will reduce the value of idleETH before burning GaintLP.
GaintLP.burn will call GiantMevAndFeesPool.beforeTokenTransfer and call updateAccumulatedETHPerLP to calculate accumulatedETHPerLPShare.
Decreasing the value of idleETH before burning GaintLP will cause totalRewardsReceived to increase and accumulatedETHPerLPShare to increase, i.e. treating the ETH deposited by the user as rewards, resulting in distributing too many rewards to the user.
## Proof of Concept
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantPoolBase.sol#L52-L64
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantMevAndFeesPool.sol#L176-L178
## Tools Used
None
## Recommended Mitigation Steps
Change to
```diff
    function withdrawETH(uint256 _amount) external nonReentrant {
        require(_amount >= MIN_STAKING_AMOUNT, "Invalid amount");
        require(lpTokenETH.balanceOf(msg.sender) >= _amount, "Invalid balance");
        require(idleETH >= _amount, "Come back later or withdraw less ETH");

-       idleETH -= _amount;

        lpTokenETH.burn(msg.sender, _amount);
+      idleETH -= _amount;

        (bool success,) = msg.sender.call{value: _amount}("");
        require(success, "Failed to transfer ETH");

        emit LPBurnedForETH(msg.sender, _amount);
    }
```
When a user transfers the GaintLP or LPToken, the subsequent rewards may not be claimed.
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#L170-L173


# Vulnerability details

## Impact
When a user transfers the GaintLP or LPToken, the beforeTokenTransfer and afterTokenTransfer functions of GiantMevAndFeesPool or StakingFundsVault are called, which help the from and to claim the reward and update the claimed variables.
However, in the afterTokenTransfer function, only claimed[to] is updated, not claimed[from].
Consider the following scenario, user A has 2e24 GaintLPs, at this point accumulatedETHPerLPShare == 100, user A transfers 1e24 GaintLPs to user B. 
In the beforeTokenTransfer function, user A receives 200 rewards, claimed[A][ GaintLP] = 200, user B has no reward, in the afterTokenTransfer function, claimed[B][GaintLP] = 100.
After that accumulatedETHPerLPShare is increased to 200, user A tries to claim the reward, due = 200 - 200 == 0, user B claims the reward, due = 200 - 100 = 100.
In fact, User A should also receive the 100 reward.  
## Proof of Concept
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantMevAndFeesPool.sol#L170-L173
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/StakingFundsVault.sol#L343-L351
## Tools Used
None
## Recommended Mitigation Steps
Consider updating the claimed[from] in the afterTokenTransfer
```diff
    function afterTokenTransfer(address _from, address _to, uint256) external override {
        if (LiquidStakingManager(payable(liquidStakingNetworkManager)).syndicate() != address(0)) {
            LPToken token = LPToken(msg.sender);
            require(KnotAssociatedWithLPToken[token].length > 0, "Invalid token");

            // claim is calculated on full balance not amount being transferred so that double claims are not possible
            claimed[_to][address(token)] = (token.balanceOf(_to) * accumulatedETHPerLPShare) / PRECISION;
+          claimed[_from][address(token)] = (token.balanceOf(_from) * accumulatedETHPerLPShare) / PRECISION;
        }
    }
...
    function afterTokenTransfer(address _from, address _to, uint256) external {
        require(msg.sender == address(lpTokenETH), "Caller is not giant LP");
        _setClaimedToMax(_to);
+      _setClaimedToMax(_from);
    }
...

```
Upgraded Q -> M from #400 [1670236164031]
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

Judge has assessed an item in Issue #400 as M risk. The relevant finding follows:

 L04 - LiquidStakingManager.dao can rug node operators with executeAsSmartWallet()
daoCommissionPercentage is used to calculate the portion of node operator network rewards that are sent to dao, when a node runner is calling claimRewardsAsNodeRunner().

There is no timelock to updateDAORevenueCommission(), meaning a dao can call it with _commissionPercentage = MODULO to set daoCommissionPercentage to 100%, effectively stealing all the network rewards from node runners.

Impact
Centralization risk

Tools Used
Manual Analysis

Mitigation
Consider either adding a timelock to updateDAORevenueCommission(), or add an upper boundary to it, so that daoCommissionPercentage is always reasonable.
Upgraded Q -> M from #400 [1670236078897]
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

Judge has assessed an item in Issue #400 as M risk. The relevant finding follows:

 L03 - LiquidStakingManager.dao can rug node operators with executeAsSmartWallet()
202: function executeAsSmartWallet(
203:         address _nodeRunner,
204:         address _to,
205:         bytes calldata _data,
206:         uint256 _value
207:     ) external payable onlyDAO {
208:         address smartWallet = smartWalletOfNodeRunner[_nodeRunner];
209:         require(smartWallet != address(0), "No wallet found");
210:         IOwnableSmartWallet(smartWallet).execute(
211:             _to,
212:             _data,
213:             _value
214:         );
215:     }
executeAsSmartWallet() is meant to enable proxy operations through DAO contracts, but introduces rugging vectors.

Note that as per the docs

LSD Networks can be deployed by any Ethereum Account
Despite the name, dao could be an EOA, making the attack even easier to perform.

A malicious dao can wait for a node operator to call registerBLSPublicKeys(), which will transfer amount = 4 ETH * Nvalidators to the smart wallet, then call executeAsSmartWallet() to transfer amount to their own account, rugging the node operator.

A bigger possible attack is rugging the staked ETH:

As per the logic in stake() the ETH staking deposit to the Ethereum Staking contract is performed by the node operator wallet.

When staking withdrawals are enabled, a dao can use executeAsSmartWallet() to withdraw all the staked ETH, rugging all the depositors.

Impact
Centralization risk

Tools Used
Manual Analysis
Upgraded Q -> M from #400 [1670235895275]
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

Judge has assessed an item in Issue #400 as M risk. The relevant finding follows:

 L01 - EOA restriction of wallet representative can be bypassed
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L435

A node operator can call registerBLSPublicKeys() to register a node runner to LSD and create a new smart wallet. The protocol only allows EOAs to be registered as _eoaRepresentative.

The issue is that this can be easily circumvented. As detailed in the isContract() definition by OpenZeppelin:

* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
*  - an externally-owned account
*  - a contract in construction
*  - an address where a contract will be created
*  - an address where a contract lived, but was destroyed
A user can for instance deploy a smart wallet via a factory using CREATE2, call SELFDESTRUCT on it.
They can then pass the address of that destructed smart contract as _eoaRepresentative to registerBLSPublicKeys().

The operator can then redeploy the same contract to the same address using a CREATE2 call in their factory.

Impact
Low
Upgraded Q -> M from #373 [1670018401792]
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

Judge has assessed an item in Issue #373 as M risk. The relevant finding follows:

 N2. ETH not accumulated in previewAccumulatedETH
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantMevAndFeesPool.sol#L91 supposed to have
accumulated += ...
Although it is an external view function, depending on its usages, it may present more issues to the callers.
Upgraded Q -> M from #265 [1669939411439]
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

Judge has assessed an item in Issue #265 as M risk. The relevant finding follows:

 ETH not updated in internal accounting of giant pools
Giant pools have a feature of bringing ETH back to the giant pools from the vaults.
The received ETH in not updated in internal accounting and therefore cannot be used for withdrawing funds which use the idleETH to determine how much ETH is idle in the giant pool

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

    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 (uint256 j; j < _lpTokens[i].length; ++j) {
                require(
                    vault.isDETHReadyForWithdrawal(address(_lpTokens[i][j])) == false,
                    "ETH is either staked or derivatives minted"
                );
            }

            vault.burnLPTokens(_lpTokens[i], _amounts[i]);
        }
    }
Upgraded Q -> M from #236 [1669938261828]
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

Judge has assessed an item in Issue #236 as M risk. The relevant finding follows:

 https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L249
It is possible the DAO determine the amount of commission very high for its own benefit, so setting a range for determining the commission seems logical. (Especially MAX commission should be pre- defined.)
Upgraded Q -> M from #205 [1669936745167]
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

Judge has assessed an item in Issue #205 as M risk. The relevant finding follows:

 L[01] - No check if EOARepresentative or EOARepresentativeOfNodeRunner is an EOA or a smart contract
Impact
A smart contract can end up being assigned as a smartWalletRepresentative.
Such smart contract might not have the functionality to interact with LSD or funds.

Proof of Concept
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L289-L303

  function rotateEOARepresentative(address _newRepresentative) external {
        require(_newRepresentative != address(0), "Zero address");
        require(isNodeRunnerBanned(msg.sender) == false, "Node runner is banned from LSD network");

        address smartWallet = smartWalletOfNodeRunner[msg.sender];
        require(smartWallet != address(0), "No smart wallet");
        require(stakedKnotsOfSmartWallet[smartWallet] == 0, "Not all KNOTs are minted");
        require(smartWalletRepresentative[smartWallet] != _newRepresentative, "Invalid rotation to same EOA");

        // unauthorize old representative
        _authorizeRepresentative(smartWallet, smartWalletRepresentative[smartWallet], false);

        // authorize new representative
        _authorizeRepresentative(smartWallet, _newRepresentative, true);
    }
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L695-L736

    function _authorizeRepresentative(
        address _smartWallet, 
        address _eoaRepresentative, 
        bool _isEnabled
    ) internal {
        if(!_isEnabled && smartWalletRepresentative[_smartWallet] != address(0)) {

            // authorize the EOA representative on the Stakehouse
            IOwnableSmartWallet(_smartWallet).execute(
                address(getTransactionRouter()),
                abi.encodeWithSelector(
                    ITransactionRouter.authorizeRepresentative.selector,
                    _eoaRepresentative,
                    _isEnabled
                )
            );

            // delete the mapping
            delete smartWalletRepresentative[_smartWallet];

            emit RepresentativeRemoved(_smartWallet, _eoaRepresentative);
        }
        else if(_isEnabled && smartWalletRepresentative[_smartWallet] == address(0)) {

            // authorize the EOA representative on the Stakehouse
            IOwnableSmartWallet(_smartWallet).execute(
                address(getTransactionRouter()),
                abi.encodeWithSelector(
                    ITransactionRouter.authorizeRepresentative.selector,
                    _eoaRepresentative,
                    _isEnabled
                )
            );

            // store EOA to the wallet mapping
            smartWalletRepresentative[_smartWallet] = _eoaRepresentative;

            emit RepresentativeAppointed(_smartWallet, _eoaRepresentative);
        } else {
Upgraded Q -> M from #121 [1669815281189]
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

Judge has assessed an item in Issue #121 as M risk. The relevant finding follows:

 Require - always false
contracts/liquid-staking/LiquidStakingManager.sol:280
isNodeRunnerWhitelisted[_nodeRunner] comparing to itself

require(isNodeRunnerWhitelisted[_nodeRunner] != isNodeRunnerWhitelisted[_nodeRunner], "Unnecessary update to same status");
should be

require(isNodeRunnerWhitelisted[_nodeRunner] != isWhitelisted, "Unnecessary update to same status");
Upgraded Q -> M from #38 [1669737057849]
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

Judge has assessed an item in Issue #38 as M risk. The relevant finding follows:

 6. require check will always be false!!
the require check will always be false so it the function will not do what it was designed to do and it will always revert Unnecessary update to same status

LiquidStakingManager.sol#L280
Upgraded Q -> M from #16 [1669734774225]
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

Judge has assessed an item in Issue #16 as M risk. The relevant finding follows:

 AQ6: https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LiquidStakingManager.sol#L356
This function provides too much power to Dao, if the dao calls the function, then he can be the node runner of each smart wallet and then call withdrawETHForKnot to drain each smart wallet.
Any user can block other users from interacting with LPTokens by transferring tiny amounts and resetting their lastInteractedTimestamp
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
LPTokens in several different contracts have an afterTokenTransfer hook, which updates the last interacted timestamp. Because users can send LPTokens to any other user, this resets their last interacted timestamp which is used for several key actions which make use of their tokens. They may be indefinitely suspended.
EOAs and system contracts can be blocked from some actions by continuously transferring them zero LP tokens
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LPToken.sol#L66-L70
https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantLP.sol#L43-L47


# Vulnerability details

## Impact
`LPToken` contains the map `lastInteractedTimestamp` which maps addresses to timestamps and is updated for from and to addresses after a token transfer. Many operations will check the last interaction time of an address and revert if it's too recent to combat botting and allow time for funds to be used properly as they're moved around. Addresses' timestamps are checked in some functions that transfer LP/ GiantLP tokens, which sometimes need to be called by other system contracts. Because anyone can update the `lastInteractedTimestamp` for any address, anyone can block system contracts or certain users from calling any functions that use `lastInteractedTimestamp` to require a cooldown.

## Proof of Concept
`lastInteractedTimestamp` is updated after token transfers for LP tokens.

[/contracts/liquid-staking/LPToken.sol#L66-L70](https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/LPToken.sol#L66-L70):
```solidity
66    function _afterTokenTransfer(address _from, address _to, uint256 _amount) internal override {
67        lastInteractedTimestamp[_from] = block.timestamp;
68        lastInteractedTimestamp[_to] = block.timestamp;
69        if (address(transferHookProcessor) != address(0)) transferHookProcessor.afterTokenTransfer(_from, _to, _amount);
70    }
```

[/contracts/liquid-staking/GiantLP.sol#L43-L47](https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/GiantLP.sol#L43-L47):
```solidity
43    function _afterTokenTransfer(address _from, address _to, uint256 _amount) internal override {
44        lastInteractedTimestamp[_from] = block.timestamp;
45        lastInteractedTimestamp[_to] = block.timestamp;
46        if (address(transferHookProcessor) != address(0)) ITransferHookProcessor(transferHookProcessor).afterTokenTransfer(_from, _to, _amount);
47    }
```

`lastInteractedTimestamp` is checked in the following functions:

[/contracts/liquid-staking/ETHPoolLPFactory.sol#L76-L82](https://github.com/code-423n4/2022-11-stakehouse/blob/4b6828e9c807f2f7c569e6d721ca1289f7cf7112/contracts/liquid-staking/ETHPoolLPFactory.sol#L76-L82):
```solidity
76    function rotateLPTokens(LPToken _oldLPToken, LPToken _newLPToken, uint256 _amount) public {
...
81        require(_amount <= _oldLPToken.balanceOf(msg.sender), "Not enough balance");
82        require(_oldLPToken.lastInteractedTimestamp(msg.sender) + 30 minutes < block.timestamp, "Liquidity is still fresh");
```
This could delay LP tokens from being rotated out of a BLS key that is never staked.

[/contracts/liquid-staking/GiantPoolBase.sol#L93
dETH are branded as slash proof, but ETH2 slashing could make 32 deposit drop much below 24 (down to 0), making dETH undercollateralized
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description
dETH is advertised as fault proof , slash proof ETH
https://docs.joinstakehouse.com/protocol/learn/DerivativeStakedAsset
However, ETH2 staked deposit can be slashed from 32 down to 0, not just to 24 as would be expected(24 dETH printed). This means dETH is undercollateralized, and indeeds bears the risk of being "paper money" based on no underlying value.

## Impact
Users are given an unfair representation of dETH, which may lead them to make incorrect financial decisions.

## Recommended Mitigation Steps
 Either correct dETH amounts based on real slashed value, or do not brand dETH as slash proof ETH.
ERC20 return values not checked
Audit: 2022-11-stakehouse-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

'token' is a ERC20 (from LPToken.sol)

The ERC20.transfer() and ERC20.transferFrom() functions return a boolean value indicating success. 
This parameter needs to be checked for success. Some tokens do not revert if the transfer failed but return false instead. 
Tokens that don’t actually perform the transfer and return false are still counted as a correct transfer.


## Proof of Concept

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

            token.transfer(msg.sender, amount);

## Tools Used

Manual inspection

## Recommended Mitigation Steps

All interactions with ERC20 tokens should follow correct EIP20 checks. 

We recommend checking the success boolean of  .transfer calls for the unknown token contract as this this could result in a loss of funds,
or to use OpenZeppelin’s SafeERC20 versions with the safeTransfer function that handle the return value check as well as non-standard-compliant tokens.


## References

This is a popular C4 finding, for example:

https://code4rena.com/reports/2021-10-mochi/#m-04-unchecked-erc20-transfer-calls
https://code4rena.com/reports/2022-01-trader-joe/#m-13-erc20-return-values-not-checked
https://code4rena.com/reports/2021-09-yaxis/#m-02-erc20-return-values-not-checked