xanoutas/solidity-security-findings huggingface dataset:
Potential Dangerous When Updating the `rsETH` and `LRTConfig`
Audit: 2023-11-kelp-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTConfig.sol#L144
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/utils/LRTConfigRoleChecker.sol#L47


# Vulnerability details

## Impact
The update on `rsETH` and `LRTConfig` can manipulate the whole system.

## Proof of Concept
In `LRTConfig`, the `rsETH` address can be updated arbitrary, however, the system is using `rsETH`'s supply to record the deposit and calculate the price, if `rsETH` is replaced by newer address, the value of the `rsETH` issued to user will be meaningless. Also, there could be a price broken while changing the `rsETH`, if the initial state of new `rsETH` is set properly. For example, if the initial supply for new `rsETH` is 0, when getting the `getRSETHPrice`, the price will be 1 ETHER, and the attacker mint large number of new `rsETH` with price 1 ether; after this, since there are already a lot of funds linked to the protocol and `rsETH`'s supply is non-zero, the return value for `getRSETHPrice` will be larger than 1 ETHER and the attacker can get the benefit. 

Similarly, all contracts inherited from the `LRTConfigRoleChecker` preserve the ability to update the `LRTConfig`, as a result, important values, such as, supported asset, rsETH can be updated, which will result in deposit loss and price change.

## Tools Used
Manual

## Recommended Mitigation Steps
Although the protocol is highly centralized, it is still recommended to remove some unnecessary privileged functions to avoid users' loss 





## Assessed type

Other
wrong accounting in minting leads to loss of funds
Audit: 2023-11-kelp-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L151-L157
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTOracle.sol#L52-L79


# Vulnerability details

## Vulnerability details:
### Details:

the protocol allows actors to deposit a supported asset and get minted RSETH depending on an exchange rate calculated in the following function:

`LRTOracle:getRSETHPrice`
```solidity=52
function getRSETHPrice() external view returns (uint256 rsETHPrice) {
        address rsETHTokenAddress = lrtConfig.rsETH();
        uint256 rsEthSupply = IRSETH(rsETHTokenAddress).totalSupply();

        if (rsEthSupply == 0) {
            return 1 ether;
        }

        uint256 totalETHInPool;
        address lrtDepositPoolAddr = lrtConfig.getContract(LRTConstants.LRT_DEPOSIT_POOL);

        address[] memory supportedAssets = lrtConfig.getSupportedAssetList();
        uint256 supportedAssetCount = supportedAssets.length;

        for (uint16 asset_idx; asset_idx < supportedAssetCount;) {
            address asset = supportedAssets[asset_idx];
            uint256 assetER = getAssetPrice(asset);

            uint256 totalAssetAmt = ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset);
            totalETHInPool += totalAssetAmt * assetER;

            unchecked {
                ++asset_idx;
            }
        }

        return totalETHInPool / rsEthSupply;
    }
```

it is called by :

```solidity=151
function _mintRsETH(address _asset, uint256 _amount) private returns (uint256 rsethAmountToMint) {
        (rsethAmountToMint) = getRsETHAmountToMint(_asset, _amount);

        address rsethToken = lrtConfig.rsETH();
        // mint rseth for user
        IRSETH(rsethToken).mint(msg.sender, rsethAmountToMint);
    }
```

and as we can see we transferFrom the user the asset and then get the exchange rate then we mint to the depositor.

`exchangeRate = totalETHInPool / rsEthSupply`
we have the totalETHInPool = previously deposited assets + the deposited asset by the depositor.

but the rsETHSupply is the previously minted RSETH, so basically the new depositor gets the wrong exchange rate.

for example : 
- there is 10 ether worth of stETH deposited. and there is 10 ether of RSETH minted.
- next depositor deposits 5 ether of stETH
- the exchange rate will be : 15 ether / 10 ether = 1.5, where it should have been 1
- so the next depoitor gets minted less 3.333 ether sETH

### Impact:

this vulnerability affects the whole protocol because it undermines the main and only functionality given to users through depositing and getting rsETH, which should have been an accounting token for the amount deposited.

