xanoutas/solidity-security-findings huggingface dataset:
Users can claim the eth accidently sent to PrelaunchPoints using the claim function
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-loop/blob/main/src/PrelaunchPoints.sol#L262


# Vulnerability details

## Impact

Any user can claim the eth accidently sent to the PrelaunchPoints contract.

## Proof of Concept

The protocol operates under the assumption that ETH sent to the contract is permanently locked:

```solidity
    /**
     * Enable receive ETH
     * @dev ETH sent to this contract directly will be locked forever.
     */
    receive() external payable {}
```

The problem arises in the _claim function, which uses address(this).balance instead of the user's balance when depositing in lpETH, resulting in a first-come, first-served scenario:

```solidity
            claimedAmount = address(this).balance;
            lpETH.deposit{value: claimedAmount}(_receiver);
```

Consequently, after all ETH has been converted, any remaining or subsequently sent ETH to the contract will be claimed by the first user.

## Tools Used

Manual review

## Recommended Mitigation Steps

Consider updating the _claim function as follows:

```solidity
            claimedAmount = userClaim > address(this).balance ? address(this).balance : userClaim;
            lpETH.deposit{value: claimedAmount}(_receiver);
```

dditionally, implement a similar function to recoverERC20 to withdraw ETH, accessible only after convertAllETH has been called.



## Assessed type

Other
Users can raise the `lpETH` tokens out of the locking process
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L392
https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L315
https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/README.md?plain=1#L124


# Vulnerability details

## Issue Description
The protocol's main job is to lock ETH and other tokens to send `lpETH` tokens to users at the right time
There is an invariant that says: `Deposits are active up to the lpETH contract and lpETHVault contract are set`
Users deposit and lock ETH before `loopActivation` and their `lpETH` tokens correspond to the number of tokens they deposited at the right time so technically they must not be able to manipulate the `lpETH` tokens after `loopActivation`.
However, this can be done by sending ether to the contract thanks to the `receive()` function before the owner calls the `convertAllETH` function which sets the `totalLpETH`.
The value of `totalLpETH` is crucial for calculating the number of `lpETH` tokens users can claim
This unintentionally enables users to influence their `lpETH` token balance after `loopActivation`, violating the protocol's intended behavior and its invariant

## Impact
This vulnerability allows users to manipulate their `lpETH` token balance after the critical `loopActivation` phase, potentially leading to discrepancies in the issuance of `lpETH` tokens. This undermines the protocol's security and reliability, impacting the expected behavior of deposit and token issuance mechanisms

## Proof of Concept
Here is the scenario:
1. Users lock ETH
2. the owner calls the `setLoopAddresses` function and ends up with the deposits
3. According to one of the main invariants, Users can't lock and deposit any tokens after setting the `loopActivation` 
However, Users can send ETH to the contract before the owner calls the `convertAllETH` which is unexpected behavior and breaks the functionality of the protocol

## Tools Used
manual review

## Recommended Mitigation Steps
remove the `receive()` function or modify it


## Assessed type

Timing
Unaccounted ETH Deposits May Lead to Unintended `lpETH` Minting and Staking
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L262


# Vulnerability details

#### Summary:

The `PrelaunchPoints` contract currently contains logic that allows users to claim their locked funds as `lpETH` during a claim period. However, there exists a vulnerability where ETH sent to the contract via direct transfer (fallback function) is not accounted for and may result in more `lpETH` being minted and potentially staked than intended based on a user's legitimate locked tokens balance.

#### Description:

In the `_claim` function, after swapping a user's ERC20 tokens for ETH using `_fillQuote`, the contract mints `lpETH` using the entire balance of ETH within the contract, rather than only the amount of ETH that was specifically obtained from the swap. This behavior opens up an opportunity for a user to "front-run" their claim by transferring ETH directly to the contract at the time of calling the `claim` function, leading to a larger amount of `lpETH` being minted than is due from their locked ERC20 token balance alone.

#### Impact:

By sending additional ETH to the contract before claiming, a user can artificially inflate the amount of `lpETH` that they receive, which could lead to an unfair advantage in the staking process. This may, in turn, distort the staking rewards and undermine the prelaunch point system's integrity and intended economics, leading to possible reputational damage and potential financial loss for legitimate participants.

#### Steps To Reproduce:

1. Lock an ERC20 token in the contract that is approved for conversion to `lpETH`.
2. Call the `claim` function with parameters to trade the ERC20 for ETH and then mint `lpETH`.
3. Immediately before the `claim` transaction is mined, send ETH directly to the contract's address, triggering the fallback function.
4. Observe that the contract uses the entire ETH balance, which includes the additional ETH sent unintentionally, to mint `lpETH`.

