xanoutas/solidity-security-findings huggingface dataset:
Users can bypass the conversion of LRT to lpETH by setting a feeRecipient in the 0x transaction
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Users can farm points without participating in the protocol.

## Proof of Concept

When users that deposited LRTs call `claim()`, the function will route through 0x, convert their LRT tokens to ETH, send ETH to the contract, and swap lpETH to ETH at a 1:1 conversion rate. 

```
 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);
```

This means that users who deposited LRT will get lpETH back when `claim()`. However, there is a way that users can get back ETH and bypass the conversion to lpETH. 

The reason for doing so could be to farm points through `lock()` without participating in getting lpETH. Also, there may be instances where lpETH breaks the 1:1 peg to ETH because of market volatility or a manipulation in the `totalLpETH` variable, which results in users wanting ETH back and not lpETH.

In [0x](https://0x.org/docs/0x-swap-api/api-references/get-swap-v1-quote), there is a request for `feeRecipient` and `feeRecipientPercentage`.

If the `feeRecipient` and `feeRecipientPercentage` is set, the percentage of buyToken will be sent to the `feeRecipient`. This means that the LRT will be converted to ETH, and the percentage of ETH will be sent to the `feeRecipient`. Note that the fee can be up to 0.99 (apparently 1.0 gets internal server error)

If a user locks 1 rETH for example, and the exchange rate to ETH is 1.1:1, he can get back 1.1 ETH and not 1.1 lpETH.

Send this request to the CLI, get the API-KEY by creating an account on 0x. Note that this is selling 1 USDC for x USDT on polygon.

```
curl --location --request GET 'https://polygon.api.0x.org/swap/v1/quote?buyToken=0xc2132D05D31c914a87C6611C10748AEb04B58e8F&sellAmount=1000000&sellToken=0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174&buyTokenPercentageFee=0.99&feeRecipient=0x324446D6676123CA1e8EB5d6F171dB201d18e2D5' --header '0x-api-key: [API-KEY]'
```

This should return the data:

```
0x415565b00000000000000000000000002791bca1f2de4661ed88a30c99a7a9449aa84174000000000000000000000000c2132d05d31c914a87c6611c10748aeb04b58e8f00000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000000000000000000000000000000020e000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000
User assets frozen indefinetely due to lack of function to rescue preapproved tokens that turn out to be faulty
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 `recoverERC20` function in the PrelaunchPoints contract, which is intended to allow the owner to recover ERC20 tokens mistakenly sent to the contract, has a logical error in its token validation check. This could lead to a situation where the owner is unable to recover locked tokens that are later found to be faulty or malicious, potentially resulting in significant financial losses for users.

## Issue Description

The `recoverERC20` function is meant to provide a way for the contract owner to recover ERC20 tokens that were mistakenly sent to the PrelaunchPoints contract.

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

```solidity
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);
}
```

The function first checks if the `tokenAddress` is either the `lpETH` token or one of the allowed tokens (i.e., tokens that can be locked in the contract, as tracked by the `isTokenAllowed` mapping). If the token is `lpETH` or allowed, the function reverts with a `NotValidToken` error.

The issue is that if a token was previously allowed for locking via the `allowToken` function, but later turns out to be faulty or malicious, the owner will not be able to use `recoverERC20` to retrieve and return these tokens to their rightful owners. The faulty tokens will remain stuck in the contract.

This could lead to a significant loss of funds for users who locked the problematic tokens, and could damage the reputation and trustworthiness of the PrelaunchPoints platform.

## Proof of Concept

Consider a scenario where the owner allows a token `FaultyToken` that seems safe at first:

```solidity
FaultyToken faultyToken = new FaultyToken();
prelaunchPoints.allowToken(address(faultyToken));
```

Several users lock `FaultyToken` in the PrelaunchPoints contract:

```solidity
prelaunchPoints.lock(address(faultyToken), 1000, bytes32(0));
```

Later, it's discovered that `FaultyToken` has a critical bug that makes it non-transferable. 

The owner tries to use `recoverERC20` to retrieve the `FaultyToken` and return them to their owners:

```solidity
prelaunchPoints.recoverERC20(address(faultyToken), 1000);
```

However, because `FaultyToken` is in the `isTokenAllowed` mapping, the `recoverERC20` function reverts with `NotValidToken`.

The owner is unable to recover the faulty tokens, and they remain stuck in the contract, effectively causing users to lose their funds.

## Tools Used

Manual code review and analysis.

## Recommended Mitigation Steps

The function should handle all cases, regardless of whether the token was previously allowed or not. Repla
Mismatch in Total Supply and Total lpETH Due to WETH Conversion
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The contract's handling of WETH conversions and the subsequent update of `totalSupply` and `totalLpETH` can lead to a mismatch between the total ETH and equivalent in the system and the actual lpETH held by the contract. This can affect users' expectations when claiming or withdrawing, potentially leading to issues with the amount of lpETH received or unexpected reverts.

## Proof of Concept
In the `_processLock` function, when locking WETH:
https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L188-L191

```solidity
if (_token == address(WETH)) {
    WETH.withdraw(_amount); // Converts WETH to ETH
    totalSupply = totalSupply + _amount;
    balances[_receiver][ETH] += _amount;
}
```

Here, the contract withdraws WETH to ETH and adds the amount to `totalSupply` and the user's balance as if it were ETH. This is correct in terms of balance management but does not account for the potential change in totalLpETH when `convertAllETH` is called.

However, when `convertAllETH` is called, all ETH held by the contract is converted into lpETH, and `totalLpETH` is updated:

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

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

This function converts all ETH held by the contract into lpETH and updates `totalLpETH`. However, if there were any operations affecting the ETH balance between deposits (like WETH withdrawals that are converted to ETH and then potentially used or lost in transactions), the `totalLpETH` might not accurately reflect the `totalSupply` of ETH that users expect to be backed by lpETH.

## Tools Used
Manual review

## Recommended Mitigation Steps
Track conversions and ensure that any operation involving WETH and ETH updates are reflected in `totalLpETH` when necessary.


## Assessed type

Error
Reverting Withdrawal During Emergency Mode
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
During emergency mode, all the withdrawals are accepted

```javascript
/**
     * @param _mode boolean to activate/deactivate the emergency mode
@--->     * @dev On emergency mode all withdrawals are accepted at
     */
    function setEmergencyMode(bool _mode) external onlyAuthorized {
        emergencyMode = _mode;
    }

