xanoutas/solidity-security-findings huggingface dataset:
NodeDelegator:: Ln 59, 122: Potential Loss of Funds Due to Strategy Change Without Prior Withdrawal 
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The NodeDelegator contract tracks the balance of assets deposited into a strategy, which is configured in the LRTConfig contract. However, the strategy associated with a particular asset can be changed using the updateAssetStrategy function in the LRTConfig contract. This could potentially lead to loss of funds if the strategy is changed while there are still assets in the old strategy.

If the strategy for an asset is changed while there are still funds in the old strategy, those funds could be lost or stuck. This is because the NodeDelegator contract might not have a reference to the old strategy anymore, and therefore cannot interact with it.

## Proof of Concept
-Initial Setup:

The LRT platform uses the NodeDelegator contract to manage assets and strategies. The platform supports a variety of assets, including rsETH, and uses different strategies to optimize the yield for each asset. The strategies are implemented in separate contracts and can be changed by the LRT manager using the LRTConfig contract.

-Depositing Assets:

The LRT manager decides to deposit a certain amount of rsETH into the platform. They call the depositAssetIntoStrategy function in the NodeDelegator contract, which transfers the rsETH from the NodeDelegator contract to the current strategy for rsETH, let's call it StrategyA.

-Changing the Strategy:

Later, the LRT manager decides to change the strategy for rsETH to a new strategy, StrategyB, because it offers a higher yield. They call the updateAssetStrategy function in the LRTConfig contract, which updates the strategy for rsETH in the NodeDelegator contract.

-The Issue:

However, they forget to withdraw the rsETH from StrategyA before changing the strategy. Now, the NodeDelegator contract thinks that the strategy for rsETH is StrategyB and has no reference to StrategyA anymore. This means it cannot interact with StrategyA to withdraw the rsETH.



The rsETH that was deposited into StrategyA is now stuck. Even though the rsETH is technically still in the platform, it cannot be accessed or managed by the NodeDelegator contract. If the LRT manager tries to withdraw the rsETH, the NodeDelegator contract will try to withdraw it from StrategyB, not from StrategyA, and the withdrawal will fail because there is no rsETH in StrategyB.

This scenario illustrates how changing the strategy for an asset while there are still funds in the old strategy can lead to loss of funds. The NodeDelegator contract loses the reference to the old strategy and cannot interact with it anymore, which means it cannot withdraw the funds from the old strategy.

## Tools Used
Foundry 
## Recommended Mitigation Steps

Withdraw funds before updating strategy:
Before updating the strategy for an asset, ensure that all funds are withdrawn from
No Storage Gap for Upgradeable Contract Might Lead to Storage Slot Collision
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTConfig.sol#L8
https://github.com/code-423n4/2023-11-kelp/blob/main/src/LRTDepositPool.sol#L14-L15
https://github.com/code-423n4/2023-11-kelp/blob/main/src/NodeDelegator.sol#L13-L14


# Vulnerability details

## Impact
For upgradeable contracts, there must be storage gap to "allow developers to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Otherwise it may be very difficult to write new implementation code. Without storage gap, the variable in child contract might be overwritten by the upgraded base contract if new variables are added to the base contract. This could have unintended and very serious consequences to the child contracts.

Refer to the bottom part of this article: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable


## Proof of Concept:

Several contracts are intended to be upgradeable contracts in the code base, including

- LRTConfig
- LRTDepositPool
- LRTOracle
- NodeDelegator

However, none of these contracts contain storage gap. The storage gap is essential for upgradeable contract because "It allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments". Refer to the bottom part of this article:


You can see [here](https://discordapp.com/channels/810916927919620096/1171865604114882600/1172771196895510528) stating sponsor that the contracts are upgradeable, also the [contest page](https://code4rena.com/contests/2023-11-kelp-dao-rseth#top) on code4rena says that the contracts are upgradeable
 
As an example, there isn't any contract (in audit scope) in the repo not contain any storage gap. If in a future upgrade, an additional variable is added to any of the contract then it can potentially overwrite the beginning of the storage layout, causing critical misbehaviors in the system.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider defining an appropriate storage gap in each upgradeable parent contract at the end of all the storage variable definitions as follows:
```solidity
uint256[50] __gap; // gap to reserve storage in the contract for future variable additions

```


## Assessed type

Upgradable
No Check for chainlink  return price value. 
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
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L109-L109


# Vulnerability details

## Impact

`LatestAnswer` can return a Value of `0` when the expected price is not available, this is also stated by Chianlink like so "This does not error if no answer has been reached, it will simply return 0."

## Proof of Concept
The code 

    function getAssetPrice(address asset) external view onlySupportedAsset(asset) returns (uint256) {
        return AggregatorInterface(assetPriceFeed[asset]).latestAnswer();
    }

Does not validate that the return value of  latestAnswer is not above zero, as this is a possible return value by the oracle when the price has not been reached for any reason necessary. 

    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();
    } 

