xanoutas/solidity-security-findings huggingface dataset:
NonfungiblePositionManager has increaseLiquidity Function Missing Authorization Check
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/NonfungiblePositionManager.sol#L245-L294


# Vulnerability details

## Impact
The increaseLiquidity function in the NonfungiblePositionManager contract lacks an authorization check, specifically the isAuthorizedForToken(params.tokenId) modifier. This omission allows any registered external caller to increase the liquidity of any NFT position without proper authorization, potentially leading to unauthorized manipulation of liquidity positions. In the NonfungiblePositionManager contract, the decreaseLiquidity function is protected by the isAuthorizedForToken(params.tokenId) modifier, ensuring that only the owner or an approved operator can decrease the liquidity of a specific NFT position. However, the increaseLiquidity function does not implement this modifier, leaving it unprotected. This inconsistency allows any registered user to call increaseLiquidity on any token ID, enabling them to manipulate the liquidity of positions they do not own or control. 

## Vulnerable Code
```Solidity
// https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/NonfungiblePositionManager.sol#L245-L294

  /// @inheritdoc INonfungiblePositionManager
  function increaseLiquidity(IncreaseLiquidityParams calldata params)
    external
    payable
    override
    checkDeadline(params.deadline)
    returns (uint128 liquidity, uint256 amount0, uint256 amount1)
  {
    Position storage position = _positions[params.tokenId];


    PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId];


    IKatanaV3Pool pool;
    (liquidity, amount0, amount1, pool) = addLiquidity(
      AddLiquidityParams({
        token0: poolKey.token0,
        token1: poolKey.token1,
        fee: poolKey.fee,
        tickLower: position.tickLower,
        tickUpper: position.tickUpper,
        amount0Desired: params.amount0Desired,
        amount1Desired: params.amount1Desired,
        amount0Min: params.amount0Min,
        amount1Min: params.amount1Min,
        recipient: address(this)
      })
    );


    bytes32 positionKey = PositionKey.compute(address(this), position.tickLower, position.tickUpper);


    // this is now updated to the current transaction
    (, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128,,) = pool.positions(positionKey);


    position.tokensOwed0 += uint128(
      FullMath.mulDiv(
        feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128, position.liquidity, FixedPoint128.Q128
      )
    );
    position.tokensOwed1 += uint128(
      FullMath.mulDiv(
        feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128, position.liquidity, FixedPoint128.Q128
      )
    );


    position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
    position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
    position.liquidity += liquidity;


    emit I
`initialize()` function lacks a check to ensure that the provided `sqrtPriceX96` is non-zero before assigning it to `slot0.sqrtPriceX96`
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L255-L264


# Vulnerability details

`initialize()` function in KatanaV3Pool.sol lacks a crucial check to ensure that the provided `sqrtPriceX96` parameter is non-zero. This oversight allows the pool to be initialized with an invalid price state, violating a fundamental invariant of the system.

The reason is that in the initialize function, we directly assign the `sqrtPriceX96` parameter to `slot0.sqrtPriceX96` without any validation: [KatanaV3Pool.sol#L255-L264](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L255-L264)
```solidity
slot0 = Slot0({
  sqrtPriceX96: sqrtPriceX96,  // <= @audit This line allows setting sqrtPriceX96 to zero
  // ...
});
```
This assignment happens without any prior check to ensure sqrtPriceX96 is non-zero.

## Impact
Some operations might consistently revert due to division by zero, effectively rendering the pool unusable.

## Proof of Concept
* Alice deploys a new KatanaV3Pool.
* Bob (maliciously or accidentally) calls `initialize()` with `sqrtPriceX96` set to 0
```solidity
pool.initialize(0)
```
* The pool is now initialized with a zero price, which is an invalid state.
* Charlie attempts to perform a swap on this pool, but the operation either reverts or produces incorrect results due to division by zero or other mathematical errors.

Minimal PoC to demonstrate the issue
```solidity
// Deploy KatanaV3Pool
KatanaV3Pool pool = new KatanaV3Pool();

// Initialize with zero price
pool.initialize(0);

