xanoutas/solidity-security-findings huggingface dataset:
Pool Initialization with Extreme Prices Can Break Core Pool Mechanics
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
`initialization` function accepts any `uint160` price value without validating against valid price bounds. When extreme prices are used, the tick calculation produces values outside the valid range, but these invalid values are still stored in the pool's state. When calling `TickMath.getTickAtSqrtRatio()` with these extreme values, it could result in ticks outside the valid range, this could lead to unexpected behavior in price calculations and tick transitions.
[KatanaV3Pool.sol.initialize#L246-L264](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L246-L267)
```solidity
function initialize(uint160 sqrtPriceX96) external override {
    require(slot0.sqrtPriceX96 == 0, "AI"); // <@Issue: Missing validation of sqrtPriceX96 bounds

    int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); // <@Issue: Can produce invalid ticks with extreme prices

    (uint16 cardinality, uint16 cardinalityNext) = observations.initialize(_blockTimestamp());

    (uint8 feeProtocolNum, uint8 feeProtocolDen) = IKatanaV3Factory(factory).feeAmountProtocol(fee);

    slot0 = Slot0({
        sqrtPriceX96: sqrtPriceX96, // <@Issue: Storing potentially unsafe price
        tick: tick, // <@Issue: Storing potentially invalid tick
        observationIndex: 0,
        observationCardinality: cardinality,
        observationCardinalityNext: cardinalityNext,
        feeProtocolNum: feeProtocolNum,
        feeProtocolDen: feeProtocolDen,
        unlocked: true
    });

    emit Initialize(sqrtPriceX96, tick);
}
```
The invalid state propagates through the system since core functions like `swap()` and `mint()` depend on valid tick/price relationships.

**The core issues are:-**
* No validation of input price bounds before initialization
* Potential for extreme prices to cause invalid tick calculations
* Storage of unsafe price and tick values in pool state
* Missing checks against `TickMath.MIN_SQRT_RATIO` and `TickMath.MAX_SQRT_RATIO`

This creates a path where extreme price values can compromise the pool's price/tick mechanics and affect core functionality like swaps and liquidity management.

**For reproduction:-**
1. Call `initialize()` with `minSqrtPrice` (4295128739)
2. The extremely low price causes issues with tick calculations
3. Same issue occurs with `maxSqrtPrice`
```solidity
function testExtremeInitialize() public {
    uint160 minPrice = 4295128739;
    uint160 maxPrice = 1461446703485210103287273052203988822378723970342;
    
    // Both calls succeed but create invalid state
    pool.initialize(minPrice); 
    pool.initialize(maxPrice);
    
    // Pool operations now break due to invalid ticks
    vm.expectRevert();
    pool.swap(...);
}
```
Connects to all price/tick dependent functions in Katan
Oracle's Transform Function Fails to Handle Zero Liquidity, Leading to Data Inaccuracy
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/libraries/Oracle.sol#L30


# Vulnerability details

### Vulnerability Overview

The `transform` function serves as a fundamental part of the Oracle system, crucial for maintaining accurate observations of the pool's state over time. These observations are essential for providing reliable price feeds, calculating liquidity, and managing reward distributions. However, there is a significant flaw in how the function handles the liquidity parameter, particularly when it is zero.

The implementation currently employs a ternary operator to prevent division by zero. While this avoids triggering an error, it introduces inaccuracies into the calculation of `secondsPerLiquidityCumulativeX128` during instances when the pool lacks liquidity.

---

### Analysis of the Code

The problematic line in the code is as follows:

```solidity
  function transform(Observation memory last, uint32 blockTimestamp, int24 tick, uint128 liquidity)
    private
    pure
    returns (Observation memory)
  {
    uint32 delta = blockTimestamp - last.blockTimestamp;
    return Observation({
      blockTimestamp: blockTimestamp,
      tickCumulative: last.tickCumulative + int56(tick) * delta,
      secondsPerLiquidityCumulativeX128: last.secondsPerLiquidityCumulativeX128
        + ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)),
      initialized: true
    });
  }
```

In cases where liquidity equals zero, the function resorts to dividing by one instead of zero. While this prevents the function from failing, it inadvertently introduces a logical error into the liquidity calculations.

---

### Consequences of the Flaw

The ramifications of this vulnerability are significant. When the liquidity is zero, the function continues to accumulate time as if one unit of liquidity exists. This leads to an exaggerated estimate of the time-weighted average liquidity.

Moreover, any system relying on this oracle for accurate liquidity data will receive misleading information. This distortion can have adverse effects on trading decisions, risk evaluations, and other mechanisms that depend on precise liquidity reporting.

---
### Illustrative Scenario

Imagine a pool that fluctuates between having liquidity and being empty. For instance, at time T=0, the pool has a liquidity of 1500. At T=120, liquidity drops to zero, then at T=240, liquidity increases to 2500. Finally, an observation is recorded at T=360.

Using the current implementation, the following occurs:

- From T=0 to T=120, the function accurately accumulates 120 seconds worth of liquidity.
- However, between T=120 and T=240, the function incorrectly continues to accumulate 120 seconds as if there was one unit of liquidity, despite the pool being empty.
- Lastly, from T=240 to T=360, it correctly accumulates another 120 seconds worth of liquidity.

As a result, this flawed logic leads to an overestimatio
Use of slot0 which is extremely manipulatable
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/base/PoolInitializer.sol#L30
https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/lens/MixedRouteQuoterV1.sol#L62
https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/lens/MixedRouteQuoterV1.sol#L101


# Vulnerability details

The use of `slot0` in getting important parameters like the `sqrtPricex96`, `tickBefore` and `tickAfter` is risky since slot0 is highly manipulatable

## Proof of Concept
Multiple places in this contract use `slot0` fro getting values of variables

for instance let's look at this function in the `PoolInitializer.sol`
```solidity
function createAndInitializePoolIfNecessary(address token0, address token1, uint24 fee, uint160 sqrtPriceX96)
    external
    payable
    override
    returns (address pool)
  {
...
      //@audit
      (uint160 sqrtPriceX96Existing,,,,,,,) = IKatanaV3Pool(pool).slot0();
      if (sqrtPriceX96Existing == 0) {
        IKatanaV3Pool(pool).initialize(sqrtPriceX96);
...
}
``` 
from the code above we can see that the pool is being intialized with a `sqrtPriceX96` from slot0 which is highly manipulable and as a result not advised.

## Recommended Mitigation Steps
use a TWAP to get these values.


## Assessed type

Other
Lack of `address(0)` in the `KatanaGovernance::_setFactory` function
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
The `KatanaGovernance::_setFactory` function is used to change the value of `_v2Factory` but depending on the current implementation, this variable can take on an `address(0)` which is an invalid address.
If `_v2Factory` is `address(0)` or is not defined correctly, calling the `KatanaGovernance::createPair` function will always fail and we will get other unexpected behaviour.
```solidity
    function _setFactory(address factory) private {
        // @audit lack of address(0) check
        _v2Factory = IKatanaV2Factory(factory);

        emit FactoryUpdated(_msgSender(), factory);
    }
```

## Impact
DOS and unattended behaviour, as some functions will always fail due to an invalid address.

## Recommended Mitigation Steps
```diff
    function _setFactory(address factory) private {
+       require(factory != address(0), "Invalid address!");
        _v2Factory = IKatanaV2Factory(factory);

        emit FactoryUpdated(_msgSender(), factory);
    }
```


## Assessed type

Invalid Validation
Liquidity providers can be restricted from burning their positions.
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


# Vulnerability details


## Vulnerability details
The `increaseLiquidity` function lacks access control, allowing a malicious user to prevent others from burning their liquidity positions.

## Proof of Concept
Since no access control on [increaseLiquidity](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/NonfungiblePositionManager.sol#L246) function , malicious user able to add a tiny amount of liquidity(1wei).

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

    ...

  position.liquidity += liquidity;

    emit IncreaseLiquidity(params.tokenId, liquidity, amount0, amount1);
  }

```
So user unable to [burn](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/NonfungiblePositionManager.sol#L410C1-L413C6) the position since position.liquidity is not equal to zero. (Malicious user front run the user burning LP position and add a tiny amount of liquidity.)

```solidity

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

  ...

   if (position.liquidity == 0) {
      delete _positions[params.tokenId];
      _burn(params.tokenId);
    }

    emit Collect(params.tokenId, recipient, amount0Collect, amount1Collect);
  }
  