```

However, users may encounter unexpected behavior when attempting to withdraw their tokens. Specifically, the withdrawal function includes a check to revert the transaction if the current timestamp is past the startClaimDate. This check remains active even during emergency mode, potentially hindering users from withdrawing their funds when they need them the most.

## Proof of Concept
The withdrawal function includes the following check:

```javascript
if (_token == ETH) {
    if (block.timestamp >= startClaimDate) {
        revert UseClaimInstead();
    }
}
```
while there is a check to bypass this check at the top as below.

```javascript
if (!emergencyMode) {
            if (block.timestamp <= loopActivation) {
                revert CurrentlyNotPossible();
            }
@------>    if (block.timestamp >= startClaimDate) {
                revert NoLongerPossible();
            }
        }
```

 it was omitted in cases where the token is ETH

## Tools Used
Manual Review

## Recommended Mitigation Steps
consider updating the withdrawal function to exclude the `startClaimDate` check when in emergency mode. Alternatively, provide clear instructions to users about the behavior of the withdrawal function during emergency mode and advise them to use alternative methods if necessary.

```javascript
if (!emergencyMode && block.timestamp >= startClaimDate) {
   revert UseClaimInstead();
}
```


## Assessed type

Invalid Validation
Missing withdrawal time check for non-ETH tokens
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The current implementation of the `withdraw` function allows users to bypass the intended withdrawal restrictions after `loopActivation` has been set and before `startClaimDate`. This could potentially allow users to withdraw their tokens at times when they should instead be using the claim functionality. This breaks an invariant :
>     * Note Can only be called after the loop address is set and before claiming lpETH, * i.e. for at least TIMELOCK. In emergency mode can be called at any time.


## Proof of Concept
The absence of a check for `block.timestamp >= startClaimDate` for non-ETH token withdrawals in the smart contract code allows users to withdraw tokens at any time, without adhering to the intended time constraints set by `loopActivation` and `startClaimDate`.

```js
else {
            // @audit no check on (block.timestamp >= startClaimDate) which means can withdraw at any time
            IERC20(_token).safeTransfer(msg.sender, lockedAmount);
        }