// Check the initialized price
(uint160 sqrtPriceX96,,,,,,) = pool.slot0();
assert(sqrtPriceX96 == 0); // This assertion will pass, indicating the bug
```
## Recommended Mitigation Steps
Implement a check in the `initialize()` function to ensure `sqrtPriceX96` is non-zero
```diff
function initialize(uint160 sqrtPriceX96) external override {
  require(slot0.sqrtPriceX96 == 0, "AI");
+ require(sqrtPriceX96 > 0, "Invalid sqrtPriceX96");

  int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);

  // ...
}
```


## Assessed type

DoS
The `KatanaV3Pool.initializeImmutables` Function is Prone to Denial of Service (DoS) Attacks
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L122


# Vulnerability details

### Description of the Issue

The `initializeImmutables` function presents a significant attack vector through frontrunning, as we can see in the following code:

```solidity
/// @inheritdoc IKatanaV3PoolImmutables
function initializeImmutables(address factory_, address token0_, address token1_, uint24 fee_, int24 tickSpacing_)
    public
    virtual
    override
{
    require(factory == address(0), "AII"); 

    (factory, token0, token1, fee, tickSpacing) = (factory_, token0_, token1_, fee_, tickSpacing_);

    // cache these immutable addresses for gas savings when swaps, mints, or burns are performed
    governance = IKatanaV3Factory(factory_).owner();
    positionManager = IKatanaGovernance(governance).getPositionManager();

    maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing_);
}
```

### Analysis of the Function

This function is designed to initialise essential parameters for a new pool. The line:

```solidity
require(factory == address(0), "AII");
```

is critical because it ensures that the `factory` variable has not been set before proceeding. However, this logic creates a vulnerability as it allows an attacker to potentially execute a frontrunning attack.

### Attack Vector

1. **Initial Legitimate Transaction:** A user (Alice) attempts to create a new pool by invoking the `initializeImmutables` function with specific parameters.
2. **Frontrunning by the Attacker:** An attacker (Eve) monitors the mempool and observes Alice’s transaction. Eve can then submit her transaction first, manipulating the parameters such as `fee` or `tickSpacing_` to her advantage.
3. **Reversion of the Legitimate Transaction:** As a result of Eve's transaction being processed first, the `factory` no longer satisfies the condition `factory == address(0)`, causing Alice's transaction to fail with the revert reason "AII".

### Consequences

The ability for an attacker to modify parameters before the legitimate transaction is processed introduces a risk of Denial of Service (DoS) against genuine users. Legitimate users may be unable to complete their transactions due to reverts caused by the attacker’s actions, which disrupts the expected functioning of the protocol.

### Suggested Mitigation

To address this vulnerability, it is recommended to improve the access control logic surrounding the `initializeImmutables` function. Specifically, consider restricting who can invoke this function to mitigate the risk of frontrunning. 

Alternatively, explore mechanisms that reduce the likelihood of frontrunning attacks, such as implementing a commit/reveal strategy or introducing time delays for certain actions. These approaches could significantly diminish the attack surface while maintaining the functionality of the contract.


## Assessed type

DoS
Improper Validation of `tickSpacing` Parameter in `initializeImmutables` Function
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L124


# Vulnerability details

## Proof of Concept
There is a lack of proper validation for the `tickSpacing_` parameter passed to the function. This allows the `tickSpacing` state variable to be set to invalid values.

The `initializeImmutables` function is responsible for initializing the immutable state variables of the KatanaV3Pool contract, including `tickSpacing`. The function takes the `tickSpacing_` parameter as input and directly assigns it to the `tickSpacing` state variable without any validation.

If `tickSpacing` is set to 0, it can cause division by zero errors in functions like `snapshotCumulativesInside`, leading to transaction reverts and disrupting the normal operation of the contract.

If `tickSpacing` is set to a value greater than 16384, it can result in incorrect tick calculations, affecting the accuracy of liquidity provisioning, swap pricing, and other critical operations of the contract. [KatanaV3Pool.sol#L124](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L124)

```solidity
  function initializeImmutables(address factory_, address token0_, address token1_, uint24 fee_, int24 tickSpacing_)
    public
    virtual
    override
  {
    require(factory == address(0), "AII");

    (factory, token0, token1, fee, tickSpacing) = (factory_, token0_, token1_, fee_, tickSpacing_); // <= FOUND

    // cache these immutable addresses for gas savings when swaps, mints, or burns are performed
    governance = IKatanaV3Factory(factory_).owner();
    positionManager = IKatanaGovernance(governance).getPositionManager();

    maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(tickSpacing_);
  }