```


## Impact
A malicious user can execute a griefing attack by adding a tiny amount of liquidity (1 wei), continuously preventing the burning of a user's position.
## Tools Used

Manual Review
## Recommended Mitigation Steps
Add a access control to `increaseLiquidity` function.



## Assessed type

Access Control
Price Manipulation Vulnerability in `PoolInitializer` Due to `sqrtPriceX96` Dependency
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/base/PoolInitializer.sol#L15-L35


# Vulnerability details

## Impact
The `PoolInitializer` contract initializes katana V3 pools using `sqrtPriceX96`, which is obtained from the `slot0` state variable of the pool. This reliance on `slot0` exposes the contract to price manipulation attacks via MEV bots and flash loans. Since `slot0` reflects the most recent price data, malicious actors can exploit this by executing sandwich attacks, temporarily altering prices to their advantage. If other contracts depend on the initialized prices for liquidity provisioning or swaps, it can lead to incorrect pricing, financial losses, or liquidity issues in the protocol.

## Proof of Concept
In the `PoolInitializer` contract, the following code initializes a pool with a given `sqrtPriceX96`:

```solidity
pool = IKatanaV3Factory(factory).getPool(token0, token1, fee);
if (pool == address(0)) {
    pool = IKatanaV3Factory(factory).createPool(token0, token1, fee);
    IKatanaV3Pool(pool).initialize(sqrtPriceX96);
}
```

The initialization process can be exploited by an attacker who manipulates `slot0` before the `createAndInitializePoolIfNecessary` function is called. If the price is artificially altered, subsequent transactions that depend on this initialized pool may be executed based on misleading price data, leading to potential losses for users interacting with the protocol.

For reference, you can view the relevant code [here](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/base/PoolInitializer.sol#L15-L35).

## Tools Used
- Manual code review

## Recommended Mitigation Steps
1. **Implement TWAP for Price Initialization**: Utilize a Time-Weighted Average Price (TWAP) mechanism to determine `sqrtPriceX96` for pool initialization, thereby reducing the risk of manipulation from transient price changes.
  
2. **Add Slippage Checks**: Introduce slippage detection mechanisms during pool initialization that prevent the pool from being created or initialized if the price deviates significantly from a historical average.



## Assessed type

MEV
Expired Accounts Retain Access Due to Flawed Expiry Validation in Authorization Logic
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept

In NonfungiblePositionManager.sol, the mint() function uses checkPair() to validate account permissions. Within checkPair(), the isAuthorized function checks if the calling account is authorized to interact with the tokens. However, within _isAuthorized, the following code introduces a potential vulnerability:

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

This line allows accounts with expired permissions to automatically be authorized, bypassing the $.allowed[account] whitelist check if block.timestamp > expiry.

Scenario:
1. Assume a user, userAccount, is initially added to the whitelist with an whitelistUntil is set to a future timestamp (e.g., expiry = block.timestamp + 1 week). This allows userAccount to interact with mint() function that check for authorization.
2. After a week, the expiry timestamp for userAccount passes. Normally, the expectation is that userAccount should no longer be authorized, and any further access should be restricted unless it’s re-whitelisted.
3. However, when mint() is called by userAccount:
checkPair() calls isAuthorized(), which in turn calls _isAuthorized(). _isAuthorized() checks block.timestamp > expiry, and since expiry has passed, userAccount is now unintentionally authorized because block.timestamp > expiry returns true. userAccount bypasses the $allowed[account] check and receives authorization due to the  block.timestamp > expiry condition.


Direct links to all referenced code in GitHub
1. https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/NonfungiblePositionManager.sol#L181-L230
2. https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/external/libraries/AuthorizationLib.sol#L7-L12
3. https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/governance/KatanaGovernance.sol#L249-L253
4. https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/governance/KatanaGovernance.sol#L375-L381

*Note
In checkPair() the same issue is present as it uses isAuthorized().

## Recommended Mitigation Steps
Modify the _isAuthorized function to explicitly disallow accounts whose expiry date has passed. For example:

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

 +   if (expiry == UNAUTHORIZED || block.timestamp > expiry) return false;
 +   if (expiry == AUTHORIZED) return true;
 
    return $.allowed[account];
  }
```