```
## Tools Used

Manual review

## Recommended Mitigation Steps

Consider adding the same check for non-ETH tokens :
```js
if (block.timestamp >= startClaimDate) {
                revert NoLongerPossible();
            }
```


## Assessed type

Other
During emergency withdraw() if the token is ERC20 then it doesn't check block.timestamp with `startClaimDate`
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
- During emergency `withdraw()` if the token is ERC20 then it doesn't check `block.timestamp` with `startClaimDate`
- If the token is `ETH` there is a check that `block.timestamp > startClaimDate()`
https://github.com/code-423n4/2024-05-loop/blob/main/src/PrelaunchPoints.sol#L291 but if the token is ERC20 then there is no check that block.timestamp > startClaimDate().

## Proof of Concept
- https://github.com/code-423n4/2024-05-loop/blob/main/src/PrelaunchPoints.sol#L291
- https://github.com/code-423n4/2024-05-loop/blob/main/src/PrelaunchPoints.sol#L301-L302

## Tools Used
Manual review

## Recommended Mitigation Steps
- Apply a check for ERC20 token as well for `withdraw()`


## Assessed type

Other
Incorrect Validation Logic for Swap Recipient Address in `function _validateData`
Audit: 2024-05-loop-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Description

Within the `function _validateData` , the final `if` condition is intended to
validate the `recipient` address from the swap data. The condition is designed
to revert the transaction if the recipient is not the contract itself
`(address(this))` or is the zero address `(address(0))`. However, the use of the
logical AND operator (&&) in the condition is incorrect because it implies that
the transaction should revert only if the recipient is simultaneously not the
contract's address and not the zero address, which is a logical impossibility.

## Impact

This flawed logic means that the condition will never trigger a revert, even if
the recipient is the zero address, which should not be a valid recipient for the
swap. As a result, the contract could incorrectly allow a swap where the swapped
ETH would be sent to the zero address, effectively burning the ETH and causing a
loss of funds.

## Proof of Concept

This is the weakest part / function of the entire codebase, as not a single test
is performed for it to catch even these types of basic vulnerabilty.

## Recommended Mitigation Steps

The condition should be corrected to use the logical OR operator (||) to ensure
that the transaction reverts if the recipient is either not the contract's
address or is the zero address. Additionally, the inclusion of address(0) as a
valid recipient should be removed unless there is a specific and valid reason
for its presence.

```
if (recipient != address(this)) {
    revert WrongRecipient(recipient);
}
```

This ensures that the transaction will only proceed if the recipient is the contract itself, preventing the loss of funds to the zero address.


## Assessed type

Invalid Validation
Misassumption in calculation of seconds per liquidity outside
Audit: 2024-10-ramses-exchange-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/libraries/Oracle.sol#L489-L500


# Vulnerability details

## Impact
Liquidity provider loses all rewards for a period

## Proof of Concept
In Ramses protocol, the rewards of a position during a specific period is calculated using similar formula of how `feeGrowthOutside` works in Uniswap V3. It calculates seconds accumulated per liquidity inside `tickLower` and `tickUpper`, and the multiply it by position's liquidity to determine the amounts of seconds that the position took during a specific period.

During the calculation, [periodCumulativesInside](https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/libraries/Oracle.sol#L470) function is used to calculate accumulated seconds per liquidity in a tick range.

Here's a [code snippet](https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/libraries/Oracle.sol#L483-L501) of the function that deals with ticks that their `secondsPerLiquidityOutside` is not initialized:

```solidity
{
    int24 startTick = $.periods[period].startTick;
    uint256 previousPeriod = $.periods[period].previousPeriod;

    snapshot.secondsPerLiquidityOutsideLowerX128 = uint160(lower.periodSecondsPerLiquidityOutsideX128[period]);

>   if (tickLower <= startTick && snapshot.secondsPerLiquidityOutsideLowerX128 == 0) {
        snapshot.secondsPerLiquidityOutsideLowerX128 = $
            .periods[previousPeriod]
            .endSecondsPerLiquidityPeriodX128;
    }

    snapshot.secondsPerLiquidityOutsideUpperX128 = uint160(upper.periodSecondsPerLiquidityOutsideX128[period]);
>   if (tickUpper <= startTick && snapshot.secondsPerLiquidityOutsideUpperX128 == 0) {
        snapshot.secondsPerLiquidityOutsideUpperX128 = $
            .periods[previousPeriod]
            .endSecondsPerLiquidityPeriodX128;
    }
}
```

If `secondsPerLiquidityOutsideUpperX128` of a specific tick is zero, it is assumed that the tick is not crossed, thus it initializes it with `endSecondsPerLiquidity` of previous period.

However, this assumption is wrong, and the `secondsPerLiquidityOutside` for the period can actually be zero, as described in the following scenario:

- Let's assume there's a position between `tickLower` and `tickUpper`.
- Let's assume that the period's `startTick` is greater than `tickUpper`, and as of start of the period, the liquidity was zero. (This happens when `zeroForOne` swaps are done in the previous period, no liquidity was placed between `tickUpper` and `slot0.tick`)
- During the period, `zeroForOne` swaps happen again which crosses the `tickUpper`(assumed this is the first tick crossed), which updates `endSecondsPerLiquidityOutside` as follows:
  
```solidity
uint256 periodSecondsPerLiquidityOutsideX128;
uint256 periodSecondsPerLiquidityOutsideBeforeX128 = info.periodS
The function `Oracle.newPeriod` does not create a new observation rendering the whole period system useless
Audit: 2024-10-ramses-exchange-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/libraries/Oracle.sol#L338-L360