```
As seen above, the `tickSpacing_` parameter is directly assigned to the `tickSpacing` state variable without any validation checks. This allows an attacker or a contract deployer to set `tickSpacing` to any arbitrary value, including 0 or values greater than 16384, which can lead to unexpected behavior and potential exploits.

For example.

1. Alice deploys the KatanaV3Pool contract.
2. Alice calls the `initializeImmutables` function with the following parameters

   * `factory_`: Address of the factory contract
   * `token0_`: Address of token0
   * `token1_`: Address of token1
   * `fee_`: Valid fee value
   * `tickSpacing_`: 0 (invalid value)

3. The `tickSpacing` state variable is set to 0.
4. Bob interacts with the KatanaV3Pool contract and calls the `snapshotCumulativesInside` function, which relies on `tickSpacing` for calculations.
5. The `snapshotCumulativesInside` function encounters a division by zero error due to the invalid `tickSpacing` value, causing the transaction to revert and potentially disrupting the contract's functionality.

## Recommended Mitigation Steps
```solidity
function initializeImmutables(add
Users may perform trades or provide liquidity based on the assumption that the pool has a valid price.
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L249
https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L255-L256


# Vulnerability details

## Proof of Concept
The `initialize` function takes a `sqrtPriceX96` parameter, which represents the initial price of the pool. Users may perform trades or provide liquidity based on the assumption that the pool has a valid price.

The vulnerable code is
```solidity
int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); // <= FOUND
```
and

```solidity
slot0 = Slot0({
  sqrtPriceX96: sqrtPriceX96, // <= FOUND
  // ...
});
```
* The `sqrtPriceX96` value is used to calculate the `tick` using the `TickMath.getTickAtSqrtRatio` function.
* The `sqrtPriceX96` value is then assigned to `slot0.sqrtPriceX96` without any validation.

## Recommended Mitigation Steps
```solidity
function initialize(uint160 sqrtPriceX96) external override {
  require(slot0.sqrtPriceX96 == 0, "AI");
  require(sqrtPriceX96 > 0, "Invalid price"); // <= ADD THIS LINE

  int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);
  // ...
}
```



## Assessed type

Other
Leftover ETH by the previous liquidator provider might be stolen by another liquidator provider. 
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

/src/periphery/base/PeripheryPayments.sol#L50-L66


# Vulnerability details

## Proof of Concept
When a user calls ```NonfungibleManager.mint()``` to provide liquidity to a pool, the following ```pay``` function is called inside the ```LiquidityManagement.katanaV3MintCallback()``` function to transfer the payments of token0 and token1 from the user to the pool: 

```javascript
function pay(address token, address payer, address recipient, uint256 value) internal {
    if (token == WETH9 && address(this).balance >= value) {
      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 contract (for the exact input multihop case)
      TransferHelper.safeTransfer(token, recipient, value);
    } else {
      // pull payment
      TransferHelper.safeTransferFrom(token, payer, recipient, value);
    }
  }
```

However, when one of the tokens is WETH9, the user can send ETH instead and the first branch of the pay function will be executed, which swaps ETH for WETH9. Since it is hard for the user to know how much ETH he needs to send since the actual amounts of token0 and token1 will be calculated during the call based on tick range and current tick, there is very likely a chance that leftover ETH will be in the ```NonfungiblePositionManager``` contract.

Such leftover ETH can be stolen by another liquidity provider in two ways: 
1) call ```NonfungibleManager.mint()``` to use the leftover ETH to pay for the liquidity;
2) simply call ```NonfungibleManager.refundETH()``` to steal it.

Of course, the first user can write another function to wrap the ```NonfungibleManager.mint()``` and 
```NonfungibleManager.refundETH()``` into one transaction. However, no such document is provided in the source code and in my opinion, such refund should be done  as part of the functionality of 
```NonfungibleManager.mint()``` as suggested below.

## Recommended Mitigation Steps
add ```refundETH()``` as the last line for ```NonfungibleManager.mint()``` so that there is no leftover ETH In the ```NonfungiblePositionManager```.



## Assessed type

Invalid Validation
Minting with Extreme Tick Values
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L342-L408
https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L411-L427
https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/test/core/KatanaV3Pool.t.sol#L39-L106


# Vulnerability details

## Proof of Concept
`_updatePosition` function responsible for updating the tick information when a position is minted or modified does not have proper checks to handle extreme tick values. When a user mints a position with `tickLower` set to -887272 and `tickUpper` set to 887272, the `_updatePosition` function attempts to update the tick information using these extreme values. This leads to a revert, preventing the position from being minted.

