xanoutas/solidity-security-findings huggingface dataset:
Users can claim as much lpETH as they want as long as they have LRT token deposited
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
* The total lpETH claimable through the PrelaunchPoints contract should be capped to the amount of ETH and ETH value of LRTs deposited up until the claims start but this allows users to bypass that limit.
* Allows users to easily arbitrage lpETH on exchanges either using their own funds or a flashloan.
* Defeats the purpose of having ETH deposits in the first place.

## Proof of Concept
### Root Cause
```solidity
    function _claim(address _token, address _receiver, uint8 _percentage, Exchange _exchange, bytes calldata _data)
        internal
        returns (uint256 claimedAmount)
    {
        uint256 userStake = balances[msg.sender][_token];
        if (userStake == 0) {
            revert NothingToClaim();
        }
        if (_token == ETH) {
            claimedAmount = userStake.mulDiv(totalLpETH, totalSupply);
            balances[msg.sender][_token] = 0;
            lpETH.safeTransfer(_receiver, claimedAmount);
        } else { <===== audit
            uint256 userClaim = userStake * _percentage / 100; // <===== audit
            _validateData(_token, userClaim, _exchange, _data);
            balances[msg.sender][_token] = userStake - userClaim; <===== audit

            // At this point there should not be any ETH in the contract
            // Swap token to ETH
            _fillQuote(IERC20(_token), userClaim, _data); <===== audit

            // Convert swapped ETH to lpETH (1 to 1 conversion)
            claimedAmount = address(this).balance; // <===== audit
            lpETH.deposit{value: claimedAmount}(_receiver);
        }
        emit Claimed(msg.sender, _token, claimedAmount);
    }

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

* When claiming lpETH against an LRT deposit, the contract swaps the LRT to ETH then deposits the entire ETH held by the contract instead of the amount received from the swap to the lpETH contract.
* Percentage is a user input and can be set to 0 or a low value based on the deposited amount to make the `userClaim` amount 0 or very low.
* 0x UniswapV3 swap does not fail in case of a 0 input amount.
* Users can send ETH directly to the contract.

### Test
```solidity
// ...
contract PrelaunchPointsTest is Test {
    // ...
    function testClaimLRT() public {
        address ezETH = 0xbf5495Efe5DB9ce00f80364C8B423567e58d2110;
        address bob = vm.addr(1);
        uint256 amount = 1 wei;

        deal(ezETH, bob, amount);
        vm.deal(bob, 1000 ether); // <==== This can be a flashloan

        prelaunchPoints.allowToken(ezETH);

        vm.startPrank(bob);
        ERC20Token(ezETH).approve(address(prelaunchPoints), amount);
        prelaunchPoints.lock(ezETH, amount, referral);
        vm.stopPrank();

        prelaunchPoints.setLoopAddress
Report on Incorrect Assumption in Contract Functionality giving users more LPETH THAN THEIR SWAPPED AMOUNT
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## Impact
The assumption made by the developer in line 257 of the contract is flawed and could lead to users getting more lpeth token than they should. This assumption incorrectly assumes that no ETH will be present in the contract at a certain point, which may not hold true as ETH can be forwarded to the contract at any time, even after this function(convertAllETH) is called 
```solidity
/**
     * @dev Called by a owner to convert all the locked ETH to get lpETH
     */
    function convertAllETH() external onlyAuthorized onlyBeforeDate(startClaimDate) .
```

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

 Consequently, users could benefit from receiving more ETH than the actual swapped amount, leading to potential exploitation of donated ETH.

## Proof of Concept
The flawed assumption is evident in the following code snippet:

```solidity
   function _claim(address _token, address _receiver, uint8 _percentage, Exchange _exchange, bytes calldata _data)
        internal
        returns (uint256 claimedAmount)
    {
        uint256 userStake = balances[msg.sender][_token];
        if (userStake == 0) {
            revert NothingToClaim();
        }
        if (_token == ETH) {
            claimedAmount = userStake.mulDiv(totalLpETH, totalSupply);
            balances[msg.sender][_token] = 0;
            lpETH.safeTransfer(_receiver, claimedAmount);
        } else {
            uint256 userClaim = userStake * _percentage / 100;
            _validateData(_token, userClaim, _exchange, _data);
            balances[msg.sender][_token] = userStake - userClaim;

            // At this point there should not be any ETH in the contract
            // Swap token to ETH
            _fillQuote(IERC20(_token), userClaim, _data);

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

This code forwards all ETH present in the contract to the user and converts it to LPETH in a 1:1 ratio, without considering the actual value that was swapped.

```solidity
   _fillQuote(IERC20(_token), userClaim, _data);
```

 This can result in users receiving more ETH than they are entitled to, as all available ETH in the contract is converted to LPETH for the user.

## Tools Used
Manual code analysis 

## Recommended Mitigation Steps
To address the flawed assumption and ensure users receive the correct amount of ETH based on the actual value that was swapped, the following mitigation step
Race condition on `PrelaunchPoints::_claim()` allow malicious actor deposit all other user `ETH` to `lpETHVault` to get `lpETH` more than they should be
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

User loses all `ETH` balance and cannot claim `lpETH`

## Proof of Concept

[PrelaunchPoints::_claim()](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L240-L266) has a function for users to claim `lpETH` based on the amount of `ETH` or other `LRT's` tokens that are locked. When a user only claims `lpETH` using the `ETH` they own, this does not become an issue but the issue lies when the user wants to claim `lpETH` using `LRT's` which have previously been locked.

```solidity
        } else {
            uint256 userClaim = userStake * _percentage / 100;
            _validateData(_token, userClaim, _exchange, _data);
            balances[msg.sender][_token] = userStake - userClaim;

            // At this point there should not be any ETH in the contract
            // Swap token to ETH
            _fillQuote(IERC20(_token), userClaim, _data);

            // Convert swapped ETH to lpETH (1 to 1 conversion)
            claimedAmount = address(this).balance;
            lpETH.deposit{value: claimedAmount}(_receiver);
        }
```

When a user wants to claim `lpETH` with the `LRT's` she/he has then the steps are:

1. User calls the claim function and enters the `LRT's` address
2. Users can also set how many `LRT's` will be used to claim `lpETH` by entering a percentage value
3. After all variables have been entered and are valid, there are two main steps that must be taken
4. The protocol validates the data that the user enters with the [_validateData()](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L405-L442) function
5. After the data is validated, the `LRT's` token will be swapped for `ETH` according to the percentage entered by the user using the [_fillQuote()](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L491-L505) function
6. `ETH` proceeds from swapping `LRT's` tokens will be sent to the `PrelaunchPoints` contract
7. To claim `lpETH`, then `ETH` is deposited with the `lpETH.deposit` function and the user gets `lpETH` according to the amount of the `ETH` balance in `PrelaunchPoints`

In Ethereum, all transactions appear on the network before being accepted. Users can see upcoming token claim. As a result, an attacker can front-run large token claim.

### Exploit Scenario

1. Alice has 100 LRT token and she call claim function to get `lpETH`
2. Bob monitors transactions in the `PrelaunchPoints` contract and waits for the swap (`LRT` to `ETH`) to be successful
3. Then Bob calls the claim function with as few `LRT` amount as possible (i.e 1 `LRT`) and with higher gas price, Bob's transaction will be executed first
4. Alice transactions will revert because the ETH balance in
User can game rewarding system siphoning rewards for themselves at a huge margin to other stakers when it shouldn't be possible
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
A user can lock up a very small amount of `LRT` e.g 0.5 `rsETH` while earning 10x+ more staking rewards compared to another user who locked up 10 `rsETH` effectively taking most of the staking reward pool from other honest stakers who are unaware of the exploit path.

## Proof of Concept
- User A locks 10 `rsETH`
- User B locks 0.5 `rsETH`
- The protocol activates `lpETH` claims
- Claiming of `lpETH` is set
- User A redeems then stakes his 10 `rsETH` for 9.99 `lpETH` (assuming he swapped at a favorable price)
- User B slowly claims 0.05 `rsETH` while ending up staking 10.05 `lpETH` each time which shouldn't be because they only locked 0.5 `rsETH`. User B can do this 10+ times since 0.05 is only 10% of his 0.5 `rsETH` deposit which means they get to redeem and stake 100.5 `lpETH` altogether when they initially only locked 0.5 `rsETH`.
- Keep in mind User A had no idea of this backdoor, so all they could stake was up to 9.99 `lpETH` and no more. With that being the case, User B now keeps 90+% of the reward pool to themself.
- This attack is possible because of the way the contract handles its ether balance using `address(this).balance` which user B leverages as a backdoor entry to successfully put in a direct ether transfer into the contract before executing the `claimAndStake()` for `lpETH` staking which then associates the ether balance in the contract to them allowing them to stake more `lpETH` even though their initial locked `rsETH` was very little ~ 0.5 `rsETH`. This can then be automated to slowly siphon rewards from other users by calling `claimAndStake()`. This is possible because of the `LRT` claim process which differs from the `ETH` and `WETH` one.

Summarization is to transfer ether into the contract directly slightly before or while executing `claimAndStake()` for your `LRT` each call which will allow you to redeem then stake more `lpETH` than it should since lock-up periods are long past.

Paste the POC test into the `PreLaunchPoints.t.sol` test file and run the code with `forge test --mt testSiphonStakeRewardsPOC`

```solidity
function testSiphonStakeRewardsPOC() public {
       address alice = makeAddr("alice");
       address bob = makeAddr("bob");

        lrt.mint(bob, 10 ether);
        lrt.mint(alice, 0.5 ether);

        vm.prank(bob);
        lrt.approve(address(prelaunchPoints), 10 ether); 
        vm.prank(alice);
        lrt.approve(address(prelaunchPoints), 0.5 ether); 

        vm.prank(bob);
        prelaunchPoints.lock(address(lrt), 10 ether, referral);
        vm.prank(alice);
        prelaunchPoints.lock(address(lrt), 0.5 ether, referral);

        // Set Loop Contracts and Convert to lpETH
        prelaunchPoints.se
Staking users can emit any valued Claimed and StakedVault events, which may disrupt point calculation.
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 send ETH to the contract before calling the claim function or claimAndStake, causing them to be converted to lqETH through the contract, triggering incorrect Claimed and claimAndStake events, which may allow them to earn more points.

## Proof of Concept
The reasons for this issue are:

1. The _claim function converts all ETH in the contract to lqETH.
2. Users can claim tokens other than ETH multiple times.
github:[https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L262](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L262)
```solidity
            claimedAmount = address(this).balance;
            lpETH.deposit{value: claimedAmount}(_receiver);
```

Due to the lack of visibility into the specific process of point calculation, the POC can only be reflected in the anomalies of events.

POC:
1. Bob locked 1 token (other than ETH and WETH).
2. During the claim process, Bob twice sent a large amount of ETH (5e18) to the contract before calling the claimAndStake function.
3. Upon inspecting the events, it was discovered that both claimed events had a claimedAmount of 5e18.
```solidity
    function testContralEvent() public {
        ERC20 DAI = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
        prelaunchPoints.allowToken(address(DAI));

        address Bob = makeAddr("Bob");

        uint lockAmount = 100;
        vm.deal(Bob, 10e18);
        deal(address(DAI), Bob, 1);

        vm.startPrank(Bob);
        DAI.approve(address(prelaunchPoints), 1);
        prelaunchPoints.lock(address(DAI), 1, referral);
        assertEq(prelaunchPoints.balances(Bob, address(DAI)), 1);
        vm.warp(1);
        vm.stopPrank();

        prelaunchPoints.setLoopAddresses(address(lpETH), address(lpETHVault));
        vm.warp(
            prelaunchPoints.loopActivation() + prelaunchPoints.TIMELOCK() + 1
        );
        prelaunchPoints.convertAllETH();

        vm.warp(prelaunchPoints.startClaimDate() + 1);

        vm.startPrank(Bob);
        for (uint i = 0; i < 2; i++) {
            bytes memory path = abi.encodePacked(address(DAI), WETH);
            bytes memory data = abi.encodeWithSelector(
                prelaunchPoints.UNI_SELECTOR(),
                path,
                0,
                0,
                address(prelaunchPoints)
            );
            address(prelaunchPoints).call{value: 5e18}("");
            prelaunchPoints.claimAndStake(
                address(DAI),
                0,
                PrelaunchPoints.Exchange.UniswapV3,
                data
            );
        }
        vm.stopPrank();
    }
```
## Tools Used
Manual audit,foundry
## Recommended Mitigation Steps
Use the balance obtained from the swap as the input for th
Users can receive more lpETH than their locked ETH allows them
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Users can lock less ERC20 and receive more lpETH, by sending ETH directly to the contract before claiming.

## Proof of Concept
In the _fillQuote function the boughtETHAmount is calculated. 
https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L503
However in the claim function the amount that is actually deposited and given to the user is the balance of the contract:
https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L262
This is because of the wrong assumption that the contract will not have any ETH balance after the convertAllETH function is called. 
Consider the following scenario:
1. A user locks only a very small amount of a supported LRT.
2. The convertAllETH function is called and now claims are unlocked.
3. The user decides that they want more lpETH. They send ETH directly to the contract right before calling claim. Because of the wrong assumption the user will actually get more lpETH with less locked tokens, which compromises the whole purpose of the locking.

## Tools Used
Manual Review
## Recommended Mitigation Steps
The `_fillQuote` function should return the boughtETHAmount and the `claimedAmount` here:
https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L263
should be replaced with the boughtETHAmount.


## Assessed type

ETH-Transfer
An attacker can swap all ETH in the contract to lpETH
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-05-loop/blob/main/src/PrelaunchPoints.sol#L259-L263
https://github.com/code-423n4/2024-05-loop/blob/main/src/PrelaunchPoints.sol#L491-L505


# Vulnerability details

## Impact
In the _claim function, due to the incorrect calculation of claimedAmount, all ETH in the contract will be swapped by the attacker to lpETH.
## Proof of Concept
https://github.com/code-423n4/2024-05-loop/blob/main/src/PrelaunchPoints.sol#L259-L263
```sol
            _fillQuote(IERC20(_token), userClaim, _data);

            // Convert swapped ETH to lpETH (1 to 1 conversion)
            claimedAmount = address(this).balance;
            lpETH.deposit{value: claimedAmount}(_receiver);
```
In this code, the amount of claimAmount is all the ETH in the current contract (including the ETH locked by all users). The correct logic should be the amount after converting ERC20 to ETH - the previous amount of ETH.

In the _fillQuote function, budedETHAmount should be returned, which represents the difference between before and after eth, which is the ETH actually redeemed by the user. The actual value of ClaimedAmount should be equal to buyETHAmount, not address(this).balance.

### Use an example to prove: (18 decimals)
User A locked 1ETH, address(this).balance is equal to 1eth
User B locked 1ETH, address(this).balance is equal to 2eth
User C locked 1WETH, address(this).balance is equal to 2eth

User C (attacker) calls prelaunchPoints.claim(WETH, 100, PrelaunchPoints.Exchange.TransformERC20, data);. At this time, the attacker has exchanged 1WETH for 1eth, address(this).balance is equal to 3eth, and claimedAmount is 3eth. , eventually the attacker will get all lpETH.
```sol
            claimedAmount = address(this).balance;
            lpETH.deposit{value: claimedAmount}(_receiver);
```
https://docs.loopfi.xyz/the-launch/the-points-program#what-happens-after-the-points-program-has-concluded

According to the document requirements, the exchange ratio between user locked tokens and lpETH is 1:1, but an attacker can get all lpETH, which I think is a high risk.
## Tools Used
Manual review
## Recommended Mitigation Steps
1. Add the return value in the `_fillQuote` function, returns (uint256 boughtAmount)
```sol
  function _fillQuote(IERC20 _sellToken, uint256 _amount, bytes calldata _swapCallData) internal returns (uint256 boughtAmount) {

```
2. Return the amount of boughtAmount at the end of the `_fillQuote` function
```sol
  boughtETHAmount = address(this).balance - boughtETHAmount;
  return boughtETHAmount;
```
3. Fix claimedAmount in `_claim` function
```sol
  claimedAmount = _fillQuote(IERC20(_token), userClaim, _data);
```











## Assessed type

Math
Direct ETH transfer to the protocol will break one of its main invariants
Audit: 2024-05-loop-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

One of the main invariants of the protocol states, "Users that deposit ETH/WETH get the correct amount of lpETH on claim (1 to 1 conversion)." However there is a critial vulnerability and if it is exploited, it would break this invariant, resulting in users not receiving lpETH at the 1:1 peg. Consequently, operations dependent on this invariant, such as determining the rewards from staking lpETH could be affected, calculating the lpETH price which in turn will affect integrating Loopfi with other protocols, because other protocols that accept lpETH as collateral might experience liquidation events and more. This will significantly impair the usability of the protocol.

## Proof of Concept

This vulnerability arises due to the susceptibility of the initial ETH amount that should be deposited to the lpETH contract to manipulation, primarily through the risk of direct ETH transfer by malicious actors to the protocol. Let's examine how this issue can be exploited:

In order to convert all the ETH initially locked by users to lpETH, the contract owner should invoke the `convertAllETH()` function. The balance that should be deposited to lpETH contract is calculated by retrieving the `PrelaunchPoints` contract's balance, which should represent the total ETH deposited by users:

```solidity
    function convertAllETH() external onlyAuthorized onlyBeforeDate(startClaimDate) {
        ...
        // deposits all the ETH to lpETH contract. Receives lpETH back
        uint256 totalBalance = address(this).balance;
        lpETH.deposit{value: totalBalance}(address(this));
        totalLpETH = lpETH.balanceOf(address(this));
        ...
    }

```

However, because this contract can accept direct ETH transfers through its `receive()` function, the calculation of the lpETH deposit amount can be disrupted. A malicious actor can front run the owner before he call `convertAllETH()` and send ETH to the contract, breaking this invariant.

This results in `totalLpETH` being higher than it should be, leading to incorrect calculations wherever it is used. For example, in the `_claim()` function, the amount a user is entitled to claim should be calculated by `(user ETH deposit amount * totalLpETH) / totalSupply`. However, due to this vulnerability, `totalLpETH` will be much higher than `totalSupply`, leading to incorrect calculations of the lpETH user's claim and consequently other issues such as incorrect staking reward amounts. For example in the following function `claimedAmount` will be much higher than it should be

```solidity
   function _claim(address _token, address _receiver, uint8 _percentage, Exchange _exchange, bytes calldata _data)
        internal
        returns (uint256 claimedAmount)
    {
        uint256 userStake = balances[msg.sender][_token];
        if (userStake == 0) {