xanoutas/solidity-security-findings huggingface dataset:
`claim()` and `claimAndStake()` Will Not Work with UniswapV3 as the Exchange Parameter
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Bug Description
Users can call `claim()` to receive their vested lpETH, or `claimAndStake()` to additionally stake them in a Loop vault for extra rewards.

[PrelaunchPoints.sol#L204-L235](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L204-L235)
```solidity
    /**
     * @dev Called by a user to get their vested lpETH
     * @param _token      Address of the token to convert to lpETH
     * @param _percentage Proportion in % of tokens to withdraw. NOT useful for ETH
     * @param _exchange   Exchange identifier where the swap takes place
     * @param _data       Swap data obtained from 0x API
     */
    function claim(address _token, uint8 _percentage, Exchange _exchange, bytes calldata _data)
        external
        onlyAfterDate(startClaimDate)
    {
        _claim(_token, msg.sender, _percentage, _exchange, _data);
    }

    /**
     * @dev Called by a user to get their vested lpETH and stake them in a
     *      Loop vault for extra rewards
     * @param _token      Address of the token to convert to lpETH
     * @param _percentage Proportion in % of tokens to withdraw. NOT useful for ETH
     * @param _exchange   Exchange identifier where the swap takes place
     * @param _data       Swap data obtained from 0x API
     */
    function claimAndStake(address _token, uint8 _percentage, Exchange _exchange, bytes calldata _data)
        external
        onlyAfterDate(startClaimDate)
    {
        uint256 claimedAmount = _claim(_token, address(this), _percentage, _exchange, _data);
        lpETH.approve(address(lpETHVault), claimedAmount);
        lpETHVault.stake(claimedAmount, msg.sender);

        emit StakedVault(msg.sender, claimedAmount);
    }
```

Users can call these functions to convert their deposited LRTs into ETH, which is then deposited into the lpETH contract to receive vested lpETH tokens.

When users choose to convert their deposited LRT tokens to lpETH, they can opt to swap through either `UniswapV3` or `TransformERC20`.

[PrelaunchPoints.sol#L37-L40](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L37-L40)
```solidity
    enum Exchange {
        UniswapV3,
        TransformERC20
    }
```

However, choosing to swap through `UniswapV3` will always fail because the data obtained from the 0x API always returns the `TRANSFORM_SELECTOR = 0x415565b0`, which leads to a revert when this check fails:

[PrelaunchPoints.sol#L414C17-L414C17](https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L414C17-L414C17)
```solidity
    /**
     * @notice Validates the data
`claim` and `claimAndStake` will always revert when 0x API uses Uniswap_V3
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

#### Severity **Medium** (Likelihood: High Impact: Low)

## Description
`claim / claimAndStake` doesn't seem to be working as expected. When non-ETH are claimed and Uniswap_V3 is used by 0x API, `_decodeUniswapV3Data` will be used to decode the 0x response, but it doesn't decode properly and will `always revert` which seems to warrant Medium severity (but border line, as change in the loop's back-end could resolve the issue without having to change the smart contract - see recommendation section).

## Impact
`claim / claimAndStake` function call `will revert` when 0x API uses `Uniswap_V3` todo the swap from LRT token to ETH.

## PoC
Use the following in Remix, which is the decoding code used by the smart contract.

### 1. Create a smart contract in Remix with the following code
```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;

contract LoopTest {

    /**
     * @notice Decodes the data sent from 0x API when UniswapV3 is used
     * @param _data      swap data from 0x API
     */
    function _decodeUniswapV3Data(bytes calldata _data)
        public
        pure
        returns (address inputToken, address outputToken, uint256 inputTokenAmount, address recipient, bytes4 selector)
    {
        uint256 encodedPathLength;
        assembly {
            let p := _data.offset
            selector := calldataload(p)
            p := add(p, 36) // Data: selector 4 + lenght data 32
            inputTokenAmount := calldataload(p)
            recipient := calldataload(add(p, 64))
            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
        }
    }

    /**
     * @notice Decodes the data sent from 0x API when other exchanges are used via 0x TransformERC20 function
     * @param _data      swap data from 0x API
     */
    function _decodeTransformERC20Data(bytes calldata _data)
        public
        pure
        returns (address inputToken, address outputToken, uint256 inputTokenAmount, bytes4 selector)
    {
        assembly {
            let p := _data.offset
            selector := calldataload(p)
            inputToken := calldataload(add(p, 4)) // Read slot, selector 4 bytes
            outputToken := calldataload(add(p, 36)) // Read slot
            inputTokenAmount := calldataload(add(p, 68)) // Read slot
        }
    }

}
```

### 2. Hit the 0x API to simulate a swap of 1 ether from pufETH to ETH.
curl --location --request GET 'https://api.0x.org/swap/v1/quote?buyToken=0xeeeeeeeeeeeeeeeeeeeeeeeeeee
Lenders cannot claim their tokens in some cases
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If a lender for example deposit 100 rswETH in prelaunchPoints contract when they want to claim their tokens actually frontend part calls [https://api.0x.org/swap/v1/quote?buyToken=0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&sellAmount=100&sellToken=0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0&includedSources=uniswap_v3](https://api.0x.org/swap/v1/quote?buyToken=0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee&sellAmount=100&sellToken=0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0&includedSources=uniswap_v3) to generate calldata but 0x protocol always doesn't return `0x803ba26d` and `0x415565b0` selectors even if frontend forces 0x to just include uniswap_v3 and this causes some lenders cannot claim their tokens

## Proof of Concept
Please add this test to PrelaunchPoints.t.sol contract and run that with this command
`forge test --match-test test_lockrswETHAndClaim -vvv --evm-version shanghai`
```solidity
        function test_lockrswETHAndClaim() external {
        address rswWhale = 0x22162DbBa43fE0477cdC5234E248264eC7C6EA7c;
        vm.startPrank(rswWhale);
        uint pre_lock_bal = rswETH.balanceOf(rswWhale);
        uint256 lockAmount = 100;
        rswETH.approve(address(prelaunchPoints), lockAmount);
        prelaunchPoints.lock(address(rswETH), lockAmount, referral);
        assertEq(rswETH.balanceOf(rswWhale), pre_lock_bal - lockAmount);
        vm.stopPrank();
        assertEq(prelaunchPoints.totalSupply(), 0);
        prelaunchPoints.setLoopAddresses(address(lpETH), address(lpETHVault));
        vm.warp(prelaunchPoints.loopActivation() + prelaunchPoints.TIMELOCK() + 1 days);
        prelaunchPoints.convertAllETH();

        vm.warp(prelaunchPoints.startClaimDate()  + 1 days);
        
//data response from 0x endpoint
        bytes memory data = hex"d9627aa400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000005800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000fae103dc9cf190ed75350761e95403b7b8afa6c0000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee869584cd0000000000000000000000001000000000000000000000000000000000000011000000000000000000000000000000007e2993a485fcee9d031e4a30ebc0dff6";

        vm.prank(rswWhale);
        vm.expectRevert();
        prelaunchPoints.claim(address(rswETH), 100, PrelaunchPoints.Exchange.UniswapV3, data);

    }
```

## Tools Used
Foundry,Manually

## Recommended Mitigation Steps


## Assessed type

Error
Consider adding disallowToken instead of only allowToken function
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The absence of a disallowToken function in the "PrelaunchPoints" smart contract could potentially lead to various risks and limitations:

Inability to remove unsupported or problematic tokens from the contract's list of supported tokens.

## Proof of Concept

```solidity
/**
 * @notice Disallows a token from being used within the contract
 * @param _token The address of the token to disallow
 * @dev Only the contract owner can call this function
 */
function disallowToken(address _token) external onlyAuthorized {
    isTokenAllowed[_token] = false;
}
```

## Tools Used
Visual Studio Code

## Recommended Mitigation Steps

Design the disallowToken function to allow the contract owner to remove specific tokens from the list of supported tokens.


## Assessed type

Context
Protocol Cannot Disallow Unwanted or Blacklist Tokens After it has been Allowed
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Protocol Cannot Disallow Unwanted or Blacklist Tokens After it has been Allowed, which will open innocent users to risk of fund loss from interacting with bad tokens
## Proof of Concept
As provided in the allowToken(...) function code below from the PrelaunchPoints contract, Protocol only implemented functionality to allow tokens. However in a situation an added token turnout to be a blacklisted token or broken token which could cause fund lose to user, Protocol would not be able to remove it as it becomes permanent
```solidity
    function allowToken(address _token) external onlyAuthorized {
>>>        isTokenAllowed[_token] = true;
    }
```
## Tools Used
Manual Review
## Recommended Mitigation Steps
As provided below Protocol should add a disallow function to ensure unauthorized tokens are not allowed to affect Protocol Integrity
```solidity
+++    function disAllowToken(address _token) external onlyAuthorized {
+++        isTokenAllowed[_token] = false;
+++    }
```


## Assessed type

Access Control
Lack of Functionality to Disallow Tokens from the allowed list
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Currently, the protocol allows the addition of tokens to the list of allowed tokens dynamically but lacks a mechanism to disallow tokens once they have been added. This poses several risks and limitations:

Allowing tokens dynamically increases the risk of malicious or incompatible tokens being added to the protocol, potentially leading to security vulnerabilities or protocol disruptions.

Tokens that are incompatible with the protocol or have faulty contracts may disrupt the operation of the protocol if not properly managed.

Users' assets may be at risk if a token with a faulty contract or malicious behavior is allowed within the protocol. Without the ability to remove such tokens, users may face potential loss or exploitation.

## Proof of Concept
At the moment tokens can be added to this contract during deployment in the constructor of using the `allowToken` function.

However, the contract currently lacks a function to disallow tokens. The `allowToken` function allows tokens to be added dynamically, while no corresponding function exists to remove them. Additionally, the constructor initializes the list of allowed tokens but does not provide a way to modify or remove them.

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

constructor(address _exchangeProxy, address _wethAddress, address[] memory _allowedTokens) {
    ...
    for (uint256 i = 0; i < _allowedTokens.length; i++) {
        isTokenAllowed[_allowedTokens[i]] = true;
    }
}

```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement a function to disallow tokens, allowing the owner or authorized users to remove tokens from the list of allowed tokens. This would mitigate risks, enhance protocol stability, and protect users' assets.

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


## Assessed type

Context
`recoverERC20` token  Failure if Owner is Blacklisted
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The current implementation of the `recoverERC20` function allows the owner to recover ERC20 tokens mistakenly sent to the contract. However, if the owner address is blacklisted, this function will always fail as it tries to transfer to token direct to the `owner`. As a result, any blacklisted tokens sent to the contract will be locked forever. making Blacklisted tokens, such as USDC, sent to the contract be irretrievable and locked forever.
The inability to recover blacklisted tokens may lead to a loss of funds

## Proof of Concept
based on the current implementation of the recoverERC20 function as below

```javascript

    /**
     * @dev Allows the owner to recover other ERC20s mistakingly sent to this contract
     */
    function recoverERC20(address tokenAddress, uint256 tokenAmount) external onlyAuthorized {
        if (tokenAddress == address(lpETH) || isTokenAllowed[tokenAddress]) {
            revert NotValidToken();
        }
        IERC20(tokenAddress).safeTransfer(owner, tokenAmount);

        emit Recovered(tokenAddress, tokenAmount);
    }
```

Any attempt to recover USDC from the protocol will always revert if the owner address is blacklisted which will lead to loss of funds

## Tools Used
Manual Review

## Recommended Mitigation Steps
To address this issue, we recommend modifying the `recoverERC20` function to accept an additional parameter `_to`, which specifies the destination address for the recovered tokens. This modification allows the contract owner to specify any address, regardless of whether it's the owner's address or not, as the destination for recovered tokens.

```javascript
/**
 * @notice Allows the owner to recover other ERC20s mistakenly sent to this contract
 * @dev Restricted to be called by the owner only.
 * @param _tokenAddress Address of the ERC20 token
 * @param _tokenAmount Amount of ERC20 token to recover
 * @param _to Destination address to send recovered tokens
 */
function recoverERC20(address _tokenAddress, uint256 _tokenAmount, address _to) external onlyAuthorized {
    if (_tokenAddress == address(lpETH) || isTokenAllowed[_tokenAddress]) {
        revert NotValidToken();
    }
    IERC20(_tokenAddress).safeTransfer(_to, _tokenAmount);

    emit Recovered(_tokenAddress, _tokenAmount);
}
```


## Assessed type

DoS
Permanent Whitelisting in PrelaunchPoints Contract Poses Security Risks
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
The inability to set `isTokenAllowed` to false means that once a token is whitelisted, it cannot be removed. This presents several risks e.g If a whitelisted token is found to have vulnerabilities or is compromised, it cannot be removed, potentially endangering the assets locked in the contract and it cannot respond to issues with whitelisted tokens, such as hacks.

## Proof of Concept
The contract lacks a function to update the isTokenAllowed mapping to false. The only related function, allowToken, can set a token's status to true but not the opposite.nshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used
Manual

## Recommended Mitigation Steps
```diff
+       function allowToken(address _token, bool _allowed) external onlyAuthorized {
+        isTokenAllowed[_token] = _allowed;
    }

```
This way function `allowToken` can be used to both white-list and delist tokens. Note: this is only suggested because its said admin is trusted. As this could be used to withdraw funds from the contract by admin via  function recoverERC20.


## Assessed type

Other
Tokens cannot be removed from Allowedlist once added
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
When a token is added to an allowed list, there should be a disallow function to be able to remove the token once the token does not meet regulations anymore or owner does not want it in the allowed list anymore.
 
In this protocol once the token is allowed, there is no way to disallow token 

## Proof of Concept
This is the function responsible for adding tokens to the allowed list
```solidity
    function allowToken(address _token) external onlyAuthorized {
        isTokenAllowed[_token] = true;
    }
```

once the address of the token is passed, owner does not have any option to change the value to false.

## Tools Used
manual analysis with vs code

## Recommended Mitigation Steps
You can add a disallow function to remove tokens from the allowed list
like this;

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



## Assessed type

Other
Lack of method for removing allowedTokens can brick the system
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The smart contract has a method to allow tokens to be supported by the platform 
but lacks a function to dis-allow the supported/allowed tokens which can brick 
the system if underlying tokens has to be changed or they get hacked or some public vulnerability is disclosed in those tokens.

## Proof of Concept
Tokens ,at the very lean explanation , are just balances in the mappings of smart contracts .
Till date , billions of dollars has been stolen from smart contracts due to the vulnerable nature of human creation.

Multiple tokens are hacked due the vulnerabilities that have been exploited in them.

For example one of them was a token that allowed double spending in the transfer function where the `to` & `from` address was the same and it used cached balances
to update the state variables.

rekt.news is a great source to look for such type of hacks.

Now coming back , if a bug like that or even worse is reported in one of the allowed tokens or due to any reason the protocol wants to remove some tokens from the allowlist , there is no function to dis-allow the tokens,
```solidity
 /**
     * @param _token address of a wrapped LRT token
     * @dev ONLY add wrapped LRT tokens. Contract not compatible with rebase tokens.
     */
    function allowToken(address _token) external onlyAuthorized {
        isTokenAllowed[_token] = true;
    }
```
Which might leave the system in an undersired state for the long run.
## Tools Used
Manual review
## Recommended Mitigation Steps
Modify the `allowToken` function to a toggler like following 

```solidity

    function toggleTokenAllowed(address _token,bool _isallowed) external onlyAuthorized {
        isTokenAllowed[_token] = _isallowed;
    }
```


## Assessed type

Other
Vulnerable tokens cannot be removed from the isTokenAllowed map
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
The `isTokenAllowed` map contains a whitelist of tokens permitted by the project team for users to lock, and tokens can be added to this whitelist through the `allowToken` function. However, the `allowToken` function can only add a specified token to the whitelist by setting `isTokenAllowed[_token]` to `true`; it does not have the capability to remove tokens from the whitelist by setting `isTokenAllowed[_token]` to `false`. 

This could result in a scenario where if a token on the whitelist is found to have vulnerabilities, the `PrelaunchPoints` contract would not be able to remove that token from the whitelist. Consequently, users could still lock the vulnerable token in the contract, potentially exposing the `PrelaunchPoints` contract to attack, which in severe cases could lead to asset loss.

## Tools Used
None

## Recommended Mitigation Steps
To address the issue, the allowToken function could be modified as follows:
```
function allowToken(address _token, bool isAllowed) external onlyAuthorized {
    isTokenAllowed[_token] = isAllowed;
}
```


## Assessed type

Other
If allowed token have vulnerability been found, there is no way to remove it, which could harm the protocol
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
There is no way to avoid harm from allowed token which been proved exploitable.

## Proof of Concept
Owner of PrelaunchPoints have authority to add allowed token, but can't remove it if one of allowed tokens have been proved exploitable, this could harm the protocol.
```solidity
    function allowToken(address _token) external onlyAuthorized {
        isTokenAllowed[_token] = true;
    }
```

## Tools Used
Vscode

## Recommended Mitigation Steps
Add a bool parameter to add/remove allowed token
```solidity
-    function allowToken(address _token) external onlyAuthorized {
+    function allowToken(address _token, bool isAllowed) external onlyAuthorized {
-        isTokenAllowed[_token] = true;
+        isTokenAllowed[_token] = isAllowed;
    }
```





## Assessed type

Access Control