xanoutas/solidity-security-findings huggingface dataset:
[M-02] Once a token is allowed using `PrelaunchPoints::allowToken` function, there is no way to disallow
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
`PrelaunchPoints::allowToken` function is protected by `onlyAuthorized` modifier and once the owner decides to allow/whitelist a certain token, the owner will call this function. If in future some changes are introduced that might break our protocol and the owner decides to disallow a certain token, there is no provision in the codebase to disallow that token. Furthermore, there is no proper mechanism in place for the tokenomics when a certain token is disallowed.

The chances are really high because certain tokens have upgradable implementations for example `weETH` implements `UUPS proxy` and `ezETH` implements `Upgradeable Beacon Proxy`

[Source](https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L364C1-L366C6)

## Proof of Concept
Owner will not be able to disallow a previously allowed token and if any breaking change is introduced in any one of these tokens then it will badly affect this protocol. It might break the protocol or results into some sort of vulnerability using which the protocol could be compromised or exploited.

## Tools Used
Manual review

## Recommended Mitigation Steps
`PrelaunchPoints::allowToken` should be converted to `PrelaunchPoints::toggleAllowToken` or a new function should be created to disallow certain token. Along with this functionality there must be a proper mechanism in place to handle the tokenomics if a token is disallowed.


## Assessed type

Other
The contract lacks of a setter to disallow a token
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Inability to disallow tokens when needed

## Proof of Concept

The contract uses [allowToken](https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L364-L366) function to set the token `true` in `isTokenAllowed` mapping.

```solidity
Contract: PrelaunchPoints.sol

364:     function allowToken(address _token) external onlyAuthorized {
365:         isTokenAllowed[_token] = true;
366:     }
```

Accordingly, if the token to be locked is not in the mapping, it can't be locked.

However, this function is one way and there is no way to toggle the boolean to `false`

For any reasons, once any token is needed to be discluded in this mapping, it won't be possible.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Refactor the function to below;

```diff
Contract: PrelaunchPoints.sol

364:     function allowToken(address _token, bool _include) external onlyAuthorized {
-        isTokenAllowed[_token] = true;
+        isTokenAllowed[_token] = _include;
366:     }
```


## Assessed type

Error
Lack of Token De-listing Functionality
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

### Description

The `PrelaunchPoints` contract initializes the allowed token list within the constructor, enabling a predefined list of ERC20 tokens to be locked within the contract. However, the contract lacks the functionality to remove or disable tokens from the allowed list once they have been added. This could create a scenario where, due to external circumstances such as vulnerabilities discovered in a token's contract or other risk factors, the need arises to disallow further locking of that token without the ability to do so.

The immutability of the allowed tokens list means that the contract does not adhere to the principle of being prepared for future risks or changes in the token's integrity, which might jeopardize the security and integrity of the contract and the funds locked within by the users.

### Example

Suppose an external ERC20 token is discovered to have a critical vulnerability that could be exploited when interacting with contracts such as `PrelaunchPoints`. Since this contract does not have the functionality to remove a token from the list of allowed tokens, users could unknowingly keep locking the vulnerable token, potentially leading to loss of funds or manipulative acts.

### Recommendation

It is recommended to include a function that allows the contract owner to remove tokens from the `isTokenAllowed` mapping. This action should be protected by the `onlyAuthorized` modifier to ensure that only the contract's owner can execute it.

Such a function could take the following form:

```solidity
function disallowToken(address _token) external onlyAuthorized {
    require(isTokenAllowed[_token], "Token is not allowed");
    isTokenAllowed[_token] = false;
    emit TokenDisallowed(_token);
}
```

An event would then need to be declared to log this action:

```solidity
event TokenDisallowed(address indexed token);
```

### Impact of Change

This change allows the contract to have the flexibility to respond to changes in the security and utility of different ERC20 tokens. It provides the contract owner with the necessary tools to mitigate risks related to external changes in token contracts, thus protecting users' interests.


## Assessed type

Other
Tokens cannot be disallowed nnce allowed
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Once a token is allowed, it can never be disallowed again.

## Proof of Concept
The allowToken function only sets isTokenAllowed to true for a token address. There is no way to set it back to false once allowed.

