xanoutas/solidity-security-findings huggingface dataset:
LRTDepositPool's deposit() doesn't handle fee-on-transfer tokens 
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Certain ERC20s (also known as fee-on-transfer tokens) charge fees each time a transfer is performed, whether through `transfer` or `transferFrom`. This is not handled in `LRTDepositPool:deposit()`, where the wrong amount of RsETH is minted after such deposits, resulting in accruing losses for the protocol.

## Proof of Concept

In `depositAsset()` on [L136-L141](https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L136-L141), it can be seen that a deposit amount of a particular ERC20 asset is taken from the user and transferred to `LRTDepositPool`. Then, the contract uses the same initial `depositAmount` to mint `rsETH` tokens, rather than accounting for the actual balance received.

```solidity
function depositAsset(
    address asset,
    uint256 depositAmount
)
  ...
{
    ...
    if (!IERC20(asset).transferFrom(msg.sender, address(this), depositAmount)) {
        revert TokenTransferFailed();
    }

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

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

## Tools Used

Manual Analysis

## Recommended Mitigation Steps

A snapshot should be taken before and after the transfer, and the difference between the initial balance and the new balance should be calculated.

```solidity
uint256 balanceBefore = IERC20(asset).balanceOf(address(this));

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

uint256 balanceAfter = IERC20(asset).balanceOf(address(this));
uint256 actualDepositAmount = balanceAfter - balanceBefore;

// interactions
uint256 rsethAmountMinted = _mintRsETH(asset, actualDepositAmount);
```



## Assessed type

Token-Transfer
Use of deprecated chainlink function
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

When using the `latestAnswer()` method to fetch the latest asset price, there's a potential issue. This method lacks a mechanism to verify the timeliness of the data, crucial for ensuring accurate and reliable price information in transactions.

## Proof of Concept

The `ChainlinkPriceOracle` contract uses the deprecated `AggregatorInterface.latestAnswer()` to retrieve the current price instead of the `latestroundData()` function recommended by Chainlink. The `latestAnswer()` function might return 0 if no answer is reached, which should be handled appropriately by the caller.

The recommended `latestRoundData()` function returns additional data which could be used to check whether the answer was reported recently enough.

If `latestAnswer()` returns 0 for a specific asset, users won't be able to mint rsETH and will receive 0 when withdrawing.

> [!NOTE]  
> I am aware that this issue has been included in the bot report but still reported this bug because the automated report does not explains the full impact of the finding and hence incorrectly judges it as low.
> The C4 docs explicitly states that raising issues from bot reports to a higher severity is fair game, as seen [here](https://docs.code4rena.com/roles/wardens/submission-policy#automated-findings-considered-known-issues).

## Tools Used

Manual review

## Recommended Mitigation Steps

Short term, consider using the latestRoundData function and add checks on the return data with proper revert messages if the price is stale or the round is uncomplete.



## Assessed type

Oracle
balances could be incorrectly counted
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/main/src/NodeDelegator.sol#L121


# Vulnerability details

## Impact
cbETH (0xBe9895146f7AF43049ca1c1AE358B0541Ea49704) which is present in the deployment script can blacklist addresses. When NodeDelegator calls `IStrategy(strategy).userUnderlyingView(address(this));`, balances will be returned without saying that the amount is unusable if the NodeDelegator contract was blacklisted. 
This can affect off-chain calculations or `getAssetCurrentLimit()`. 

## Proof of Concept
https://github.com/code-423n4/2023-11-kelp/blob/main/src/NodeDelegator.sol#L94
https://github.com/code-423n4/2023-11-kelp/blob/main/src/NodeDelegator.sol#L121

## Tools Used
Manual review.

## Recommended Mitigation Steps
Take into account that NodeDelegator can be blacklisted when returning balances.


## Assessed type

ERC20
Denial of service on the getRSETHPrice() function.
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The supportedAssetList array can grow indefinitely which may lead to a run-out of gas error that will lead to a denial of service for the `getRSETHPrice()` function, this will stop everyone from operating the function, including external contracts that rely on it.

## Proof of Concept
The admin can add as many tokens as they want to the array of supported tokens, with no way of shrinking the array, this might lead to a ran out of gas error when the array exceeds a certain length, rendering the `getRSETHPrice()` function inoperable.

## Tools Used
Manual Analysis and remix

## Recommended Mitigation Steps
Ensure that the supported asset lists don't grow too much, and keep the list as short as possible, a maximum length would be fine.


## Assessed type

DoS
The `getRSETHPrice()` function doesn’t scale the `assetER` before multiplying by `totalAssetAmt` and adding to `totalETHInPool`
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
In LRTOracle.sol the getRSETHPrice() is called it does a for loop to get the total amount of assets in the pool and adding the together in totalETHInPool, which it later divides by rsETH supply to determine the rsETH current price, the issue is when it gets the exchange rate of the asset `assetER` ,  since all the different assets are added into totalETHInPool it doesn’t consider the different price decimals gotten from the different price feed that different assets have, since totalETHInPool should be estimated by 1e18 as it is supposed to be dominated in ETH, the issue arises in using this unscaled exchange rates and multiplying by the totalAssetAmt to be added to the totalETHInPool that is used to get the price of rsETH.

- **This will lead to an incorrect Exchange rate of rsETH return via getRSETHPrice()**

## Tools Used
Manual review & Patrick alpha yt video on chainlink oracles 
## Recommended Mitigation Steps
Scale the exchange rate of the different assets to get the accurate totalETHInpool to be used to generate the rsETH price.








## Assessed type

Math
The `depositAssetIntoStrategy` function successfully emits the `AssetDepositIntoStrategy` event even if the underlying `depositIntoStrategy()` operation in Eigenlayers fails.
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/NodeDelegator.sol#L61-L67


# Vulnerability details

## Impact
The `depositAssetIntoStrategy()` function in the `NodeDelegator` contract is designed to deposit users' funds into Eigenlayers. However, the `AssetDepositIntoStrategy` event is emitted successfully even if Eigenlayers encounters an error. This can lead to inaccurate accounting and disruptions in off-chain systems relying on events for synchronization with the smart contract.

## Proof of Concept
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/NodeDelegator.sol#L61C1-L67C122

```solidity