# Vulnerability details

## Impact

Because `Oracle.newPeriod` never creates a new period/observation all values are written in the same period making the whole period system useless. 

 

## Proof of Concept

The Ramses protocol employs a period system. Each period is 1 week long and some specific values are supposed to be tracked for each period in a separate observation. These values are namely:
the `blockTimestamp` (the last time the observation was updated), 
the ` tickCumulative` (tick * time elapsed since the pool was first initialized),
the ` secondsPerLiquidityCumulativeX128`( seconds elapsed / max(1, liquidity) since the pool was first initialized) and
the `initialized` which indicates if the observation was initialized or not.

Each period is supposed to have one observation which holds the corresponding data for the period. These observations are updated every time a swap happens, or a user changes the liquidity of his position. (Functions `swap` and `_modifyPosition`).
If one period is over and the function `swap` or any function with the `advancePeriod()` modifier is called, the function `Oracle.newPeriod` is called to wrap up the observation of the last period and create a new observation for the current period. 

The issue arises from the fact that the function `Oracle.newPeriod` never creates a new observation but only updates the current one. 

In the function [swap]( https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/RamsesV3Pool.sol#L439-L443) as well as in [_advancePeriod]( https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/RamsesV3Pool.sol#L761-L765), the arguments given to the function `Oracle.newPeriod` are the observations array, the observationIndex from slot0 and the current period:

```solidity
    uint160 secondsPerLiquidityCumulativeX128 = Oracle.newPeriod(
    	$.observations,
               _slot0.observationIndex,
               period
      );
```

In the function `newPeriod()`, first the time between the last update and the end of the current period is calculated as delta. This delta is then used to calculate secondsPerLiquidityCumulativeX128 and finally the current observation is updated. 

```Solidity
    function newPeriod( 
        Observation[65535] storage self,
        uint16 index,
        uint256 period
    ) external returns (uint160 secondsPerLiquidityCumulativeX128) {
        Observation memory last = self[index];
        PoolStorage.PoolState storage $ = PoolStorage.getStorage();

        unchecked {
            uint32 delta = uint32(period) * 1 weeks - 1 - last.blockTimestamp; 

            secondsPerLiquidityCumulativeX128 =
                last.secondsPerLiquidityCumulativeX128 +
                ((ui