```
    function allowToken(address _token) external onlyAuthorized {
        isTokenAllowed[_token] = true;
    }
```

Tokens should be able to be allowed and disallowed as needed.

## Tools Used

## Recommended Mitigation Steps
Add a disallowToken function:

```
function disallowToken(address _token) external onlyAuthorized {
  isTokenAllowed[_token] = false; 
}

```


## Assessed type

Context
Missing Token Removal Functionality
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The lack of token removal functionality undermines the contract's security and flexibility. It could lead to situations where unauthorized tokens remain permitted

## Proof of Concept
The contract lacks a function to remove or disallow tokens once they have been added using the allowToken function. This omission poses a potential security risk as there is no mechanism to revoke permission for tokens that are no longer desired or trusted.
```
function allowToken(address _token) external onlyAuthorized {
        isTokenAllowed[_token] = true;
    }

```

## Tools Used
Manual code review

## Recommended Mitigation Steps
Add disallowToken function
```
function disallowToken(address _token) external onlyAuthorized {
    isTokenAllowed[_token] = false;
}
```


## Assessed type

Context
Incorrect Parsing Of UniswapV3's Token Path
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
UniswapV3 decodes the parameters for each swap pool in a multi-pool `path` bytes array in the following order: `tokenA,fee,tokenB`. Each token address is encoded by 20 bytes and fee by 3bytes. If there are remaining bytes in the `path`, they will be ignored. For example, if you provide `path` like `tokenA,fee,tokenB,tokenC`, this `path` will be treated as `tokenA,fee,tokenB` in UniswapV3's `SwapRouter`.

In `PrelaunchPoints`, the `_decodeUniswapV3Data` only parses the first and the last token from the `path`:
```solidity
encodedPathLength := calldataload(add(p, 96)) // Get length of encodedPath (obtained through abi.encodePacked)
inputToken := shr(96, calldataload(add(p, 128))) // Shift to the Right with 24 zeroes (12 bytes = 96 bits) to get address
outputToken := shr(96, calldataload(add(p, add(encodedPathLength, 108)))) // Get last address of the hop
``` 
and then these tokens will be checked in `_validateData`:
```solidity
if (_exchange == Exchange.UniswapV3) {
            (inputToken, outputToken, inputTokenAmount, recipient, selector) = _decodeUniswapV3Data(_data);
            if (selector != UNI_SELECTOR) {
                revert WrongSelector(selector);
            }
            // UniswapV3Feature.sellTokenForEthToUniswapV3(encodedPath, sellAmount, minBuyAmount, recipient) requires `encodedPath` to be a Uniswap-encoded path, where the last token is WETH, and sends the NATIVE token to `recipient`
            if (outputToken != address(WETH)) {
                revert WrongDataTokens(inputToken, outputToken);
            }
        } 
```
The attacker can use a malicious path, for example, `USDC,fee,MyToken,WETH`. The decoded last token will be `WETH`, thus the check in `_validateData` will be passed. However, the UniswapV3 will decode this path as `USDC,fee,MyToken` and ignore the `WETH` when executing the swap.

The attacker can create a `USDC-MyToken` pool and swap all his locked USDC to `MyToken` and then remove all liquidity of this pool. As a result, the attacker's USDC will not be converted to the `lpETH`.


## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider using UniswapV3's Path library(https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/Path.sol#L29-L54) to decode the last token:
```solidity
 using Path for bytes;
 ...
 ...
 uint256 pools = path.numPools();
 address last;
 for(uint256 i=0;i<pools;i++){
 (, address tokenOut,) = path.decodeFirstPool();
 last = tokenOut;
 }
 return last;
```





## Assessed type

en/de-code
Permanent token whitelisting in protocol poses exploitation risks
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

In the event that a whitelisted token is found to be vulnerable and has been actively exploited by an attacker in the wild, the protocol needs to mitigate the issue swiftly by removing the vulnerable token from the protocol. However, the mitigation effort will be hindered by the fact that there is no way to remove a token within the protocol once it has been whitelisted. Thus, it might not be possible to stop the attacker from exploiting the vulnerable token. The protocol team would need to find a workaround to block the attack, which will introduce an unnecessary delay to the recovery process where every second counts.

Additionally, if the admin accidentally whitelisted the wrong token, there is no way to remove it.