## Assessed type

Invalid Validation
KatanaV3Pool.sol cannot initializeImmutables
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

There is an issue of conflict in KatanaV3Pool.sol where factory is set as address(1) but the require statement requires it to be address 0.



```solidity
  constructor() {
    // disable immutables initialization
    factory = address(1);
  }


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

The constructor sets factory to address(1), so the initializeImmutables function will always revert.
This makes it impossible to initialize the immutables as intended.


## Recommended Mitigation Steps

Just change initializeImmutables to address(1) 

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




## Assessed type

Error
slot0 is easy to manupilate
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/lens/MixedRouteQuoterV1.sol#L62


# 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
slot0 price is easy to manupilate.

 function katanaV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes memory path) external view override {
    require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported
    (address tokenIn, address tokenOut, uint24 fee) = path.decodeFirstPool();
    CallbackValidation.verifyCallback(factory, tokenIn, tokenOut, fee);

    (bool isExactInput, uint256 amountReceived) =
      amount0Delta > 0 ? (tokenIn < tokenOut, uint256(-amount1Delta)) : (tokenOut < tokenIn, uint256(-amount0Delta));

    IKatanaV3Pool pool = getPool(tokenIn, tokenOut, fee);
  @>>  (uint160 v3SqrtPriceX96After, int24 tickAfter,,,,,,) = pool.slot0();
## Recommended Mitigation Steps
use twap inpace of slot0


## Assessed type

Context
Protocol Fee Ratio Bypass Leading to Excessive Fee Extraction
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Factory.sol#L46-L74
https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Factory.sol#L121-L126
https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Factory.sol#L115


# Vulnerability details

## Proof of Concept
The bug is in the protocol fee ratio validation in KatanaV3Factory.sol. Let's break it down: [#L46-L73](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Factory.sol#L46-L74), [#L121-L124](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Factory.sol#L121-L126)
```solidity
function initialize(address beacon_, address owner_, address treasury_) external {
    require(beacon == address(0), "KatanaV3Factory: ALREADY_INITIALIZED");

    // @Issue - No validation of fee protocol ratios in _enableFeeAmount calls
    // These hardcoded values could set invalid fee ratios during initialization
    _enableFeeAmount(100, 1, 5, 10);
    _enableFeeAmount(3000, 60, 5, 30);
    _enableFeeAmount(10000, 200, 15, 100);
}