there is direct loss of funds/stealing ability for attacker(see poc)/undermines protocol trust -> deserves high severity

### Proof of Concept:

here is a foundry POC's for the attack: get the environment from my inflation attack POC , a
Initialization functions can be front-run
Audit: 2023-11-kelp-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/RSETH.sol#L14
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L19


# Vulnerability details

## Impact
kelp protocol makes extensive use of openzepllin `Initializable` contract such as in `RSETH` and `LRTDepositPool` contracts. The `_disableInitializers` function of `Initializable` contract is invoked in `construcotrs` of the contracts and later `initialize` function is called to set the parameters & state variables of the contracts. However, the `initialize` function does not have any access control and can be called by anyone. This also means that `initialize` function can be front-run, allowing an attacker to incorrectly initialize the contracts. Thus, malicious user may `initialize` the deployed contracts to wrong values, forcing the deployer of to redeploy these contracts. The process can be repeated, which costs the malicious user less (only a small transaction fee) than it would to the owner of the kelp protocol (cost of contract re-deployment), potentially unnecessarily draining funds from the development team.

## Proof of Concept
Following test shows that any user can invoke `initialize` function on deployed contracts and configure the state to desired values. Include following test in `RSETHInitialize` contract in `RSETHTest.t.sol` file for testing :-
```
    function test_InitializeContractsVariablesByAnyOne() external {   
        vm.startPrank(bob);        //bob is not admin
        rseth.initialize(address(alice), address(lrtConfig));     

        assertTrue(rseth.hasRole(rseth.DEFAULT_ADMIN_ROLE(), alice), "Alice not set as Admin");
        assertEq(address(lrtConfig), address(rseth.lrtConfig()), "LRT config address is not set");        

        assertEq(rseth.name(), "rsETH", "Name is not set");
        assertEq(rseth.symbol(), "rsETH", "Symbol is not set");
        vm.stopPrank();
    }
```

Similarly, include following test in `LRTDepositPoolInitialize` contract in `LRTDepositPoolTest.t.sol` file for testing :-
```
    function test_InitializeContractsVariablesByAnyOne() external {
        vm.startPrank(bob);         //bob is not admin
        address dummy = makeAddr("dummy");
        lrtDepositPool.initialize(address(dummy));

        assertEq(lrtDepositPool.maxNodeDelegatorCount(), 10, "Max node delegator count is not set");
        assertEq(address(dummy), address(lrtDepositPool.lrtConfig()), "LRT config address is not set");
        vm.stopPrank();
    }
```

similar issue applicable to other contracts such as `NodeDelegator`.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement valid access control such that only the relevant/ authorize deployer can `initialize()`.


## Assessed type

Access Control
pess-unprotected-initialize
Audit: 2023-11-kelp-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/utils/LRTConfigRoleChecker.sol#L27-L33


# Vulnerability details

## Impact
The impact of having an unprotected initializer is that contract may be vulnerable to malicious or accidental initialization, which can compromise the security and functionality of your contract. We should always protect our initializers to prevent such issues.

## Proof of Concept
An unprotected initializer is a function that can be called only once to initialize the state of a contract, but does not have any access control or checks to prevent unauthorized or unintended calls. This can lead to security risks, such as:

- An attacker can call the initializer before the intended owner and take over the contract.
- An attacker can call the initializer after the intended owner and reset the state of the contract.
- An unintended caller can call the initializer by mistake and cause unexpected behavior.
```
 modifier onlyLRTAdmin() {
        bytes32 DEFAULT_ADMIN_ROLE = 0x00;
        if (!IAccessControl(address(lrtConfig)).hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) {
            revert ILRTConfig.CallerNotLRTConfigAdmin();
        }
        _;
    }
```

## Tools Used
- slither & Manual Inspection
## Recommended Mitigation Steps
To protect initializers, you should use modifiers or require statements that ensure that:

- The initializer can only be called by the intended owner or a trusted entity.
- The initializer can only be called once and cannot be called again.
- The initializer can only be called in the correct context, such as during deployment or upgrade.