## Proof of Concept

The Loopfi protocol intends to support different LRT tokens, and the way to add a new supported token to the protocol happens by the admin calling the `allowToken()` function with the address of the new token that should be supported by the protocol.

```solidity
 function allowToken(address _token) external onlyAuthorized {
        isTokenAllowed[_token] = true;
}
```

However, the protocol is missing the feature to remove a token. For example, if one of the supported LRT tokens is hacked and allows attackers to exploit Loopfi as well because it supports it, there is no way to remove this vulnerable token. Another case would be if there is an issue within one of the supported DEXs and the vulnerable token, such as price manipulation. By doing so, attackers could artificially inflate this token's price, and if they have locked this token to the protocol, they can receive much more lpETH tokens against it due to the manipulated price. Eventually stealing from other users and the protocol, as this will cause the lpETH token to be also affected, this can lead to drop of price, liquidation on protocols which supports it as collateral and so on. And If one of these scenarios becomes real, there is no way to remove/block usage of the vulnerable token.

Something similar happened when Luna imploded. Due to the missing failsafe mechanisms in place, this allowed malicious actors to drain two protocols - one for \$13.5M and the second one for \$8.3M respectively due to Chainlink oracle returning the wrong price, and as mentioned, there were no failsafe mechanisms. Link to the exploit: https://rekt.news/venus-blizz-rekt/

## Tools Used

VSCode

## Recommended Mitigation Steps

Consider implementing an additional function to disallow tokens from the whitelist, enabling the swift removal of vulnerable tokens if necessary.

```solidity
 function disallowToken(address _token) external onlyAuthorized {
        isTokenAllowed[_token] = false;
}
```

Additionally, incorporate checks to determine if operations for withdrawing and claiming can proceed with
The `withdraw()` function forces users to claim `lpETH` even in emergency mode
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

With the current implementation of the `withdraw()` function `ETH` is impossible to withdraw after `startClaimDate` in emergency mode. It can cause an issue, as when the Loop protocol chooses to go into emergency mode, the `lpETH` is likely to be illiquid on all DEXes, meaning that unless the issue which caused emergency is resolved, the users essentially lost their money. Not only it heavily impacts the trust to the Loop protocol's owner who can at any time decide to start the emergency mode, but also it incentivizes users to only use LRTs as their stake that can be easily withdrawn in case of such situation.

## Proof of Concept 
1. User stakes ETH 
2. After time passes, the owner starts the claim period
3. For any reason, the owner starts emergency mode
4. The user tries call `withdraw()` but it reverts due to:
```javascript
 if (block.timestamp >= startClaimDate) {
                revert UseClaimInstead();
            }
```
## Tools Used
Manual Review 

## Recommended Mitigation
Instead of converting all ETH to `lpETH` when calling `convertAllETH` and then transferring it to users the contract could use `lpETH.deposit()` function just like for LRTs stake. This way no `lpETH` has to be held in the `PrelaunchPoints` contract making it more encouraging for the users to lock `ETH` rather than only LRTs and it increases the reliability of the protocol.mmended Mitigation Steps


## Assessed type

Token-Transfer
Insufficient Validation of _validateData funcion
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

https://vscode.dev/github/code-423n4/2024-05-loop/blob/main/src/PrelaunchPoints.sol#L414


# Vulnerability details

## Impact
 The `_validateData` function conducts preliminary checks on certain parameters, including the input token, input amount, and the selector. However, it is important to note that this function does not extend its validation to verify the integrity of the swap path or to confirm the accuracy of the amounts received post-swap.