61:        address eigenlayerStrategyManagerAddress = lrtConfig.getContract(LRTConstants.EIGEN_STRATEGY_MANAGER);
62:
63:        uint256 balance = token.balanceOf(address(this));
64:
65: @>     emit AssetDepositIntoStrategy(asset, strategy, balance);
66:
67: @>     IEigenStrategyManager(eigenlayerStrategyManagerAddress).depositIntoStrategy(IStrategy(strategy), token, balance);
```

Line number 65 will successfully emit event even if line number 67 reverted

## Tools Used

Manual Review

## Recommended Mitigation Steps

Modify the code to emit the `AssetDepositIntoStrategy` event after the successful execution of Eigenlayers' `depositIntoStrategy()`. Additionally, include the return value (shares) of `depositIntoStrategy()` in the event to provide insight into the amount of shares that have been deposited.


## Assessed type

Other
Rate calculation assumes 1:1 price ratio between asset and RSETH but pricing models may differ
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The getRsETHAmountToMint function calculates the amount of RSETH to mint based on the supplied asset amount and exchange rates from the oracle contract.

However, it assumes a 1:1 price ratio between the asset and RSETH when doing this calculation. In reality, oracle contracts may support different pricing models where the exchange rate between asset and RSETH is not necessarily 1:1. 

By hardcoded this assumption, it fails to properly handle the general case where the pricing model yields a non-1:1 ratio. This could lead to incorrect RSETH mint amounts. 

The contract should query the specific pricing model from the oracle and perform the rate calculation accordingly, rather than relying on a hardcoded assumption about the price ratio.



## Proof of Concept

The getRsETHAmountToMint() function performs the rate calculation based on a hardcoded 1:1 price ratio assumption.
```solidity
    function getRsETHAmountToMint(
        address asset,
        uint256 amount
    )
        public
        view
        override
        returns (uint256 rsethAmountToMint)
    {
        // setup oracle contract
        address lrtOracleAddress = lrtConfig.getContract(LRTConstants.LRT_ORACLE);
        ILRTOracle lrtOracle = ILRTOracle(lrtOracleAddress);

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

## Tools Used
Manual Review
## Recommended Mitigation Steps

Perform the rate calculation directly based on the oracle pricing logic rather than a hardcoded assumption.



## Assessed type

Oracle
chainlink price might be stale or incorrect
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/oracles/ChainlinkPriceOracle.sol#L37-L39


# Vulnerability details

## Vulnerability details:
### Details:

the protocol uses chainlink as a price oracle , but if there is a problem with Chainlink starting a new
round and finding consensus on the new value for the oracle (e.g. Chainlink nodes abandon the oracle, chain congestion, vulnerability/attacks on the chainlink system) then chainlink will report the wrong price.
```solidity=37
function getAssetPrice(address asset) external view onlySupportedAsset(asset) returns (uint256) {
        return AggregatorInterface(assetPriceFeed[asset]).latestAnswer();
    }
```

the protocol uses chainlink to check the price of supported assets, to see how much rsETH to mint.so if chainlink reports a stale price then attackers will be able to game the protocol and get value out of users.(see POC)

### Impact:

as the poc shows an attacker can leak value from other depositors if the chainlink price becomes stale. because of that this deserves a medium severity for leak of value.

### Proof of Concept:
lets imagine this scenario:
the exchange rate of stETH/rsETH = 1:1.
their are already depositors and the vault has stETH

- the current price of stETH is 1 ETH
- chainlink price oracle is stale and reporting an old price of 1.01 ETH.
- then arbitragers will deposit 100 stETH at the inflated price to get 101 rsETH where they shouldve gotten 100 rsETH.
- then after the chainlink oracle updates the price to 1 ETH
- their rsETH now is worth 101 stETH , basically taking value from other depositors.

### Tools Used:

vscode

### Recommended Mitigation Steps:

because `latestAnswer` is deprecated , and doesn't return roundId or updatedAt protocol should use `getLatestRoundData` and do the following checks:
```solidity
( roundId, rawPrice, , updateTime, answeredInRound ) = priceFeed.latestRoundData();
require(rawPrice > 0, "Chainlink price <= 0");
require(updateTime != 0, "Incomplete round");
require(answeredInRound >= roundId, "Stale price");

```


## Assessed type

Oracle
Use of deprecated Chainlink API that can lead to fetch stale prices
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/oracles/ChainlinkPriceOracle.sol#L37-L39


# Vulnerability details

## Impact
Chainlink contracts use the **latestAnswer** method to obtain asset prices. However, it's important to note that this method is deprecated, and relying on it without verifying the freshness of the returned price can impact an account's health and, consequently, lead to liquidations. Outdated prices may be employed, influencing liquidation pricing. While this method provides the last value, it doesn't allow you to confirm the data's freshness.

To address this issue, Chainlink recommends using the **latestRoundData** method, which enables additional validations. Utilizing stale prices can pose a risk to funds. According to Chainlink's documentation, the **latestRoundData** function does not produce an error if no answer has been reached but returns 0, potentially causing an incorrect price to be fed to the Price Oracle (source: Chainlink Documentation). It's crucial to be aware that **latestAnswer** is deprecated, as indicated in the Chainlink Price Feeds API Reference.

## Proof of Concept
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/oracles/ChainlinkPriceOracle.sol#L37-L39

## Severity justification
While bot report includes [this](https://github.com/code-423n4/2023-11-kelp/blob/main/bot-report.md#l-02-avoid-the-use-of-deprecated-chainlink-functions) it marked as low. 
But without description and mitigation steps this report doesn't make a lot of sense and can even harm in case if developers will keep it as known issue because of no justification or explanation.

## Tools Used
Manual check

## Recommended Mitigation Steps
```
       (
          roundId,
          rawPrice,
          ,
          updateTime,
          answeredInRound
        ) = AggregatorInterface(assetPriceFeed[asset]).latestRoundData();
        require(rawPrice > 0, "Chainlink price <= 0");
        require(updateTime != 0, "Incomplete round");
        require(answeredInRound >= roundId, "Stale price");
```











## Assessed type

Oracle
leak of value due to false reporting of funds in specific case 
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/NodeDelegator.sol#L121-L124
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTConfig.sol#L17
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTConfig.sol#L109-L122


# Vulnerability details

## Vulnerability details:
### Details:

the protocol uses the amount of assets held by in either `LRTDepositPool`, `NodeDelegator`, `eigenLayerStrategies`.
it gets the amount held by eigenLayer through the function: 

`NodeDelegator`

```solidity=121
function getAssetBalance(address asset) external view override returns (uint256) {
        address strategy = lrtConfig.assetStrategy(asset);
        return IStrategy(strategy).userUnderlyingView(address(this));
    }
```

which basically gets how much of an asset is held by a eigenLayer strategy plus reward ans slashing. but the strategy is gotten from a mapping in `LRTConfig` which maps each asset to a strategy.

```solidity=17
mapping(address token => address strategy) public override assetStrategy;
```

this mapping can be changed by admin: 

```solidity=109
function updateAssetStrategy(
        address asset,
        address strategy
    )
        external
        onlyRole(DEFAULT_ADMIN_ROLE)
        onlySupportedAsset(asset)
    {
        UtilLib.checkNonZeroAddress(strategy);
        if (assetStrategy[asset] == strategy) {
            revert ValueAlreadyInUse();
        }
        assetStrategy[asset] = strategy;
    }
```

so if an admin changes the asset strategy, the reported amount that  is invested  in eigenlayer would be one of the new strategy and not the old one.

currently eigenLayer supports one strategy per token, but it can change in the future and the protcol has the functionality to change the strategy of each token.

### Impact:

if an admin changes a strategy to another , the reported funds held by the protocol would be lower meaning that the exchange rate of asset/RSETH would also be lower or even 0, meaning the next person to deposit will get minted 0(SEE POC), because the likelyhood is low but the impact is high this should get a medium severity.

### Proof of Concept:

lets imagine this scenario:
their are already depositors and the vault has 100 stETH given to eigenLayer strategy A

- eigenlayer deploys second strategy B which is better.
- admin changes the strategy of stETH to strategy B
- alice comes to deposit 10 stETH
- the protocol calculates exchangeRate=totalAssetsDeposited/rsETHMinted
- the protocol would look for how much is deposited in eigenlayer strategy , but because admin changed it to B, it would return 0 because no deposits in strategy B.
- which would mean alice gets minted 0.
- basically DOS the protocol until somone donates stETH to the pool. or admin changes strategy back.

this POC shows that if the admin changes the strategy of an asset , it will create real problems
Denial of service on the getSupportedAssetList() function. 
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTConfig.sol#L135


# Vulnerability details

## Impact
The supportedAssetList array can grow indefinitely which may lead to ran out of gas error that will lead to a denial of service for the `getSupportedAssetList()` function, this will stop everyone from operating the function, including external contracts that rely on it.

## Proof of Concept
The admin can add as many tokens as they want to the array of supported tokens, with no way of shrinking the array, this might lead to a ran out of gas error when the array exceeds a certain length, rendering the `getSupportedAssetList()` function inoperable.

## Tools Used
Manual Analysis and remix

## Recommended Mitigation Steps
Get the supported asset list within a range, using this method will prevent running out of gas error

    function getSupportedAssetList(uint start, uint256 end) external view override
     returns (address[] memory) {
        address [] memory  _supportedAssetList = new address[](end - start);
        uint index = 0;
        for (uint i = start; i < end;) {
            _supportedAssetList[index] = supportedAssetList[i];
            unchecked {
                ++i;
                ++index;
            }
        }
        return _supportedAssetList;
    }

Or

Ensure that the supported asset lists don't grow too much, and keep the list as short as possible, a maximum length would be fine.




## Assessed type

DoS
No check for minimum and maximum price
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/oracles/ChainlinkPriceOracle.sol#L37-L39


# Vulnerability details

## Impact
Chainlink oracles aggregator contain both minimum and maximum price/answers, this are possible values that the oracle can return. This means when the price agreed upon by the nodes is below the minimum, the minimum will be returned, same applies to the maximum. 

## Proof of Concept

The Three Assets with strategies all have the following min and max answers

stEth 
Min : 1000000000000000 
Max : 100000000000000000000

cbEth
Max : 95780971304118053647396689196894323976171195136475135
Min: 1

rEth
Max: 95780971304118053647396689196894323976171195136475135 
Min: 1

## Tools Used
Manual 

## Recommended Mitigation Steps
Validate return prices against minimum and max, when value matches, kelp should respond as it would to an invalid answer. 


## Assessed type

Oracle
centralization risk in this protocol contract
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/utils/LRTConfigRoleChecker.sol#L47-L51
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L162-L176
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTOracle.sol#L107-L108
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/NodeDelegator.sol#L132-L134
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L213-L214


# Vulnerability details

## Impact
the modifier `onlyLRTAdmin` have the `DEFAULT_ADMIN_ROLE` rule which is not recommended to use it to give any address a rule, as this rule is the owner for himself too as the OZ contract mentioned, which mean the owner/holder of this rule can give another address his power and manipulate critical info/functions. 

[the OZ warning link](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/74a5d4d4348effabf220ee15909a3aa4467267d5/contracts/access/AccessControlUpgradeable.sol#L45-L48)

## Proof of Concept
holder of this rule have some critical power that can manipulate the whole protocol if it goes/gives to another address by mistake:

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

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L162-L176

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

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/NodeDelegator.sol#L132-L134

https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L213-L214

## Tools Used
N/A

## Recommended Mitigation Steps
recommend to follow OZ recommendation and use `AccessControlDefaultAdminRules`


## Assessed type

Access Control