In instances when this is not validated against it produces an invalid price and prices assets wrongly. 

## Tools Used
Manual 

## Recommended Mitigation Steps
Validate that the chainlink Returned price is greater than zero. Alternatively consider using the more updated `LatestRoundData` function. 


## Assessed type

Oracle
important check is missed  for the `getAsset` function which using latestAnswer from chainlink
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
https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/oracles/ChainlinkPriceOracle.sol#L11-L13


# Vulnerability details

## Bug description

using the `latestAnswer` from chainlink Aggregator missed some sanity checks that should be added into the function `getAssetPrice`.

## Impact

the `getAssetPrice` using the `latestAnswer` from chainlink Aggregator and it miss some important checks that should be added, these checks is added by the `latestRoundData` too and should be added here for the `latestAnswer` in `getAssetPrice`.

## Proof of Concept

the function `getAssetPrice` and the `AggregatorInterface` :

```solidity
interface AggregatorInterface {
    function latestAnswer() external view returns (uint256);
}

 function getAssetPrice(address asset) external view onlySupportedAsset(asset) returns (uint256) {
        return AggregatorInterface(assetPriceFeed[asset]).latestAnswer();
    }
```

sanity checks should be added in the `getAssetPrice` function. this function is used in many places and its important to add these checks for it, it called in this [line](https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTDepositPool.sol#L109) and this [line](https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTOracle.sol#L46) and this [line](https://github.com/code-423n4/2023-11-kelp/blob/f751d7594051c0766c7ecd1e68daeb0661e43ee3/src/LRTOracle.sol#L68) too

## Tools Used

manual review

## similar issue

https://solodit.xyz/issues/m-15-lacking-validation-of-chainlink-oracle-queries-code4rena-vader-protocol-vader-protocol-contest-git

## Recommended Mitigation Steps

add the checks for `answeredInRound` & `price > 0` & `updateTime != 0` :

```solidity
if(answeredInRound < roundID){
    revert ERR()
}
if( price <= 0) {
    revert err2()
}
if (updateTime == 0) revert  err3()

```



## Assessed type

Other
Unused Return :: ignores the return value of an external call to the function
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
It ignores the return value of an external call to the function `getDeposits` of the contract `IEigenStrategyManager`. 
This function returns two values: an array of strategies and an array of amounts. The code only stores the first value in a local variable, and discards the second value. 
- This can lead to potential issues, such as:

- The second value may contain important information that is relevant to the logic or state of the contract, such as the total amount of deposits or the balance of each strategy.
- The second value may indicate the success or failure of the external call, such as by returning a zero or non-zero value, or by using a revert reason.
- The second value may be used to check the consistency or validity of the first value, such as by verifying the length or sum of the arrays.

## Proof of Concept
NodeDelegator.getAssetBalances() (src/NodeDelegator.sol#94-116) ignores return value by (strategies) = IEigenStrategyManager(eigenlayerStrategyManagerAddress).getDeposits(address(this)) (src/NodeDelegator.sol#102-103)
```
   function getAssetBalances()
        external
        view
        override
        returns (address[] memory assets, uint256[] memory assetBalances)
    {
        address eigenlayerStrategyManagerAddress = lrtConfig.getContract(LRTConstants.EIGEN_STRATEGY_MANAGER);

     @>   (IStrategy[] memory strategies,) =
     @>      IEigenStrategyManager(eigenlayerStrategyManagerAddress).getDeposits(address(this));

        uint256 strategiesLength = strategies.length;
        assets = new address[](strategiesLength);
        assetBalances = new uint256[](strategiesLength);

        for (uint256 i = 0; i < strategiesLength;) {
            assets[i] = address(IStrategy(strategies[i]).underlyingToken());
            assetBalances[i] = IStrategy(strategies[i]).userUnderlyingView(address(this));
            unchecked {
                ++i;
            }
        }
    }
```

## Tools Used
- Manual

## Recommended Mitigation Steps
To prevent these issues, you should always store and use all the return values of the external calls, or at least check that they are not null or zero. You can use the require or assert statements to perform such checks, or use the values in your logic or state updates. 
```
function getAssetBalances()
        external
        view
        override
        returns (address[] memory assets, uint256[] memory assetBalances)
    {
        address eigenlayerStrategyManagerAddress = lrtConfig.getContract(LRTConstants.EIGEN_STRATEGY_MANAGER);

        // Store both return values
        (IStrategy[] memory strategies, uint256[] memory amounts) =
            IEigenStrategyManager(eigenlayerStrategyManagerAddress).getDeposits(address(this));

        // Check that the arrays are not null or zero
        require(strategies.length > 0 &&
`getRSETHPrice()` doesn’t consider the tokens decimal before dividing by  `rsEthSupply` which can lead to incorrect prices returned for rsETH.
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
In `getRSETHPrice()` when calculating the totalETHInPool in the for loop, it doesn’t put into context the different asset’s decimal which can lead to incorrect price of rsETH returned due to either rounding issues or under/overestimating of the total ETH in the pool, mainly because not every LST has the same decimal and not considering its decimal and adding it to totalETHInPool will breed an incorrect value of what’s in the pool

## Proof of Concept

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

```

            totalETHInPool += totalAssetAmt * assetER;

```


## Tools Used
Manual review
## Recommended Mitigation Steps
Consider the tokens decimal before adding the amount to `totalETHInPool`




## Assessed type

Error
`rsEth` TOKEN, MINT AMOUNT CALCULATION, IS BROKEN DUE TO FEE-ON-TRANSFER TOKEN DEPOSITS HAPPEN VIA `LRTDepositPool.depositAsset` FUNCTION
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The `LRTDepositPool.depositAsset` function is used to allow user to stake a particular asset amount to the protocol. After the initial input validation checks the `asset transfer` is executed from the `msg.sender` to the `deposit pool` as shown below:

        if (!IERC20(asset).transferFrom(msg.sender, address(this), depositAmount)) { //@audit-info - transfer the deposit amount to this address
            revert TokenTransferFailed(); //@audit-info - revert if the transfer failed
        }

Then the `depositAmount` is used to calculate the `rsEth` (receipt token) amount to mint for the `msg.sender` as shown below:

        uint256 rsethAmountMinted = _mintRsETH(asset, depositAmount);

The issue here is that the `asset transfer` in the `LRTDepositPool.depositAsset` function does not account for `fee-on-transfer`. Since the `kelp` product is expected to work with multiple `ERC20 tokens` as assets there could be certain assets which `charge` a `fee on transfers`. But in the actual asset amount transfer from the `msg.sender` to the `deposit pool` the fee-on-transfer scenario is not considered.

As a result more `rsethAmountMinted` amount could be minted for a `msg.sender` who deposits a `fee-on-transfer` ERC20 token asset amount (depositAmount) to the protocol. But the actual deposited asset amount to the `LRTDepositPool` contract would be less than the `depositAmount` due to the fee charged on the transfer.

This could lead to the `rsethAmountToMint` calculation in the `LRTDepositPool.getRsETHAmountToMint` erroneous as shown below:

1. `getRsETHAmountToMint` function calculates the `rsethAmountToMint` as shown below:

        rsethAmountToMint = (amount * lrtOracle.getAssetPrice(asset)) / lrtOracle.getRSETHPrice();

2. Here the `lrtOracle.getRSETHPrice()` calculation uses the `total asset deposits` in the protocol and converts those asset amounts into `Eth amount` as shown below:

        for (uint16 asset_idx; asset_idx < supportedAssetCount;) {
            address asset = supportedAssets[asset_idx];
            uint256 assetER = getAssetPrice(asset); //@audit-info - get the asset price for each of the assets in the supported asset list

            uint256 totalAssetAmt = ILRTDepositPool(lrtDepositPoolAddr).getTotalAssetDeposits(asset); //@audit-info - get the total asset amount from all three contracts
            totalETHInPool += totalAssetAmt * assetER; //@audit-info - increment the total asset amount in the contract via the ETH amount (because it is common denominator)

            unchecked {
                ++asset_idx;
            } //@audit-info - increment the i
Chainlink price feed is deprecated, not sufficiently validated and can return stale prices Which will lead to a lot of issues
Audit: 2023-11-kelp-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact 
This issue is not like the boy report as the bot doesn’t show means of exploitation and a proper mitigation.
In ChainlinkPriceOracle.sol  The function _createActionInfo() uses Chainlink's deprecated latestAnswer function, this function also does not guarantee that the price returned by the Chainlink price feed is not stale and there is no additional checks to ensure that the return values are valid.
They following issues can arise.
- stale prices.
- outdated prices.
- price returned can be above or below its min and max range.

The following issues could be used to exploit/drain the protocol if an asset’s feed returns the above.


## Proof of concept 

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

function getAssetPrice(address asset) external view onlySupportedAsset(asset) returns (uint256) {
        return AggregatorInterface(assetPriceFeed[asset]).latestAnswer();
    }

```

## Tools Used
Manual review 
## Recommended Mitigation Steps
Use chainlink’s latest round data and check against 
- stake prices
- outdated timestamp 
- min/max range 


## Assessed type

Invalid Validation
H-03 Unmitigated
Audit: 2024-04-ai-arena-mitigation-findings
Severity: HIGH

# Lines of code




# Vulnerability details

Mitigation of H-03: NOT fully mitigated

## Mitigated issue
[H-03: Players have complete freedom to customize the fighter NFT when calling redeemMintPass and can redeem fighters of types Dendroid and with rare attributes](https://github.com/code-423n4/2024-02-ai-arena-findings/issues/366)

The issue was that `FighterFarm.redeemMintPass()` allowed for user controlled input to customize their new fighter.

## Mitigation review - "re-requesting of randomness"
A delegated signer scheme like the one in `claimFighters()` has been implemented in `redeemMintPass()`. This means that the delegated signer has to approve and sign the input values.

There is a likely issue in the randomness of the signed input. At least the fighter DNA is supposed to be random. Since it is still the user who calls `redeemMintPass()`, this means the signed message has to be requested by the user from the delegated signer. This is an off-chain process. Since it must be taken into account that the user might lose this signature and need a new one, it should be possible for the user to re-request a signature, which suggests that the DNA is regenerated anew. This is the equivalent of the issue of [re-requesting randomness from a randomness oracle](https://docs.chain.link/vrf/v2/security#do-not-allow-re-requesting-or-cancellation-of-randomness).
It cannot be guaranteed that the delegated signer will store the signature indefinitely for the user, and if the DNA is deterministically generated from on-chain data it does not make sense to mitigate the issue in this way.

This means that this mitigation still leaves this an issue of the same kind as [M-05: Can mint NFT with the desired attributes by reverting transaction](https://github.com/code-423n4/2024-02-ai-arena-findings/issues/376).

### Suggested further mitigation
A simple way to amend this would be to have the delegated signer itself call `redeemMintPass()`. That is, the delegated signer then acts like a random oracle. This is of course more centralized than using a decentralized VRF, but may be acceptable in this case. Or if the delegated singer calls another function to just set the DNA and other values, which are then retrieved in `redeemMintPass()`.
If the delegated signer is used to set some randomness on-chain, it seems a good and transparent solution is to use `blockhash(block.number - 1)`. Since the user has no control over when the delegated signer calls the contract, he cannot influence the randomness. This reduces the centralization to only giving the delegated signer the choice of when to call the contract (to wait for another `blockhash`).
Review of ADD-06: Issue found
Audit: 2024-04-ai-arena-mitigation-findings
Severity: HIGH

# Lines of code

https://github.com/ArenaX-Labs/2024-02-ai-arena-mitigation/blob/d81beee0df9c5465fe3ae954ce41300a9dd60b7f/src/Neuron.sol#L168-L169


# Vulnerability details

## Reviewed changes
New airdrop mechanism in https://github.com/ArenaX-Labs/2024-02-ai-arena-mitigation/pull/16/commits/d81beee0df9c5465fe3ae954ce41300a9dd60b7f.

## Review
### QA issues
[Missing @param for `root`](https://github.com/ArenaX-Labs/2024-02-ai-arena-mitigation/blob/d81beee0df9c5465fe3ae954ce41300a9dd60b7f/src/Neuron.sol#L69-L74)

Note that `rootClaimedAirdrop` is keyed by the user address rather than by leaves (address _and amount_). This means that you have to make sure that an address appears in at most one leaf, since it can only claim one amount. If this is undesirable `rootClaimedAirdrop` should be keyed by the leaf(hash).

### High issue
[These lines in `Neuron.claim()`](https://github.com/ArenaX-Labs/2024-02-ai-arena-mitigation/blob/d81beee0df9c5465fe3ae954ce41300a9dd60b7f/src/Neuron.sol#L168-L169) are wrong.
```solidity
_approve(treasuryAddress, msg.sender, amount);
transferFrom(treasuryAddress, msg.sender, amount);
```
Approval is given to `msg.sender` to spend from `treasuryAddress` in addition to a transfer from `treasuryAddress` to `msg.sender`. This leads either to a revert in `transferFrom()` if Neuron has not been given approval by the treasury, or a double claim by the user spending his approved amount in addition to the same amount transferred.
What is intended is probably
```diff
- _approve(treasuryAddress, msg.sender, amount);
+ _approve(treasuryAddress, address(this), amount);
transferFrom(treasuryAddress, msg.sender, amount);
```
I'm not sure why airdropped token have to pass via the treasury, but otherwise they could instead just be minted directly to the user, i.e. `_mint(msg.sender, amount)`.


## Assessed type

ERC20