In KatanaV3Pool.sol, the vulnerability is in the [_updatePosition function](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L342-L408)
```solidity
  function _updatePosition(address owner, int24 tickLower, int24 tickUpper, int128 liquidityDelta, int24 tick)
    private
    returns (Position.Info storage position)
  {
    position = positions.get(owner, tickLower, tickUpper);

    uint256 _feeGrowthGlobal0X128 = feeGrowthGlobal0X128; // SLOAD for gas optimization
    uint256 _feeGrowthGlobal1X128 = feeGrowthGlobal1X128; // SLOAD for gas optimization
    uint128 _maxLiquidityPerTick = maxLiquidityPerTick; // SLOAD for gas optimization

    // if we need to update the ticks, do it
    bool flippedLower;
    bool flippedUpper;
    if (liquidityDelta != 0) {
      uint32 time = _blockTimestamp();
      (int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128) =
        observations.observeSingle(time, 0, slot0.tick, slot0.observationIndex, liquidity, slot0.observationCardinality);

      // @Issue: The following lines don't handle the case when both tickLower and tickUpper are at extreme values
      flippedLower = ticks.update(
        tickLower,
        tick,
        liquidityDelta,
        _feeGrowthGlobal0X128,
        _feeGrowthGlobal1X128,
        secondsPerLiquidityCumulativeX128,
        tickCumulative,
        time,
        false,
        _maxLiquidityPerTick
      );
      flippedUpper = ticks.update(
        tickUpper,
        tick,
        liquidityDelta,
        _feeGrowthGlobal0X128,
        _feeGrowthGlobal1X128,
        secondsPerLiquidityCumulativeX128,
        tickCumulative,
        time,
        true,
        _maxLiquidityPerTick
      );

      int24 _tickSpacing = tickSpacing; // SLOAD for gas optimization
      if (flippedLower) {
        tickBitmap.flipTick(tickLower, _tickSpacing);
      }
      if (flippedUpper) {
        tickBitmap.flipTick(tickUpper, _tickSpacing);
      }
    }

    // ...
  }
```
The vulnerability is also present in the mint function, which [calls
LPholders can be prevented from burning their position NFTs
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/NonfungiblePositionManager.sol#L246
https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/NonfungiblePositionManager.sol#L410


# Vulnerability details



## Proof of Concept
Liquidity providers can  burn their position during `NonfungiblePositionManager.sol:collect()` if there's no liquidity and no tokens owed 

```
 function collect(CollectParams calldata params)
    external
    payable
    override
    isAuthorizedForToken(params.tokenId)
    returns (uint256 amount0, uint256 amount1)
  {
    .......

    // if there's no liquidity and no tokens owed, burn the position
 
 @>   if (position.liquidity == 0) {
      delete _positions[params.tokenId];
 @>     _burn(params.tokenId);
    }

....
  }
```

But `increaseLiquidity` has no access control, and anyone can increase a position's liquidity by any amount, no matter how little.

```
    function increaseLiquidity(
        IncreaseLiquidityParams calldata params
    )
        external
        payable
        override
        checkDeadline(params.deadline)
        returns (uint128 liquidity, uint256 amount0, uint256 amount1)
    {
  @>      Position storage position = _positions[params.tokenId];

        PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId];

        IRamsesV3Pool pool;
  @>      (liquidity, amount0, amount1, pool) = addLiquidity(
            AddLiquidityParams({
                token0: poolKey.token0,
                token1: poolKey.token1,
                tickSpacing: poolKey.tickSpacing,
                tickLower: position.tickLower,
                tickUpper: position.tickUpper,
                amount0Desired: params.amount0Desired,
                amount1Desired: params.amount1Desired,
                amount0Min: params.amount0Min,
                amount1Min: params.amount1Min,
                recipient: address(this),
                index: params.tokenId
            })
        );

// ...
 @>      position.liquidity += liquidity;

```

A malicious user in a griefing attack can continously increase liquduity by as low as 1 wei to continously dos attempt to burn a position

## Recommended Mitigation Steps
Ensure only a position owner can increase his liquidity.



## Assessed type

DoS
Expired Accounts Continue To be Authorized
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/governance/KatanaGovernance.sol#L378


# Vulnerability details

## Proof of Concept

`KatanaGovernance` may specify a configurable `expiry` data for authorized `account`s, however due to an invalid comparator, expired authorizations remain active for longer than intended:

```solidity
/**
 * @dev Checks if an account is authorized.
 * @param account The address of the account to check authorization for.
 * @return A boolean indicating whether the account is authorized or not.
 */
function _isAuthorized(Permission storage $, address account) private view returns (bool) {
    uint256 expiry = $.whitelistUntil;
    if (expiry == UNAUTHORIZED) return false;
@>  if (expiry == AUTHORIZED || block.timestamp > expiry) return true;

    return $.allowed[account];
}
```

Notice here that if the `expiry` is neither configured to `UNAUTHORIZED` nor `AUTHORIZED`, it will be compared against the `block.timestamp`:

```solidity
if (expiry == AUTHORIZED || block.timestamp > expiry) return true;
```

Here, we see that if the `block.timestamp` exceeds the configured `expiry`, **it will continue to be interpreted as authorized**.

## Recommended Mitigation Steps

Invert the comparison:

```diff
- if (expiry == AUTHORIZED || block.timestamp > expiry) return true;
+ if (expiry == AUTHORIZED || block.timestamp < expiry) return true;
```





## Assessed type

Invalid Validation