xanoutas/solidity-security-findings huggingface dataset:
RamsesV3Pool has _advancePeriod Function Is Public but Should Be Internal
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/core/RamsesV3Pool.sol#L748-L778


# Vulnerability details

## Proof of Concept
Explain and rationalize the potential impact. Provide:
- Direct links to all referenced code in GitHub
- Screenshots, logs, or any other relevant proof that illustrates the concept
- Any additional research or code needed to validate the claims made in your submission

### Impact
The _advancePeriod function is intended for internal use within the RamsesV3Pool contract to manage period transitions and update internal state variables. However, it is currently declared as public, allowing any external user to call it. This unintended exposure can lead to unauthorized manipulation of the contract’s internal state, potentially causing incorrect behavior, disrupting period-dependent logic, or exploiting other vulnerabilities related to timing.

By calling _advancePeriod externally, a public caller could force the contract to advance periods prematurely, affecting calculations that depend on the period timing.

### Vulnerable Code
```Solidity
// File: contracts/CL/core/RamsesV3Pool.sol
// Lines 749-778

function _advancePeriod() public {
    PoolStorage.PoolState storage $ = PoolStorage.getStorage();

    // If in a new week, record lastTick for previous period
    // Also record secondsPerLiquidityCumulativeX128 for the start of the new period
    uint256 _lastPeriod = $.lastPeriod;
    if ((_blockTimestamp() / 1 weeks) != _lastPeriod) {
        Slot0 memory _slot0 = $.slot0;
        uint256 period = _blockTimestamp() / 1 weeks;
        $.lastPeriod = period;

        // Start new period in observations
        uint160 secondsPerLiquidityCumulativeX128 = Oracle.newPeriod(
            $.observations,
            _slot0.observationIndex,
            period
        );

        // Record last tick and secondsPerLiquidityCumulativeX128 for old period
        $.periods[_lastPeriod].lastTick = _slot0.tick;
        $.periods[_lastPeriod].endSecondsPerLiquidityPeriodX128 = secondsPerLiquidityCumulativeX128;

        // Record start tick and secondsPerLiquidityCumulativeX128 for new period
        PeriodInfo memory _newPeriod;

        _newPeriod.previousPeriod = uint32(_lastPeriod);
        _newPeriod.startTick = _slot0.tick;
        $.periods[period] = _newPeriod;
    }
}
```

### Proof of Concept
An public or external call can be made to the _advancePeriod function.
```Solidity
pragma solidity ^0.8.20;

contract PublicCaller {
    RamsesV3Pool public pool;

    constructor(address poolAddress) {
        pool = RamsesV3Pool(poolAddress);
    }

    function attackAdvancePeriod() external {
        // External call to the internal _advancePeriod function
        pool._advancePeriod();
    }
}
```
By deploying the PublicCaller contract and invoking attackAdvancePeriod, the public caller forces the RamsesV3Pool contract to advance the period.
## Recommended
NonfungiblePositionManager has Missing Access Control in increaseLiquidity Function
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/periphery/NonfungiblePositionManager.sol#L195-L261


# Vulnerability details

## Proof of Concept
Explain and rationalize the potential impact. Provide:
- Direct links to all referenced code in GitHub
- Screenshots, logs, or any other relevant proof that illustrates the concept
- Any additional research or code needed to validate the claims made in your submission

### Impact
The increaseLiquidity function in the NonfungiblePositionManager contract lacks proper access control. 
Specifically, it is missing the isAuthorizedForToken(params.tokenId) modifier.

### Vulnerable Code
```Solidity
// File: NonfungiblePositionManager.sol
// Location: Lines 195-261
// https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/periphery/NonfungiblePositionManager.sol#L195-L261

    /// @inheritdoc INonfungiblePositionManager
    function increaseLiquidity(
        IncreaseLiquidityParams calldata params
    )
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (uint128 liquidity, uint256 amount0, uint256 amount1)
    {...
```