For example, you can use the initializer modifier from the `OpenZeppelin` Contracts Upgradeable library1, which checks that the function has not been initialized before and that the contract address is a proxy. Alternatively, we can implement our own logic to check the initialization status and the caller’s role.
```
// Import the library
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

// Inherit from Initializable
contract LRTConfigRoleChecker is Initializable {
    // Use the initializer modifier
    function initialize() public initializer {
        // Your initialization logic here
    }

    // Use the onlyLRTAdmin modifier
    modifier onlyLRTAdmin() {
        bytes32 DEFAULT_ADMIN_ROLE = 0x00;
        require(IAccessControl(address(lrtConfig)).hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Caller is not LRTConfig admin");
        _;
    }
}

```
This code ensures that the initialize function can only be called once and only by a proxy, and that the onlyLRTAdmin modifier checks the caller’s role before executing the function. 


## Assessed type

Library
First depositor can deposit dust amounts to break rsETH accounting
Audit: 2023-11-kelp-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L95-L110
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTOracle.sol#L52-L79


# Vulnerability details


## Proof of Concept

This is the formula to calculate how much rseth to mint from the amount of LST, in `LRTDepositPool.getRsETHAmountToMint()`

```
        // calculate rseth amount to mint based on asset amount and asset exchange rate
        rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
```

`lrtOracle.getAssetPrice(asset)` returns the ETH price of the asset, using the asset/ETH oracle and returning latestAnswer (in 18 decimal places), `LRTOracle.getAssetPrice()`

```
    function getAssetPrice(address asset) public view onlySupportedAsset(asset) returns (uint256) {
        return IPriceFetcher(assetPriceOracle[asset]).getAssetPrice(asset);
    }
```

`lrtOracle.getRSETHPrice()` gets the price of rsETH. It takes the totalETHInPool and divides by the rsETHSupply. If there is no supply yet, return 1 ether.

```
    function getRSETHPrice() external view returns (uint256 rsETHPrice) {
        if (rsEthSupply == 0) {
            return 1 ether;
        }
        return totalETHInPool / rsEthSupply;
```

From all these, we know that the rsETH price is dependent on every LST asset deposited. Let's look at what potential issue can a first depositor surface.

Assume that there is no deposits, and no rsETH minted. The LST used is rETH, with a ratio of 1:1.09, rETH:ETH. (1 rETH is worth 1.09 ETH) 

- Malicious user deposits 1 wei of rETH through `depositAsset()`
- `getRsETHAmountToMint()` is called. 
- `lrtOracle.getAssetPrice(rETH)` returns 1.09e18. 
- `lrtOracle.getRSETHPrice()` returns 1e18 since rsETH supply == 0.
- rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
- rsethAmountToMint = (1 * 1.09e18) / 1e18;
- 1.09 wei of rsETH tokens is minted

Next, a normal user deposits 1e18 rETH through `depositAsset()`
- `getRsETHAmountToMint()` is called. 
- `lrtOracle.getAssetPrice(rETH)` returns 1.09e18.
- Since rsETH supply is not zero anymore, `getRSETHPrice()` calculation is executed
- `TotalAssetDeposits` is 1e18 + 1 wei of rETH. (user deposit of 1e18 + malicious user deposit of 1 wei)
- `totalETHinPool` is (1e18 + 1) * 1.09e18 = 1.09e36
- `totalETHinPool / rsEthSupply` = 1.09e36 / 1.09 = 1e36
- rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();
- rsethAmountToMint = (1e18 * 1.09e18) / 1e36 = 1.09
- Normal user deposited 1e18 worth of rETH and got back 1.09 wei worth of rsETH.