The absence of comprehensive validation measures could potentially expose the contract to security vulnerabilities, particularly in scenarios where the data provided by the 0x API is inaccurate or subject to manipulation.
```
 function _validateData(address _token, uint256 _amount, Exchange _exchange, bytes calldata _data) internal view {
        address inputToken;
        address outputToken;
        uint256 inputTokenAmount;
        address recipient;
        bytes4 selector;

        if (_exchange == Exchange.UniswapV3) {
            (inputToken, outputToken, inputTokenAmount, recipient, selector) = _decodeUniswapV3Data(_data);
            if (selector != UNI_SELECTOR) {
                revert WrongSelector(selector);
            }
            // UniswapV3Feature.sellTokenForEthToUniswapV3(encodedPath, sellAmount, minBuyAmount, recipient) requires `encodedPath` to be a Uniswap-encoded path, where the last token is WETH, and sends the NATIVE token to `recipient`
            if (outputToken != address(WETH)) {
                revert WrongDataTokens(inputToken, outputToken);
            }
        } else if (_exchange == Exchange.TransformERC20) {
            (inputToken, outputToken, inputTokenAmount, selector) = _decodeTransformERC20Data(_data);
            if (selector != TRANSFORM_SELECTOR) {
                revert WrongSelector(selector);
            }
            if (outputToken != ETH) {
                revert WrongDataTokens(inputToken, outputToken);
            }
        } else {
            revert WrongExchange();
        }

        if (inputToken != _token) {
            revert WrongDataTokens(inputToken, outputToken);
        }
        if (inputTokenAmount != _amount) {
            revert WrongDataAmount(inputTokenAmount);
        }
        if (recipient != address(this) && recipient != address(0)) {
            revert WrongRecipient(recipient);
        }
    }

```

However, it is important to note that this function does not extend its validation to verify the integrity of the swap path or to confirm the accuracy of the amounts received post-swap.


## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used
Manual
## Recommended Mitigation Steps
Validate the swap path to ensure it only includes trusted tokens and exchanges.
Verify the amounts received post-swap match the expected minimums or estimates.Introduce a `minBuyAmount` parameter to the `claim` and `clai
Any user can bypass the conversion of LRTs to lpETH during the claim process by deploying custom pools on Uniswap
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users can skip the conversion of their staked in `PrelaunchPoints` LRTs without losing points by deploying a mock ERC20 token and two pools on Uniswap, which will negatively affect the main purpose of `PrelaunchPoints` - commitment to the conversion. Bypassing the conversion is intended to only be allowed in `withdraw()`.

There's a mention of a *similar* issue in the Publicly Known Issues section:

> Crafting malicious calldata so that users get less funds as expected when claiming lpETH on LRT deposits (e.g. by setting big slippage/price impact)

But it does not apply here because its purpose is to invalidate user mistakes, social engineering attacks, and standard swap frontrunning (which are invalid anyway by the C4 rules), and the current report is about a malicious calldata that benefits the user.

## Proof of Concept
During the claiming process, users are allowed to use their own calldata for the 0x call with some restrictions (see `_validateData()`). They can also choose the function that will execute the calldata: `transformERC20()` or `sellTokenForEthToUniswapV3()`.

In both cases, there's no validation for any intermediary swaps or slippage, which allows users to use self-created pools to extract their deposit through the slippage and skip the conversion to lpETH.

Example with `sellTokenForEthToUniswapV3()`:

https://github.com/0xProject/protocol/blob/e66307ba319e8c3e2a456767403298b576abc85e/contracts/zero-ex/contracts/src/features/UniswapV3Feature.sol#L107-L126

```solidity
    function sellTokenForEthToUniswapV3(
        bytes memory encodedPath,
        uint256 sellAmount,
        uint256 minBuyAmount,
        address payable recipient
    ) public override returns (uint256 buyAmount) {
        buyAmount = _swap(
            encodedPath,
            sellAmount,
            minBuyAmount,
            msg.sender,
            address(this) // we are recipient because we need to unwrap WETH
        );
        WETH.withdraw(buyAmount);
        // Transfer ETH to recipient.
        (bool success, bytes memory revertData) = _normalizeRecipient(recipient).call{value: buyAmount}("");
        if (!success) {
            revertData.rrevert();
        }
    }
```

1. Deploy a mock ERC20 token (`MOCK`)
2. Deploy two pools on Uniswap:
      1. LRT/MOCK with a larger amount of LRT and some negligible amount of MOCK (e.g., 10000000:1)
      2. MOCK/WETH with a larger amount of MOCK and some negligible amount of WETH
3. Craft a calldata with the `encodedPath` containing the required LRT as the input token, MOCK as an intermediary token, WETH as the output token, and `minBuyAmount` set to 0.
4. Call `claim()`. 0x will swap the LRT to MOCK through the first pool with the slippage about 99%, resulting in some small amount of MOCK; then it'll swap MOCK to WETH through the second pool,