function _enableFeeAmount(uint24 fee, int24 tickSpacing, uint8 feeProtocolNum, uint8 feeProtocolDen) private {
    // @Issue - Missing validation of feeProtocolNum < feeProtocolDen
    // This allows setting invalid fee ratios that could exceed 100%
    feeAmountTickSpacing[fee] = tickSpacing;
    feeAmountProtocol[fee] = Fraction(feeProtocolNum, feeProtocolDen);
    emit FeeAmountEnabled(fee, tickSpacing, feeProtocolNum, feeProtocolDen);
}
```
This allows setting invalid protocol fee ratios during initialization that could exceed 100% of trading fees, bypassing the safety checks present in the public interface. This can lead to excessive fee extraction from users and unfair fee distribution between liquidity providers and the protocol.

While there is a check in the public [enableFeeAmount():#L115](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Factory.sol#L115)
```solidity
require(feeProtocolNum < feeProtocolDen, "KatanaV3Factory: INVALID_FEE_PROTOCOL");
```
The private `_enableFeeAmount()` function used in `initialize()` lacks this validation, allowing invalid fee ratios to be set during initialization.

Protocol fees could be set higher than 100% during initialization and this breaks core business logic assumptions.

1. Protocol Risk:
* Invalid fee ratios where numerator ≥ denominator could be set during initialization
* This allows protocol fees to exceed 100% of trading fees
* Breaks core business assumptions about fee distribution between LPs and protocol

2. User Impact:
* Users could lose excessive amounts to protocol fees
* Trading becomes economically unviable if protocol takes majority