### Proof of Concept (PoC)
An unauthorized user can increase the liquidity of a position.
```Solidity
pragma solidity ^0.8.20;

import "./NonfungiblePositionManager.sol";

contract UnauthorizedLiquidityAdder {
    NonfungiblePositionManager public nonfungiblePositionManager;

    constructor(address _nonfungiblePositionManager) {
        nonfungiblePositionManager = NonfungiblePositionManager(_nonfungiblePositionManager);
    }

    function addLiquidityToAnotherPosition(
        uint256 targetTokenId,
        uint256 amount0Desired,
        uint256 amount1Desired
    ) external payable {
        NonfungiblePositionManager.IncreaseLiquidityParams memory params = NonfungiblePositionManager
            .IncreaseLiquidityParams({
                tokenId: targetTokenId,
                amount0Desired: amount0Desired,
                amount1Desired: amount1Desired,
                amount0Min: 0,
                amount1Min: 0,
                deadline: block.timestamp + 1 days
            });

        // Unauthorized increase of liquidity on a tokenId not owned by this contract
        nonfungiblePositionManager.increaseLiquidity{value: msg.value}(params);
    }
}
```
### Steps to Reproduce
1.	Deploy the NonfungiblePositionManager contract.
2.	Assume tokenId is owned by User A.
3.	User B deploys the UnauthorizedLiquidityAdder contract, passing the address of the NonfungiblePositionManager.
4.	User B calls addLiquidityToAnotherPosition with targetTokenId belonging to User A, along with desired liquidity amounts.
5.	The liquidity of User A’s position is increased without their consent.

## Recommended Mitigation Steps
Add the isAuthorizedForToken(params.tokenId) modifier to the increaseLiquidity function to enfo
`getReward` incorrectly sets msg.sender as receiver instead of owner.
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/gauge/GaugeV3.sol#L452


# Vulnerability details



### Proof of Concept

`getReward` calls `_getAllRewards` passing in `msg.sender` as recevier. Since, not only the owner can call the function for a tokenId, the rewards are incorrectly sent to the caller instead of the owner which is a security breach. In cases where the interactions are done through a contract, the rewards might be lost in the contract instead.
```solidity
    function getReward(uint256 tokenId, address[] memory tokens) public lock {
// ...
        _getAllRewards(
            address(_nfpManager),
            tokenId,
            tickLower,
            tickUpper,
            tokens,
            msg.sender
        );
    }
```
### Recommended Mitigation Steps

Send the rewards to the token owner instead.
```diff
        _getAllRewards(
            address(_nfpManager),
            tokenId,
            tickLower,
            tickUpper,
            tokens,
-           msg.sender
+           owner
        );
    }
```


## Assessed type

Context
The function cachePeriodEarned has no authorization.
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/gauge/GaugeV3.sol#L286


# Vulnerability details

## Issue

The function cachePeriodEarned has no authorization.

```solidity
    function cachePeriodEarned(
        uint256 period,
        address token,
        address owner,
        uint256 index,
        int24 tickLower,
        int24 tickUpper,
        bool caching
    ) public override returns (uint256 amount) {
        uint256 periodSecondsInsideX96;

        bytes32 _positionHash = positionHash(
            owner,
            index,
            tickLower,
            tickUpper
        );

        /// @dev get seconds from pool if not already written into storage
        if (!periodAmountsWritten[period][_positionHash]) {
            (bool success, bytes memory data) = address(pool).staticcall(
                abi.encodeCall(
                    IRamsesV3PoolState.positionPeriodSecondsInRange,
                    (period, owner, index, tickLower, tickUpper)
                )
            );

            if (!success) {
                return 0;
            }

            (periodSecondsInsideX96) = abi.decode(data, (uint256));

            if (period < _blockTimestamp() / WEEK && caching) {
                periodAmountsWritten[period][_positionHash] = true;
                periodNfpSecondsX96[period][
                    _positionHash
                ] = periodSecondsInsideX96;
            }
        } else {
            periodSecondsInsideX96 = periodNfpSecondsX96[period][_positionHash];
        }

        amount = FullMath.mulDiv(
            tokenTotalSupplyByPeriod[period][token],
            periodSecondsInsideX96,
            WEEK << 96
        );
```

the user can pass in period parameter immediately and set cache to true to write and store periodSecondsInsideX96