```
 for (uint16 asset_idx; asset_idx < supportedAssetCount;) {
            address asset = supportedAssets[asset_idx];
            uint256 assetER = getAssetPrice(asset);

            uint256 totalAssetAmt = ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset);
The method getRSETHPrice() in LRTOracle can overflow unexpectedly due to unsafe multiplication
Audit: 2023-11-kelp-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTOracle.sol#L52-L79


# Vulnerability details

## Summary
The `getRSETHPrice()` method calculated the exchange rate of RSETH. Which is used to calculate the amount of RSETH to mint in `_mintRsETH()` method. But the calculation of exchange rate the unsafe multiplication can cause overflows. Because the `_mintRsETH()` will unexpectedly revert due to overflow.

## Vulnerability Details
During the calculation of exchange rate of RSETH the below expression is used. But both totalAssetAmt & assetER has 1e18 decimals. Which makes totalETHInPool to have 1e36 decimals. So, the totalETHInPool will overflow for bigger numbers which is unexpected. 
```
totalETHInPool += totalAssetAmt * assetER;
```

### POC Logs:
```
  Total Eth In Pool: 
  6000000000000000000000000000000000000
  RS Eth Supply: 
  3000000000000000000
```

## Impact
Although the contract supports deposits till **uint256** but the exchange rate of RSETH calculations will decrease the range and revert for bigger values.

## Recommendations
Use the libraries such as Uniswap FullMath library which facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision.

https://docs.uniswap.org/contracts/v3/reference/core/libraries/FullMath


## Assessed type

Under/Overflow
getAssetPrice() is not checking for the return value
Audit: 2023-11-kelp-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/main/src/oracles/ChainlinkPriceOracle.sol#L38


# Vulnerability details

## Impact
Chainlink's `latestAnswer()` might return 0 if no answer could be reached. The code is not checking for that, which is dangerous as it is used to calculate pricing between pairs. In the worst case, this could affect the exchange rate and result in fund loss for the protocol. 

## Proof of Concept
https://github.com/code-423n4/2021-06-tracer-findings/issues/145

## Tools Used
Manual review.

## Recommended Mitigation Steps
Check for the returned price or better yet, use latestRoundData instead.


## Assessed type

Oracle
Lack of Withdrawal Function in the Protocol
Audit: 2023-11-kelp-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L1-L216


# Vulnerability details

## Description

The Kelp DAO protocol lacks a critical feature—a withdrawal function. This absence means that users currently have no way to retrieve their deposited assets from the protocol. In the world of decentralized finance (DeFi), where user control and flexibility are paramount, the absence of a withdrawal function can be a significant limitation.

## Impact

- **Inability to Withdraw Deposited Assets:** The primary impact of this issue is that users cannot withdraw assets they've deposited into the Kelp DAO protocol. Essentially, this means that once assets are committed, users have no means to initiate withdrawals. This lack of flexibility can be a significant inconvenience.

- **Reduced User Freedom:** The absence of a withdrawal function restricts users within the protocol. They are unable to move their assets as needed, respond to market dynamics, or make strategic decisions based on their financial goals.

## Explanation

A withdrawal function is a fundamental component of DeFi protocols. It empowers users by allowing them to access their assets when they need them. Without this feature, users are effectively locked into the protocol, unable to manage their assets according to their preferences.

In the context of Kelp DAO, the absence of a withdrawal function means that once assets are deposited, there's no way to initiate a withdrawal. This limitation can be especially problematic if users want to transfer their assets to other protocols, trade them on external platforms, or adapt to changing market conditions.

## Proof of Concept

The absence of a withdrawal function is evident upon reviewing the Kelp DAO protocol's codebase. There is no provision for users to initiate asset withdrawals in the current implementation.

## Recommended Mitigation Steps

To enhance user experience and align with DeFi principles, it is advisable to implement a withdrawal function within the Kelp DAO protocol. This feature would empower users by allowing them to initiate withdrawals of their deposited assets, providing them with much-needed control and flexibility over their holdings. This step would also ensure that the protocol remains in line with the core principles of decentralized finance, where users have full access to their assets within the platform.



## Assessed type

Error
integer division can cause inaccuracies in the getRSETHPrice() function
Audit: 2023-11-kelp-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTOracle.sol#L52-L79


# Vulnerability details

## Impact
RSETH exchange rate could be underestimated, potentially underfunding the pool over time.
The severity depends on the relative values of `totalETHInPool` and `rsEthSupply`:
• If `totalETHInPool` is close in magnitude to `rsEthSupply`, the exchange rate could be underestimated by a significant amount like 0.99 ETH. Severity is higher.
• As the ratio of `totalETHInPool/rsEthSupply` increases, the potential inaccuracy and severity increases.


## Proof of Concept
using integer division in the `getRSETHPrice()` function can cause inaccuracies in the returned exchange rate.
The relevant code is:

       uint256 totalETHInPool; 
       // ...
       totalETHInPool += totalAssetAmt * assetER;

       return totalETHInPool / rsEthSupply;

Here, `totalETHInPool` and `rsEthSupply` are both uint256 integers. Dividing them directly with / will truncate any decimal portion.
For example, if:
• `totalETHInPool` = 100
• `rsEthSupply` = 10
Then `totalETHInPool / rsEthSupply` will return 10, when the more accurate answer is 10.0.
This could underestimate the true RSETH price by up to 0.99 ETH per RSETH. Over multiple transactions, this error could accumulate and result in the pool being underfunded.



## Tools Used
Manual
## Recommended Mitigation Steps
Use decimal division by first converting the integers to the required decimal precision:


      function getRSETHPrice() external view returns (uint256 rsETHPrice) {

         // ...

        return (totalETHInPool * 1e18) / (rsEthSupply * 1e18); 

        }

By multiplying both values by 1e18, we convert them to 18 decimal fixed point numbers before dividing. This will preserve the full decimal precision and return the most accurate RSETH price.


## Assessed type

Other
inflation attack leads to loss of funds
Audit: 2023-11-kelp-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L119-L144
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTOracle.sol#L52-L79


# Vulnerability details

## Vulnerability details:
### Details:

the protocol allows depositing liquid staking token (stETH, cbETH, rETH) into `LRTDepositPool` and gives `RSETH` in return as a receipt token, according to an exchange rate defined as the sum of eth in the system (in `LRTDepositPool`, `NodeDelegator`, `eigenlayerStrategies`) divided by the RSETH supply. So in essence it acts as a vault, but without the protections necessary to protect against inflation attacks.

the function `depositAsset` is responsible for depositing supported assets in `LRTDepositPool` and mints you RSETH in exchange:
```solidity=119
function depositAsset(
        address asset,
        uint256 depositAmount
    )
        external
        whenNotPaused
        nonReentrant
        onlySupportedAsset(asset)
    {
        // checks
        if (depositAmount == 0) {
            revert InvalidAmount();
        }
        if (depositAmount > getAssetCurrentLimit(asset)) {
            revert MaximumDepositLimitReached();
        }

        if (!IERC20(asset).transferFrom(msg.sender, address(this), depositAmount)) {
            revert TokenTransferFailed();
        }

        // interactions
        uint256 rsethAmountMinted = _mintRsETH(asset, depositAmount);

        emit AssetDeposit(asset, depositAmount, rsethAmountMinted);
    }
