xanoutas/solidity-security-findings huggingface dataset:
When minting or increasing liquidity through `NonfungiblePositionManager`, access ETH send to the contract is not refunded 
Audit: 2024-10-ramses-exchange-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/periphery/NonfungiblePositionManager.sol#L130-L182
https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/periphery/NonfungiblePositionManager.sol#L196-L261
https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/periphery/base/PeripheryPayments.sol#L52-L69
https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/periphery/base/PeripheryPayments.sol#L44-L46


# Vulnerability details

## Impact
When minting or increasing liquidity through `NonfungiblePositionManager`, access ETH send to the contract is not refunded to the caller and stays in the contract. This results in a permanent loss for the user if he does not actively call `refundETH` in the same transaction.

## Proof of Concept
 
The `NonfungiblePositionManager` is the entry point for users if they want to interact with pools in the Ramses protocol. Here they can, beside other things, mint a position or increase the liquidity of an existing position. Both functions [mint]( https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/periphery/NonfungiblePositionManager.sol#L143-L157) and [increaseLiquidity](https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/periphery/NonfungiblePositionManager.sol#L210-L224) call the function [addLiquidity]( https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/periphery/base/LiquidityManagement.sol#L50-L87) which in turn calls the [mint function]( https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/core/RamsesV3Pool.sol#L267-L298 ) of the respective Ramses pool.  After doing some calculations the [uniswapV3MintCallback]( https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/core/RamsesV3Pool.sol#L293 ) is called which in turn calls the [pay function]( https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/periphery/base/PeripheryPayments.sol#L52-L69 ) of the `NonfungiblePositionManager` for each of the tokens in the pool to facilitate the token transfer:

```solidity
    function pay(
        address token,
        address payer,
        address recipient,
        uint256 value
    ) internal {
        if (token == WETH9 && address(this).balance >= value) {
            // pay with WETH9
            IWETH9(WETH9).deposit{value: value}(); // wrap only what is needed to pay
            IWETH9(WETH9).transfer(recipient, value);
        } else if (payer == address(this)) {
            // pay with tokens already in the co
LP positions in low activity pools will have periodSecondsInsideX96 as 0 leading to lost rewards
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Summary

**Context:**
When users call mint(), burn(), swap() on a pool, the _advancePeriod() function is called, which advances the period to the current period and does not allowing period advancements until a week has passed. 

We know that it is possible for unpopular & low-activity pools to have their pool functions mint(), burn() and swap() called significantly after the beginning of a new period (i.e a block timestamp that is an even multiple of 1 weeks).

**Issue:**
The issue is that if a period is skipped, the periodSecondsInsideX96 of an existing position will be returned as 0. Since the GaugeV3 reward [here](https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/gauge/GaugeV3.sol#L321C2-L325C11) utilizes periodSecondsInsideX96 (i.e. 0) to calculate the rewards to transfer to the LP position owner, the rewards calculated would be 0.

## Proof of Concept

How to use this POC:
-  The below foundry test contract uses the existing setup from the test coverage invitational so no additional setup is required other than git cloning the test coverage repo and installing the dependencies.
 - Add the POC to the `test` folder and name it `TestHistoricalDataIssue.sol`
 - Run the test using `forge test --mt testHistoricalDataIssue -vvvvv`
 - The test displays the console.log statements in the traces, where we can see the affected periodSecondsInsideX96 value to be 0.
 - I've left helpful comments in the POC to help navigate the actions taking place.

Here's what the POC does:
 - Enter period 1
 - Mint position in period 1
 - Enter period 2
 - No mint(), burn() or swap() interactions in period 2 (since it is an unpopular and low-activity pool)
 - Enter period 3
 - _advancePeriod() called in period 3 through a swap, mint or burn. In the test, we just call _advancePeriod() directly for simplicity to showcase the bug.
 - We call the positionPeriodSecondsInRange() function on the pool to retrieve the periodSecondsInsideX96 for the position for period 1 and 2. Since the liquidity of the position was provided for the full duration of the first period and the second period, the periodSecondsInsideX96 should be (1 weeks * 2**96) for both period 1 and 2 each. But as we can see, the value returned is 0 for both period 1 and 2. This occurred because the pool had no interactions during period 2, essentially skipping it. When we call _advancePeriod() in period 3, we can see the damage the done i.e. periodSecondsInsideX96 being returned as 0, which means the LP position owner would not receive any rewards for those two periods even though active liquidity was provided.
 - We know the bug arises from skipping a period and then receiving a mint(), burn() or swap() call (indirectly calling _advancePeriod()) in a future pe
ClGaugeFactory and all GaugeV3 instances continue using old FeeCollector address even after update
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/gauge/GaugeV3.sol#L28
https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/gauge/ClGaugeFactory.sol#L20


# Vulnerability details

## Summary

**Context:**
The RamsesV3Factory allows updating the fee collector address using the function [setFeeCollector()](https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/RamsesV3Factory.sol#L169). According the sponsor team, the setter function is provided in case there is a need to change how fees are distributed or if there’s a new version of the fee collector.

**Issue:**
The issue is that the ClGaugeFactory and all the deployed GaugeV3 instances continue using the old feeCollector contract and do not upgrade to using the new one dynamically. This occurs because in the ClGaugeFactory [here](https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/gauge/ClGaugeFactory.sol#L20) and GaugeV3 [here](https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/gauge/GaugeV3.sol#L28), the feeCollector storage variables are marked as immutable. This means the feeCollector on the existing or even the future GaugeV3 instances will be the old one and not the new one.

Due to this, the team is never able to implement a new version of the fee collector contract and use it in the system to change how the fees are distributed.

## Proof of Concept

1. ClGaugeFactory is deployed with the feeCollector as immutable.

```solidity
    address public immutable override feeCollector;

    constructor(
        address _nfpManager,
        address _votingEscrow,
        address _voter,
        address _feeCollector
    ) {
        nfpManager = _nfpManager;
        votingEscrow = _votingEscrow;
        voter = _voter;
        feeCollector = _feeCollector;

        emit OwnerChanged(address(0), msg.sender);
    }
```

2. During the deployment of a GaugeV3 contract instance through the ClGaugeFactory, we use the immutable feeCollector address always.

```solidity
    function createGauge(
        address pool
    ) external override returns (address gauge) {
        require(msg.sender == voter, "AUTH");
        require(getGauge[pool] == address(0), "GE");
        gauge = address(new GaugeV3(voter, nfpManager, feeCollector, pool));
        getGauge[pool] = gauge;
        emit GaugeCreated(pool, gauge);
    }
```

3. The feeCollector gets stored in an immutable variable in the GaugeV3.sol contract as well.

```solidity
    IFeeCollector public immutable feeCollector;    

    constructor(
        address _voter,
        address _nfpManager,
        address _feeCollector,
        address _pool
    ) {
        _unlocked = true;

        voter = _voter;
        feeCollector = IFeeC
Inflated `GaugeV3` rewards when period is skipped
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Details


The `GaugeV3` contract distributes rewards based on the proportion of liquidity that each position had in range over each 1-week "period". This is calculated by `cachePeriodEarned()` in the gauge, which calls `positionPeriodSecondsInRange()` on the `RamsesV3Pool`. A key part of this calculation is the `periodCumulativesInside()` function, which computes the total seconds per liquidity within a tick range for that period.

In `periodCumulativesInside()`, one sub-case occurs when the period is in the past, and the tick range was active at the end of that period:

```solidity
function periodCumulativesInside(/* ... */) /* ... */ {
    // ...
    if (lastTick < tickLower) {
        // ...
    } else if (lastTick < tickUpper) {
        // ...
        if (currentPeriod <= period) {
            // ...
        } else {
            cache.secondsPerLiquidityCumulativeX128 = $.periods[period].endSecondsPerLiquidityPeriodX128;
        }
        return
            cache.secondsPerLiquidityCumulativeX128 -
            snapshot.secondsPerLiquidityOutsideLowerX128 -
            snapshot.secondsPerLiquidityOutsideUpperX128;
    } else {
        // ...
    }
}
```

Notice that this sub-case relies on `$.periods[period].endSecondsPerLiquidityPeriodX128`, which is meant to represent the total seconds per liquidity when the period ended. However, this value is actually more accurately described as "the seconds per liquidity at the start of the next period", which can be seen in how `_advancePeriod()` and `newPeriod()` are implemented:

```solidity
function _advancePeriod() /* ... */ {
    // ...
    if ((_blockTimestamp() / 1 weeks) != _lastPeriod) {
        // ...
        uint160 secondsPerLiquidityCumulativeX128 = Oracle.newPeriod(
            $.observations,
            _slot0.observationIndex,
            period
        );
        // ...
        $.periods[_lastPeriod].endSecondsPerLiquidityPeriodX128 = secondsPerLiquidityCumulativeX128;
        // ...
    }
}

function newPeriod(/* ... */) /* ... */ {
    // ...
    uint32 delta = uint32(period) * 1 weeks - 1 - last.blockTimestamp;
    secondsPerLiquidityCumulativeX128 =
        last.secondsPerLiquidityCumulativeX128 +
        ((uint160(delta) << 128) / ($.liquidity > 0 ? $.liquidity : 1));
    // ...
}
```

So, this means that if a period is skipped (meaning no activity happens in the pool during the period), the next time `_advancePeriod()` is called, the `endSecondsPerLiquidityPeriodX128` for the last period will be set to just before the start of the period after the skipped period. This is effectively one week after the last period actually ended. This adds extra time to the sub-case mentioned above, which leads to inflated rewards for users.

## Impact

If period `p` has gauge rewards and period `p+1` has no p
Issue with pools that have one as tick spacing
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/RamsesV3Factory.sol#L39-L41
https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/RamsesV3Pool.sol#L607


# Vulnerability details

## Impact
Some positions don't get fees accrued from their liquidity.

## Proof of Concept
Current design of Ramses protocol allows creating pools using one as tick spacing.
This feature includes a vulnerability during swaps.

In Uniswap's CL architecture, token swaps are accomplished by going through ticks.
When `zeroForOne` is true(swapping `token0` to `token`), the ticks are crossed in decreasing manner, and when `zeroForOne` is false(swapping `token1` to `token0`), the ticks are crossed in increasing manner, all starting from `slot0.tick` which is current tick.

The algorithm of finding next initialized tick uses `TickBitmap` and it works a bit different between `zeroForOne` and `oneForZero`.

- In case of `zeroFoOne`, the next initialized tick is determined as **first smaller tick or the tick equal to current one**, as implemented [here](https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/libraries/TickBitmap.sol#L54-L64).
- In case of `oneForZero`, the next initialized tick is determined as **first bigger tick**, as implemented [here](https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/libraries/TickBitmap.sol#L66-L77).

After token swap is accomplished between `currentTick` and `nextTick`, the next `currentTick` is determined as follows(here's a [link](https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/RamsesV3Pool.sol#L606-L608) to the implementation):

```solidity
unchecked {
    state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
}
```

So now, let's think of this:

- A swap(`zeroForOne`) is done between `tickPrev` and `tickNext`.
- The next tick(`state.tick`) is determined as `tickNext - 1`, which is assumed to be initialized.
- In this scenario, the liquidity between `tickNext` and `tickNext - 1` is ignored and does not participate in swap.
- This scenario only happens in pools with one as tick spacing.
  
By abusing this issue, a malicious user might prevent a position from earning fees from the liquidity:

- Let's assumes that there is a position between `tickLower` and `tickUpper` where `tickUpper = tickLower + 10`.
- A malicious user initialize ticks between `tickLower` and `tickUpper` by adding liquidity as small as 1 or 2 wei. 
- As a result, the position does not earn fee at all from the liquidity.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Tick spacing one should not be allowed.


## Assessed type

Context
It is possible to set the initial fee for a tick spacing to 100% even though the fee should be capped to 10% 
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/RamsesV3Factory.sol#L107
https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/libraries/ProtocolActions.sol#L78


# Vulnerability details

## Impact
If in the `RamsesV3PoolFactory` the initial fee for a tickSpacing is set above 10%, all pools created with this tick spacing will have a fee which is higher than the protocol`s intended max fee of 10%. This will lead to fees for users that are higher than intended.  


## Proof of Concept
 
Pools in the Ramses protocol are created through the `RamsesV3PoolFactory` using the initial fee set for the tick spacings used for the pool. A pool can only use a tick spacing where a initial fee was set / which was enabled.
When the `RamsesV3PoolFactory` is initialized, a total of 6 tick spacings and their corresponding initial fees are enabled. Further tick spacings can be unlocked by calling “enableTickSpacing”:
```solidity
    function enableTickSpacing(int24 tickSpacing, uint24 initialFee) external override restricted {
@>        require(initialFee < 1_000_000);
        /// @dev tick spacing is capped at 16384 to prevent the situation where tickSpacing is so large that 
        /// @dev TickBitmap#nextInitializedTickWithinOneWord overflows int24 container from a valid tick
        /// @dev 16384 ticks represents a >5x price change with ticks of 1 bips
        require(tickSpacing > 0 && tickSpacing < 16384, 'TS');
        require(tickSpacingInitialFee[tickSpacing] == 0, 'TS!0');

        tickSpacingInitialFee[tickSpacing] = initialFee;
        emit TickSpacingEnabled(tickSpacing, initialFee);
    }
``` 

If the protocol wants to change the fees for a specific pool it can call the function `setFee` in the factory. This function call the pool and ends up calling the function `setFee` in the pool:
```solidity
    function setFee(uint24 _fee, address factory) external {
        PoolStorage.PoolState storage $ = PoolStorage.getStorage();
        if (msg.sender != factory) revert Unauthorized();
@>        if (_fee > 100000) revert InvalidFee();
        uint24 _oldFee = $.fee;
        $.fee = _fee;
        emit FeeAdjustment(_oldFee, _fee);
    }
``` 

This function limits the fee that can be set to 100.000 (10%) which shows the intend of the protocol to limit the fees to a maximum of 10%.

The issue arises from the fact that in the factory, the initial fee can be set to a maximum of 999_999 (99,99%) which contradicts the limit of 10% intended by the protocol.

Each pool created with a newly enabled tick spacing where the initial pool fee is set to more than 10% will have a fee that is higher than the 10% intended by the protocol. This is made even worse by the fact that once the initial fee for a tick spacing is set, it can not be changed.   


## Recommended Mitigation Steps
Make sure that, when enabling a new tick sp
Not 100% of `protocolFees` are associated with the right period in the `feeDistributor`
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/libraries/ProtocolActions.sol#L48-L73
https://github.com/code-423n4/2024-10-ramses-exchange/blob/4a40eba36bc47eba8179d4f6203a4b84561a4415/contracts/CL/core/RamsesV3Pool.sol#L749-L778


# Vulnerability details

## Impact
Because it can not be guaranteed that the function `collectProtocol()` is called in the last block of a period, swapFees for the protocol accrued in one period might be accounted for a different period in the feeDistributor. This results in the inaccurate distribution of voting incentives for voters.


## Proof of Concept


Whan a user makes a swap or takes a flashloan in one of the `RamsesPools` he pays a fee. Part of the fee goes to the LP providers of the pool, but the bigger part (80%) goes to the protocol. Normally, a small part of the fees going to the protocol is sent to the treasury and most of the fees are sent to the pools `feeDistributor`. These fees are then used to incentivise users to vote on the distribution of the RAM token emissions. Users can vote for a pool and will then get the fees from its `feeDistributor` as a reward. Since voters only get rewards from the `feeDistributor` for periods they vote in, it is crucial to associate the swapFees generated in one period to this particular period in the `feeDistributor`.

The only way to move the swapFees from the pool to the `feeDistributor` is to call the function `collectProtocolFees` in the `FeeCollector` contract. The function is public and can be called by anyone. It in turn calls the pool’s `collectProtocol()` function which checks if there are any accumulated `protocolFees`. If so these fees are send to the `feeCollector` which in turn splits the fees between the treasury and the `feeDestributor`. The function `notifyRewardAmount` of the `feeDistributor` is called which in turn transfers the feeAmount to the `feeDistributor` and associates the fees to the current period. 

```solidity
    function notifyRewardAmount( //i: only the feeCollector can call this function
        address token,
        uint256 amount
    ) external nonReentrant {
        require(msg.sender == pairFees, Unauthorized()); //i: only the feeCollector can call this function
        uint256 period = getPeriod();

        /// @notice there are no votes for the first period; distribute first period fees as vote incentives to second period voters
        if ([period] == 0) { //i: no votes => give rewards to next period (period starts with 0)
            period += 1;
        }

        rewards.add(token);

        uint256 balanceBefore = IERC20(token).balanceOf(address(this));
        _safeTransferFrom(token, msg.sender, address(this), amount);
        uint256 balanceAfter = IERC20(token).balanceOf(address(this));

        amount = balanceAfter - balanceBefore;
@>        rewardSupply[period][token] += amount;
        emit NotifyReward(msg.sender, token, amount,