#### Recommendation:

To mitigate this issue, the contract should clearly track and account for the ETH received solely from the execution of the token swap. Only this amount of ETH should be used for the minting of `lpETH`. The contract can achieve this by checking the ETH balance before and after the swap transaction, using the difference between these balances for minting:

```solidity
uint256 preSwapBalance = address(this).balance;
// Execute the token to ETH swap
_fillQuote(IERC20(_token), _amount, _swapCallData);
uint256 postSwapBalance = address(this).balance;
// Calculate ETH obtained from the swap
uint256 swappedETHAmount = postSwapBalance - preSwapBalance;
// Only use the swapped ETH amount for lpETH minting
lpETH.deposit{value: swappedETHAmount}(_receiver);
```

Implementing these changes would ensure that only the ETH obtained from swapping locked ERC20 tokens is used for claiming, thereby preventing manipulation by front-running with direct ETH t
Users don't claim the correct amount of `lpETH` token, violating the invariant
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L249
https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L321-L324
https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L392


# Vulnerability details

## Issue Description
There is an invariant that says : 
`Users that deposit ETH/WETH get the correct amount of lpETH on claim (1 to 1 conversion)`
The protocol's invariant, which guarantees a 1:1 conversion ratio of deposited ETH to lpETH upon claim, is being violated due to the presence of the `receive()` function and the `convertAllETH` function.
```java
    function convertAllETH() external onlyAuthorized onlyBeforeDate(startClaimDate) {
        // ...

        uint256 totalBalance = address(this).balance;
        lpETH.deposit{value: totalBalance}(address(this));

        totalLpETH = lpETH.balanceOf(address(this));

        // ...
    }
```
The `convertAllETH` function improperly calculates `totalLpETH` by assigning the contract's ETH balance directly to this variable, rather than ensuring accurate handling of ETH and `lpETH` balances. This miscalculation allows `totalLpETH` to be increased through the receipt of ETH without corresponding `lpETH` issuance, leading to a disparity between `totalLpETH` and `totalSupply`.
This all happens because of the simple math calculation in the `_claim` function:
```java
function _claim(address _token, address _receiver, uint8 _percentage, Exchange _exchange, bytes calldata _data)
        internal
        returns (uint256 claimedAmount)
    {
        //...

        if (_token == ETH) {
              // Users gets more lpETH because of `totalLpETH > totalSupply`
@>            claimedAmount = userStake.mulDiv(totalLpETH, totalSupply);
            balances[msg.sender][_token] = 0;
            lpETH.safeTransfer(_receiver, claimedAmount);
        }

        // ...
}
```
Although users get more than they expected it broke one of the main invariants
which everything comes from a lack of ETH handling
There is another important note. the locking process also emits the `Locked` event for handling the points but due to this bug, it can't handle the points very well.

## Impact
Users are receiving more lpETH than expected due to incorrect handling of ETH within the protocol. This violation undermines the fundamental 1:1 conversion ratio between ETH deposits and lpETH issuance, compromising protocol integrity.