```

the amount of rsETH yu get minted is according to the exchange rate that is calculated by the function `LRTOracle:getRSETHPrice` : 
```solidity=52
function getRSETHPrice() external view returns (uint256 rsETHPrice) {
        address rsETHTokenAddress = lrtConfig.rsETH();
        uint256 rsEthSupply = IRSETH(rsETHTokenAddress).totalSupply();

        if (rsEthSupply == 0) {
            return 1 ether;
        }

        uint256 totalETHInPool;
        address lrtDepositPoolAddr = lrtConfig.getContract(LRTConstants.LRT_DEPOSIT_POOL);

        address[] memory supportedAssets = lrtConfig.getSupportedAssetList();
        uint256 supportedAssetCount = supportedAssets.length;

        for (uint16 asset_idx; asset_idx < supportedAssetCount;) {
            address asset = supportedAssets[asset_idx];
            uint256 assetER = getAssetPrice(asset);

            uint256 totalAssetAmt = ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset);
            totalETHInPool += totalAssetAmt * assetER;

            unchecked {
                ++asset_idx;
            }
        }

        return totalETHInPool / rsEthSupply;
    }
```

which is vulnerable to inflation attack , where an attacker mints a really small amount of `RSETH`, then inflates it's exchange rate so that next deposits are rounded down even to zero in some cases.

see POC below

### Impact:

with the vulnerability an attac