```solidity
            if (period < _blockTimestamp() / WEEK && caching) {
                periodAmountsWritten[period][_positionHash] = true;
                periodNfpSecondsX96[period][
                    _positionHash
                ] = periodSecondsInsideX96;
            }
```

user can call the function when [there is no checkpoint](https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/core/libraries/Position.sol#L215) for a period to set 

```solidity
 function positionPeriodSecondsInRange(
        PositionPeriodSecondsInRangeParams memory params
    ) public view returns (uint256 periodSecondsInsideX96) {
        PoolStorage.PoolState storage $ = PoolStorage.getStorage();

        uint256 currentPeriod = $.lastPeriod;
        if (params.period > currentPeriod) revert FTR();

        bytes32 _positionHash = positionHash(params.owner, params.index, params.tickLower, params.tickUpper);

        uint256 liquidity;
        int160 secondsPerLiquidityPeriodStartX128;

        PositionCheckpoint[] storage checkpoints = $.positionCheckpoin
Pool fee structure can be broken once set
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/main/contracts/CL/core/RamsesV3Factory.sol#L38-L63
https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Factory.sol#L61-L73
https://github.com/code-423n4/2024-10-ramses-exchange/blob/main/contracts/CL/core/RamsesV3Factory.sol#L80
https://github.com/code-423n4/2024-10-ramses-exchange/blob/main/contracts/CL/core/RamsesV3Pool.sol#L745
https://github.com/code-423n4/2024-10-ramses-exchange/blob/main/contracts/CL/core/libraries/ProtocolActions.sol#L78


# Vulnerability details

## Proof of Concept
During creation of pools, the fee set for the pool is based upon the tick spacing specified by the pool creator as well as whether that specified tick spacing has an avialable fee structure in the `tickSpacingInitialFee` mapping. An example is such as this `tickSpacingInitialFee[1] = 100;`

There is a bounds limit set upon creating new tick spacing and fee structure as can be seen below:

```solidity
function enableTickSpacing(int24 tickSpacing, uint24 initialFee) external override restricted {
        require(initialFee < 1_000_000, FTL()); // @note making sure we don't go over this bound for fee
        require(tickSpacing > 0 && tickSpacing < 16384, 'TS');
        require(tickSpacingInitialFee[tickSpacing] == 0, 'TS!0'); // @audit allowing the fee to tick spacing being setup
        tickSpacingInitialFee[tickSpacing] = initialFee; // @note write
        emit TickSpacingEnabled(tickSpacing, initialFee);
    }
```

Once such tick & spacing structure has been created, it can then be used to deploy pools with such as can be seen in the `createPool` function below of the `RamsesV3Factory` contract:

```solidity
function createPool(
        address tokenA,
        address tokenB,
        int24 tickSpacing,
        uint160 sqrtPriceX96
    ) external override returns (address pool) {
        ...

@>        uint24 fee = tickSpacingInitialFee[tickSpacing];
        require(fee != 0, F0());
        ...

        parameters = Parameters({
            factory: address(this),
            token0: token0,
            token1: token1,
            fee: fee,
            tickSpacing: tickSpacing
        });
        pool = IRamsesV3PoolDeployer(ramsesV3PoolDeployer).deploy(token0, token1, tickSpacing);
        delete parameters;

        ...
    }
```

The issue is that a pool could be deployed with the following fee structure and tick spacing that is allowed:

```solidity
tickSpacingInitialFee[16383] = 999_999; @note rough example that keeps us beneath upper bounds for initialFee and tickSpacing
```

When such pools have been deployed however, its fee structure cannot go below 89% when trying to do so because of the if statement check below in `setFee` function of the RamsesV3Pool contract (snipped from the library ProtocolActions.sol):

```solidity
FILE: RamsesV3Pool.sol

function setFee(uint24 _fee) external override lock {
        ProtocolActions.setFee(_fee, factory);
    }
```

```solidi
The fee for the protocol in the function RamsesV3Pool::flash() if not calculated correctly
Audit: 2024-10-ramses-exchange-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/core/RamsesV3Pool.sol#L713
https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/core/RamsesV3Pool.sol#L718


# Vulnerability details

## Impact

Because the fee for the protocol is not calculated correctly, the split of fees is wrong resulting in less fees for the protocol

## Proof of Concept
 
When calling the function [flash]( https://github.com/code-423n4/2024-10-ramses-exchange/blob/236e9e9e0cf452828ab82620b6c36c1e6c7bb441/contracts/CL/core/RamsesV3Pool.sol#L684-L725) on a RamsesV3Vault the caller initiates a flash loan. For the amount he flashes he needs to pay a fee. This fee is then divided between the protocol and the liquidity provider of the protocol. The issue arises from the fact how the share of the fees the protocol should get is calculated:

```solidity
uint256 pFees0 = feeProtocol == 0 ? 0 : paid0 / feeProtocol;
```  

The feeProtocol is a number between 0 and 100 representing the % of fees the protocol should get, which is initially set to 80 meaning 80% of the fees should go to the protocol. With the current calculation and the initial value of 80, the protocol would only get 1/80 of the fees instead of 80% of the fees, contradicting the intended fee distribution and resulting in way less fees for the protocol than intended.

## Recommended Mitigation Steps

To ensure the right amount of fees are distributed to the protocol when a user initiates a flashloan change the current calculations for token0 and token 1 to the following:

```diff
    function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external override lock {
	…
            if (paid0 > 0) {
-                uint256 pFees0 = feeProtocol == 0 ? 0 : paid0 / feeProtocol; 
+                uint256 pFees0 = feeProtocol == 0 ? 0 : paid0 * feeProtocol / 100; 

                if (uint128(pFees0) > 0) $.protocolFees.token0 += uint128(pFees0);
                $.feeGrowthGlobal0X128 += FullMath.mulDiv(paid0 - pFees0, FixedPoint128.Q128, _liquidity);
            }
            if (paid1 > 0) {
-                uint256 pFees1 = feeProtocol == 0 ? 0 : paid1 / feeProtocol; 
+                uint256 pFees1 = feeProtocol == 0 ? 0 : paid1 * feeProtocol / 100; 

                if (uint128(pFees1) > 0) $.protocolFees.token1 += uint128(pFees1);
                $.feeGrowthGlobal1X128 += FullMath.mulDiv(paid1 - pFees1, FixedPoint128.Q128, _liquidity);
            }

…
}

``` 



## Assessed type

Math
malicious rental owner can rob bidders of the currency amounts sent in via older bids. 
Audit: 2024-10-coded-estate-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L708-L709


# Vulnerability details

## Impact
malicious rental owner can rob bidders of the currency amounts sent in via older bids. 

## Proof Of Concept 
- user adds a bid via `setbidtobuy()` with token.sell.denom for currency A 
- in the middle of bidding, the token owner calls `setlistforsell()` and changes token.sell.denom to currency B
- user attempts to add another bid via `setbidtobuy()` with token.sell.denom as currency B
- because user had a previously acvtive bid before, the logic runs a for loop to iterate through previous bids and find the old bid. 
https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L657-L664
 ```rust
        for (i, item) in token.bids.iter().enumerate() {
            if item.address == info.sender.to_string()
            {
                position = i as i32;
                amount = item.offer.into();
                break;
            }
        }
```
this old bid is found and `position` is set to the index value. This means the if `poisition == -1`  if  block does not execute/is skipped but the else block which deletes the older bid is executed 

https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L694-L697
```rust 
        else {
            // update the approval list (remove any for the same spender before adding)
            token.bids.retain(|item| item.address != info.sender);
        }
```

This is then saved into the token storage and now because position is not equal to -1, money is attempted to be sent back to the bidder as refund for its previous bid. See below 

https://github.com/code-423n4/2024-10-coded-estate/blob/97efb35fd3734676f33598e6dff70119e41c7032/contracts/codedestate/src/execute.rs#L700-L710
```rust 
        if position != -1 && (amount > Uint128::from(0u64)) {
            Ok(Response::new()
            .add_attribute("action", "setbidtobuy")
            .add_attribute("sender", info.sender.clone())
            .add_attribute("token_id", token_id)
            .add_message(BankMsg::Send {
                to_address: info.sender.to_string(),
                amount: vec![Coin {
                    denom: token.sell.denom,
                    amount: amount,
                }],
            }))
        }
```

This send action will send the currency amount for older bid while the denom is currency B. Like instead of sending back 10 units of currency A for example, it will send back 10 units of currency B. Even though both currencies may not be of the same value!

This can be exploited by a malicious rental owner to accept bids, change currency and rob bidders of their value used in the older bids. 

## Recommened Mitigation 
ensure that the refund is being sent with the correct curren
Attackers can steal funds from this contract
Audit: 2024-10-coded-estate-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-coded-estate/blob/main/contracts/codedestate/src/execute.rs#L646-L720


# Vulnerability details


## Description
The Coded Estate is a decentralized platform, anybody can mint his real estate NFT then they can sell or rent it.
the `execute.rs#mint()` function is the entry point to the protocol 
One of the things that get filled in the mint is the `sell` struct
```rust
let sell = Sell {
            islisted:None,
            auto_approve:false,
            price:0u64,
            denom:"ibc/F082B65C88E4B6D5EF1DB243CDA1D331D002759E938A0F5CD3FFDC5D53B3E349".to_string(),            
        };
```
As you can tell it sets the denom to `ibc/....`
Now, the attacker can just call `setbidtobuy()` function and transfer any amount of token.
Because it doesn't check if the token is listed or not.

The attacker will wait for the NFT owner to update the `sell.denom` by triggering `setlistforsell()` to a token that has more value in the market. 

Now, the attacker can invoke `setbidtobuy()` again so he will receive back his paid amount but in terms of the new token, because the logic takes the `denom` from `sell` struct and not from the `bids`  

## Impact

Attackers can steal funds from this contract (for a lesser amount) 
 
## Tools Used

Manual Review

## Recommended Mitigation Steps

- Check `token.sell.islisted` value in `setbidtobuy()` and revert if the token is not for sell
- When you send funds take `denom` from the `bids`  


## Assessed type

Other
Attackers can steal any NFT that is currently under sale
Audit: 2024-10-coded-estate-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-coded-estate/blob/main/contracts/codedestate/src/execute.rs#L365-L417
https://github.com/code-423n4/2024-10-coded-estate/blob/main/contracts/codedestate/src/execute.rs#L694-L697


# Vulnerability details


## Description

Any address that has approval can call `execute.rs#transfer_nft()` successfully because it ensures he has permissions using `check_can_send()`. Also if the address `recipient` has bids in this NFT it will send the funds to the previous owner 
```rust
File: execute.rs

365:     fn transfer_nft(
/**CODE**/
370:         recipient: String,
371:         token_id: String,
372:     ) -> Result<Response<C>, ContractError> {
373:         let mut token = self.tokens.load(deps.storage, &token_id)?;
374:         // ensure we have permissions
375:         self.check_can_send(deps.as_ref(), &env, &info, &token)?;
 /**CODE**/
384:         for (i, item) in token.bids.iter().enumerate() {
385:             if item.address == recipient.to_string()
386:             {
387:                 position = i as i32;
388:                 amount = item.offer.into();
389:                 break;  
390:             }
...

```
When NFT is on sell with `token.sell.auto_approve == true`
any user can invoke `execute.rs#setbidtobuy()` two times and he will end up with an approval that never expired 
Because the first call will user will transfer the funds to the contract in exchange for getting approval (so he can transfer the NFT).
However, the next call to `setbidtobuy()` will delete the user from the `bids` vec and send all the funds back to him. But it doesn't delete the approval in that second transaction 

```rust
File: execute.rs#setbidtobuy()

693:         else {
694:             // update the approval list (remove any for the same spender before adding)
695:             token.bids.retain(|item| item.address != info.sender);
696:         }

```
Using this advantage attackers will take the approval and steal NFT  with all the funds under it

## Impact

Attackers can steal any NFT that is currently under sale.

## Tools Used

Manual Review

## Recommended Mitigation Steps
```diff
File: execute.rs#setbidtobuy()

693:         else {
694:             // update the approval list (remove any for the same spender before adding)
695:             token.bids.retain(|item| item.address != info.sender);
+                 token.approvals.retain(|apr| apr.spender != info.sender);
696:         }

```



## Assessed type

Other