## Proof of Concept
add this test to the `PrelaunchPointsTest.t.sol`:
```java
    function test_MainInvariantBreaksWithETHDeposit() public {
        address alice = makeAddr("alice");

        // `address(this)` could represent some users
        vm.deal(address(this), 90e18);
        prelaunchPoints.lockETH{value: 90e18}("");

        // let the alice lock her ETH
        vm.deal(alice, 10e18);
        vm.prank(ali
Users can bypass locking funds and use claim directly
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L262


# Vulnerability details

## Impact
Users can bypass _processLock and use claim when the start claim date is active. This breaks a core concept in the protocol to lock funds within the period of 120 days at max.


## Proof of Concept
Users will call [_processLock](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L172)  to transfer thier tokens to PrelaunchPoints contract  to be locked, then they have to wait for a period of time until the deposits are paused then after 7 days the claim date is activeted, and only then they can start to claim lpETH.

An attacker can deposit small amount using _processLock let's say 100 WEI amount before the 7 days start, then he will wait for the owner to call `convertAllETH`, after that he will send an amount of X Native ETH let's say 100e18, and call [claim](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L211), the attacker will receive an amount of lpETH while he didn't lock any amount before except for 100 WEI.

Break down the Scenario:
- Bob will deposit 100 WEI of LRT token (e.g stETH).
- Deposit period is done and now the 7 days started with deposits being paused.
- The Owner convert all ETH and now the start claim date is active.
- Pay attention to the comment [// At this point there should not be any ETH in the contract](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L257), it's true since the conversion is done now there no ETH in the contract.
- Bob will send 10e18 ETH to PrelaunchPoints contract address.
- Bob will call claim with 1 WEI amount for stETH Token.
- _claim will pass [if (userStake == 0)](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L245) since Bob balance is 100 WEI.
- The block [else](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L253) will be triggerd since it's ERC20 token.
- _fillQuote will swap 100 WEI to ETH and succeed.
- Now on L262 the [claimedAmount](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L262) will consider `address(this).balance` as the amount for the _receiver (Bob).
- A deposit is done by [lpETH.deposit{value: claimedAmount}(_receiver)](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L263-L264) 
- Bob now receive 10e18 lpETH tokens.

The reason behind this issue, that claimedAmount always consider `address(this).balance` as the amount that the receiver will get.


## Tools Used
Manual Review

## Recommended Mitigation Steps

Return the amount that was swapped and use it instead of
Users can claim more lpETH than locked ETH in case of someone sends ETH to the contract directly
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L179-L182
https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L321-L322


# Vulnerability details

## Impact
The contract is designed to receive ETH so that users can use the function ```lockETH()```
 to get lpETH (1 to 1 conversion). User deposits are stored in the state variable ```totalSupply```. While converting all user deposited ETH to lpETH via a privileged function ```convertAllETH()```, the ETH balance of the current contract is used instead of the state variable ```totalSupply```. In case that some users mistakenly sent ETH directly to the contract the 1 to 1 conversion ratio of ETH to lpETH will be broken leading to users getting more lpETH than they were supposed to.


## Proof of Concept

- Consider all user ETH deposits in the state variable is 1000. 
- Someone mistakenly had sent 10 ETH directly to the contract. 
- ```totalBalance``` variable in the function ```convertAllETH()```  will be 1010 resulting in minting of 1010 lpETH. ```totalLpETH``` state variable will be also 1010.
- While user is claiming lpETH,   the following formula is used
 ```claimedAmount = userStake.mulDiv(totalLpETH, totalSupply);```

In this situation, all users will get 1.01 more lpETH that the ETH they staked. It will be more if the amount of ETH that was mistakenly sent is higher.




## Tools Used

Manual review.

## Recommended Mitigation Steps

Use state variable ```totalSupply``` in the function ```convertAllETH``` instead of "address(this).balance" to calculate the amount to be deposited to lpETH contract.




## Assessed type

Invalid Validation
Inconsistent Emergency Mode Handling in `withdraw` Function
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L290-L303


# Vulnerability details

## Description

The [`withdraw`](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L274-L306) function in the PrelaunchPoints contract has an inconsistency in how it handles emergency mode for different token types. In emergency mode, the function treats ETH withdrawals differently compared to other tokens, such as other LRTs. This inconsistency can lead to unintended behavior and potential issues.

Let's examine the relevant code snippet:

```solidity
function withdraw(address _token) external {
    if (!emergencyMode) {
        // Standard time checks
    } else {
        if (_token == ETH) {
            if (block.timestamp >= startClaimDate) {
                revert UseClaimInstead();
            }
        }
        // No similar check for other tokens
    }
    // Withdrawal logic...
}
```

In the code above, when the contract is in emergency mode, the function checks if the token being withdrawn is ETH. If it is ETH and the current timestamp is greater than or equal to `startClaimDate`, the function reverts with the `UseClaimInstead` error, indicating that the user should use the claim process instead of withdrawing.

However, the function does not apply the same logic to other token types (LRTs). This means that even if the contract is in emergency mode and `startClaimDate` has passed, users can still withdraw wrapped LRTs, without any restrictions.

## Impact

The inconsistent handling of emergency mode in the `withdraw` function can have significant consequences:

- In emergency mode, after `startClaimDate` has passed, users should be directed to use the claim process for all token types. However, the current implementation allows users to withdraw wLRT tokens even when they should be using the claim process. This can lead to unintended token withdrawals and potential loss of control over the token management.

- The inconsistent behavior of the `withdraw` function in emergency mode can lead to confusion and frustration for users. They may expect all token withdrawals to be restricted after `startClaimDate` in emergency mode, but the current implementation allows them to withdraw wLRT tokens, leading to an inconsistent user experience.

## Proof of Concept

Here is the code snippet:
https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L284-L303


Let's consider the following scenario:

1. The `PrelaunchPoints` contract is in emergency mode.
2. The `startClaimDate` has already passed, indicating that all token withdrawals should be handled through the claim process.
3. A user attempts to withdraw rsETH tokens by calling the `withdraw` function with `_token` set to the rsETH token address.
4. The function does not have any specific checks for wLRT tokens i
Wrong `if` statement in the `_validateData` function causes the `claim` function always to revert when claiming tokens that are not ETH
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-loop/blob/main/src/PrelaunchPoints.sol#L439


# Vulnerability details

## Impact
The vulnerability identified in the `_validateData` function of the `PrelaunchPoints` contract can lead to unintended contract behavior. Specifically, the contract will always revert with the `WrongRecipient` error due to a misleading condition in the code. This can prevent expected functionality related to token swaps and recipient validation.

## Proof of Concept
The vulnerability can be found in the `_validateData` function within the `PrelaunchPoints` contract:

```solidity

function _validateData(address _token, uint256 _amount, Exchange _exchange, bytes calldata _data) internal view {
    // Other code omitted for brevity

    if (recipient != address(this) && recipient != address(0)) {    // Incorrect condition
        revert WrongRecipient(recipient);
    }
}


```

The issue arises from the condition `recipient != address(this) && recipient != address(0)`, which expects `recipient` to be neither the contract's address (`address(this)`) nor the zero address (`address(0)`). However, this condition does not align with the expected behavior of the `recipient` variable in the context of token swaps.

Impact of the Vulnerability:

- The condition always evaluates to `true` due to the nature of recipient addresses used in token swap operations.
- As a result, the contract always reverts with the `WrongRecipient` error, regardless of the actual recipient address.

## Tools Used

Manual Review

## Recommended Mitigation Steps
To mitigate this vulnerability, the condition in the `_validateData` function regarding the `recipient` address check should be revised to align with the actual use case of the recipient address in token swap scenarios. Considerations for valid recipient addresses in token swaps should be made to ensure smooth contract functionality without unnecessary reverts.


## Assessed type

Invalid Validation
it is possible to claim `lpETH` anytime after the `startClaimDate` without locking assets from before
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L262


# Vulnerability details

## Impact
A malicious user can lock a minimum amount of an allowed ERC20 token like DAI during the lock time. They can then wait until the claim date begins and send Ether directly to the `PrelaunchPoints` contract. Afterward, they can call the `claim()` or `claimAndStake()` function.

This allows the user to claim `lpETH` based on the amount of Ether they just sent to `PrelaunchPoints`, not the amount they locked before, during the locking time.

The issue lies in the `_claim()` function where the `claimedAmount` is determined by `address(this).balance`, which can be manipulated by the user by sending Ether directly.

## Proof of Concept
The foundry test `testClaimLpEthWihtoutLocking` demonstrates how this bug can be exploited.
### ./test/Bug.t.sol
```solidity  
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/PrelaunchPoints.sol";
import "../src/interfaces/ILpETH.sol";

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../src/mock/MockLpETH.sol";
import "../src/mock/MockLpETHVault.sol";
import {ERC20Token} from "../src/mock/MockERC20.sol";
import {LRToken} from "../src/mock/MockLRT.sol";

import "forge-std/console.sol";

contract Bug is Test {
    PrelaunchPoints public prelaunchPoints;

    ILpETH public lpETH;
    ILpETHVault public lpETHVault;
    ERC20Token public dai;
    bytes32 referral = bytes32(uint256(1));

    address constant EXCHANGE_PROXY = 0xDef1C0ded9bec7F1a1670819833240f027b25EfF;
    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    address public constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address[] public allowedTokens;

    function setUp() public {
        dai = ERC20Token(DAI);

        address[] storage allowedTokens_ = allowedTokens;
        allowedTokens_.push(DAI);

        prelaunchPoints = new PrelaunchPoints(EXCHANGE_PROXY, WETH, allowedTokens_);

        lpETH = new MockLpETH();
        lpETHVault = new MockLpETHVault();
    }

    function testClaimLpEthWihtoutLocking() public {
        address user = makeAddr("maliciousUser");

        // provide user with 1 dai and 1 ether
        uint daiAmount = 1e18;
        vm.deal(user, 1 ether);
        deal(DAI, user, daiAmount, true);

        // user lock 1 dai in prelaunchpoints contract
        vm.startPrank(user);
        dai.approve(address(prelaunchPoints), daiAmount);
        prelaunchPoints.lock(DAI, daiAmount, referral);
        vm.stopPrank();

        // owner set loop addresses
        prelaunchPoints.setLoopAddresses(address(lpETH), address(lpETHVault));

        // 7 days pass
        vm.warp(block.timestamp + prelaunchPoints.TIMELOCK() + 1);

        // owner converts ETHs to lpETHs
        prelaunch