xanoutas/solidity-security-findings huggingface dataset:
Usage of slot 0 to get sqrtPriceLimitX96 can be easily manipulated
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

The `sqrtPriceX96` is derived from `slot0(), which can be easily manipulate. 


  struct Slot0 {
    // the current price
    uint160 sqrtPriceX96;
    // the current tick
    int24 tick;
    // the most-recently updated index of the observations array
    uint16 observationIndex;
    // the current maximum number of observations that are being stored
    uint16 observationCardinality;
    // the next maximum number of observations to store, triggered in observations.write
    uint16 observationCardinalityNext;
    // the numerator of the current protocol fee which is a ratio of the swap fee
    uint8 feeProtocolNum;
    // the denominator of the current protocol fee which is a ratio of the swap fee
    uint8 feeProtocolDen;
    // whether the pool is locked
    bool unlocked;
  }

This report below inspired this bug report and it is similar to this situatiom
Example: https://solodit.cyfrin.io/issues/h-02-use-of-slot0-to-get-sqrtpricelimitx96-can-lead-to-price-manipulation-code4rena-maia-dao-ecosystem-maia-dao-ecosystem-git

## Recommended Mitigation Steps
Use the `TWAP` function to get the value of `sqrtPriceX96`.



## Assessed type

Oracle
Initialization Deadlock in KatanaV3Pool Due to Conflicting Constructor and initializeImmutables Logic
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Summary

The KatanaV3Pool contract has an initialization deadlock where the constructor sets `factory = address(1)` while `initializeImmutables()` requires `factory == address(0)` to proceed. This creates a paradox. The initialization protection mechanism conflicts with the proxy pattern design, creating a deadlock where the very check meant to protect initialization makes it impossible.

```solidity
constructor() {
    factory = address(1);  // Sets factory to 1
}

function initializeImmutables(...) public virtual override {
    require(factory == address(0), "AII");  // Requires factory to be 0
    // ... initialization logic that can never be reached
}
```

## Impact
- Pool initialization is permanently blocked
- Proxy deployment pattern becomes unusable
- Contract parameters cannot be set
- Pool cannot be used for its intended purpose


The problem stems from a contradiction in the initialization logic. The constructor immediately sets `factory = address(1)`, but the `initializeImmutables` function requires `factory == address(0)` to proceed. This creates a logical impossibility - the initialization function can never be called successfully because its primary guard condition can never be satisfied.

## Scenario
Let's walk through the execution flow:

1. When the contract is deployed, the constructor executes:
```solidity
constructor() {
    factory = address(1);
}
```
This permanently sets the factory address to 1. The comment suggests this is intended to "disable immutables initialization", but this creates issues with the proxy pattern implementation.

2. Later, when someone attempts to initialize the pool through `initializeImmutables`, the first check is:
```solidity
require(factory == address(0), "AII");
```
This check will always fail because `factory` is already set to `address(1)`. The error "AII" (Already Initialized Immutables) will be thrown in every case.

This becomes particularly problematic when considering the contract's intended usage with proxy patterns, as indicated by the comment:
```solidity
// These below immutable constants are set when deploying the beacon proxy of the pool and
// cannot be changed unless upgraded.
```

## Fix
Remove the constructor initialization of factory since it's breaking the proxy initialization pattern:

```solidity
// BEFORE - Problematic constructor
constructor() {
    // disable immutables initialization
    factory = address(1);  // This prevents initialization
}

// AFTER - Fixed constructor
constructor() {
    // Remove factory initialization entirely
    // Let proxy handle initialization properly
}
```

The initialization check in `initializeImmutables()` can stay as is:

```solidity
func
slot0 Reliance in swap Allows Price Manipulation
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Summary
The KatanaV3Pool contract has a vulnerability that allows attackers to manipulate the price of tokens during swaps. This is because the contract uses the instantaneous price from slot0, which can be manipulated through MEV bots or flash loan attacks.

The bug lies in how it fetches and uses price data during a swap.

The contract stores critical pool state in a `Slot0` struct:

```solidity
struct Slot0 {
    // the current price
    uint160 sqrtPriceX96;
    // ... other pool state variables
}
Slot0 public override slot0;
```

The `swap()` function uses the `sqrtPriceX96` from `slot0` to calculate swap amounts and execute trades:

```solidity
function swap(
    // ... function parameters
) external override returns (int256 amount0, int256 amount1) {
    // ...

    Slot0 memory slot0Start = slot0; // Fetching price from slot0

    // ... logic for performing the swap using sqrtPriceX96 from slot0Start
}
```

The problem is that `slot0.sqrtPriceX96` reflects the current instantaneous price. This value can be manipulated by attackers using techniques like:

* **Miner Extractable Value (MEV) bots:** These bots can analyze pending transactions, identify profitable arbitrage opportunities, and insert their own transactions to manipulate the price before the victim's transaction is included in a block.
* **Flash Loan Attacks:** Attackers can take out a flash loan, use it to influence the token prices on the underlying DEX, and then execute a swap on `KatanaV3Pool` at the manipulated price, profiting from the artificial price difference.

## Scenario 

Imagine the fair price of token A is 10 tokens of B. An attacker might use a flash loan to briefly drive the price of token A up to 15 tokens of B on the underlying DEX.  Because `KatanaV3Pool` uses the instantaneous `slot0` price, the attacker could then swap a large amount of token B for token A at this inflated price. Once the attacker repays the flash loan, the price of token A returns to normal, leaving the attacker with a significant profit and the pool with a loss.


## Impact

* **Financial Loss:** The most direct impact is financial loss for users who interact with the `swap()` function. They may receive less of the desired token than expected due to the manipulated price.
* **Loss of Funds:** In extreme cases, if the price manipulation is severe, the pool could lose significant value, potentially draining its liquidity.

## Recommended Solution

To fix this vulnerability, the `KatanaV3Pool` contract should be modified to use a TWAP for price retrieval instead of relying on the instantaneous `slot0` price. This would involve:

* **Storing Price History:** The contract needs to store historical price data at regular intervals.
* **Calculating TWAP:**  Implement a function to calculate the TWAP over a defined period (e
Potential overestimation of liquidity
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Vulnerability details

Note about contest scope: `Oracle::transform` is being called from `KatanaV3Pool::_modifyPosition()` and `KatanaV3Pool::swap()` (line 308, line 693 in KatanaV3Pool.sol), and the KatanaV3Pool contract is in scope of this contest.

The function `Oracle::transform()` is used to update observations of the pool state over time, which are critical for accurate price feeds, liquidity calculations, and potentially reward distributions. However, there is an issue with the handling of the `liquidity` parameter when it is 0.

The current implementation uses a ternary operator to avoid division by zero, but this approach can lead to inaccurate accumulation of secondsPerLiquidityCumulativeX128 when the pool has no liquidity (line 40 katana-v3-contracts/src/core/libraries/Oracle.sol):

```solidity
+ ((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1)),
```

With 0 liquidity it is dividing by 1 instead of zero to avoid reverting, but this introduces a logical error in the calculations.

## Impact

Overestimation of time-weighted average liquidity:
During periods of zero liquidity, the function is still accumulating time as if there was 1 unit of liquidity.

Misleading Oracle Data: Any system relying on this oracle for liquidity data will receive inaccurate information. This could affect trading decisions, risk assessments, or any other mechanisms that depend on accurate liquidity reporting.

## Example

We have a pool that alternates between having liquidity and being empty:

T0: Liquidity = 1000

T100: Liquidity drops to 0

T200: Liquidity increases to 2000

T300: Observation is taken

With our current implementation:

From T0 to T100 and from T200 to T300 we accumulate 100 seconds worth of liquidity.

However from T100 to T200, we incorrectly accumulate 100 seconds as if there was 1 unit of liquidity.

This results in an overestimation of the average liquidity over this period.

## Recommended Mitigation Steps

Modify `Oracle::transform()` in order to skip accumulation if `liquidity` is 0, so that `secondsPerLiquidityCumulativeX128` is only accumulated if there is liquidity in the pool.


## Assessed type

Other
Locking of funds inside `PoolInitializer` contract.
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


# Vulnerability details

# **Description** 
`PoolInitializer` contract or any related contract using `createAndInitializePoolIfNecessary` function is not transfering funds anywhere. But This function is marked payable it means, this contract recieves funds using function but there is no withdraw mechanism or any fund handling property in contracts.

# **Impact** Locking of funds

# **Recommended mitigation** 
- Remove payable if Unnecessary.
- Add Withdrawal Mechanism if Needed


## Assessed type

Payable
Parameters mostly not checked for 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#L117-L131


# Vulnerability details

## Proof of Concept

The Parameters are mostly not checked for the initializeImmutables function.

Namely:
```Solidity
address token0_ parameter is not checked.
address token1_ parameter is not checked.
uint24 fee_ parameter is not checked.
int24 tickSpacing_ is not checked.
```

The impact this could cause is out of range bound errors when invalid values are entered for these variables. And having reverts that do not have sufficient information on what went wrong. Making the function calls unstable.

Vulnerable Function
```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_);


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

Erroneous Call
```Solidity
katanav3pool.initializeImmutables(address(0), address(type(uint256).max), address(type(uint256).max), uint24(type(uint24).max), int24(type(int24).max));
```

## Recommended Mitigation Steps
A checks for these unchecked parameters.
```Solidity
function initializeImmutables(address factory_, address token0_, address token1_, uint24 fee_, int24 tickSpacing_)
    public
    virtual
    override
  {
    require(factory_ == address(0), "AII");
+   require(token0_ != address(0), "Zero Address");
+   require(token1_ != address(0), "Zero Address");
+   require(fee_ > 0 && fee_ <= MAX_FEE, "More Money");
+   require(tickSpacing_ > 0 && <= MAX_TICKSPACING, "More Money");
  ...
  }
```


## Assessed type

Invalid Validation
Incorrect refund calculation in V3Migrator contract
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/V3Migrator.sol#L88


# Vulnerability details

```solidity
uint256 refund0 = amount0V2 - amount0V3;
```
## Vulnerability Details

There is a logic error in the refund calculation during the migration process from V2 to V3 liquidity pools. The current implementation calculates the refund amount based on the total liquidity burned from the V2 pool (`amount0V2`) instead of the amount intended to be migrated (`amount0V2ToMigrate`). This can lead to excessive refunds when users migrate only a portion of their liquidity.

If you look at this concept

 A user has 1000 tokens in a V2 liquidity pool.
 The user decides to migrate 50% of their liquidity to V3 (500 tokens).
 The V2 position is burned, returning 1000 tokens.
 The V3 position is created with 495 tokens (assuming some slippage).
 The current logic would refund 505 tokens (1000 - 495) instead of the correct 5 tokens (500 - 495).

This could be exploited by users to receive more tokens than intended, potentially draining liquidity from the protocol over time.

## Recommended Mitigation Step

The refund calculation should be based on the amount intended to be migrated (`amount0V2ToMigrate`) instead of the total amount received from burning V2 liquidity (`amount0V2`).

```solidity
uint256 refund0 = amount0V2ToMigrate - amount0V3;
```

This will ensures that only the dust amount from the intended migration is refunded, preventing excessive refunds in partial migration scenarios. The same fix should be applied to the calculation for `token1` as well.



## Assessed type

Error
If `feeProtocolNum` equals `feeProtocolDen`, it would mean 100% of the fees go to the protocol, which is likely unintended.
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Proof of Concept
The `initialize` function retrieves `feeProtocolNum` and `feeProtocolDen` from the factory contract using `IKatanaV3Factory(factory).feeAmountProtocol(fee)`. It then directly assigns these values to `slot0.feeProtocolNum` and `slot0.feeProtocolDen` without checking if `feeProtocolNum < feeProtocolDen`. This oversight in validation allows the pool to be initialized with a fee protocol ratio where `feeProtocolNum` could be greater than or equal to `feeProtocolDen`.

[KatanaV3Pool.sol#L253-L262](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L253-L267)
```solidity
/// @dev not locked because it initializes unlocked
function initialize(uint160 sqrtPriceX96) external override {
  require(slot0.sqrtPriceX96 == 0, "AI");

  int24 tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96);

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

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

  slot0 = Slot0({
    sqrtPriceX96: sqrtPriceX96,
    tick: tick,
    observationIndex: 0,
    observationCardinality: cardinality,
    observationCardinalityNext: cardinalityNext,
    feeProtocolNum: feeProtocolNum, // <= FOUND
    feeProtocolDen: feeProtocolDen, // <= FOUND
    unlocked: true
  });

  emit Initialize(sqrtPriceX96, tick);
}
```
Users may end up paying higher fees than expected or the protocol may collect more fees than intended. If a pool is initialized with an invalid fee protocol ratio (i.e., `feeProtocolNum >= feeProtocolDen)`, it could lead to incorrect fee calculations and distributions.

## Recommended Mitigation Steps
Add a check in the `initialize` function to ensure `feeProtocolNum < feeProtocolDen` before setting the values in `slot0`. Consider adding validation in the factory's `feeAmountProtocol` function to always return values satisfying this condition.


## Assessed type

Invalid Validation
Authorization function of the Governance can still restrict user from migrating v2 liquidity to v3 liquidity
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/V3Migrator.sol#L36


# Vulnerability details

# Proof of concept

The readme clearly say that once the pool is created and initialized,

[the governance should not be able to block user from migrating their V2 position to V3.](https://github.com/code-423n4/2024-10-ronin)

> If a liquidity pool (pair of tokens) is already open for liquidity provision on Katana V2, liquidity providers are expected to be able to migrate their liquidity to the corresponding pool on Katana V3 when it is created, without being restricted by the authorization function of the Governance.

Howver, this is not the case.

When migrating, [this is the code](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/V3Migrator.sol#L36):

```solidity
  function migrate(MigrateParams calldata params) external override {
    require(params.percentageToMigrate > 0, "Percentage too small");
    require(params.percentageToMigrate <= 100, "Percentage too large");

    // burn v2 liquidity to this address
    TransferHelper.safeTransferFrom(params.pair, msg.sender, params.pair, params.liquidityToMigrate);
    (uint256 amount0V2, uint256 amount1V2) = IKatanaV2Pair(params.pair).burn(address(this));

    // calculate the amounts to migrate to v3
    uint256 amount0V2ToMigrate = amount0V2.mul(params.percentageToMigrate) / 100;
    uint256 amount1V2ToMigrate = amount1V2.mul(params.percentageToMigrate) / 100;

    // approve the position manager up to the maximum token amounts
    TransferHelper.safeApprove(params.token0, nonfungiblePositionManager, amount0V2ToMigrate);
    TransferHelper.safeApprove(params.token1, nonfungiblePositionManager, amount1V2ToMigrate);

    // mint v3 position
    (,, uint256 amount0V3, uint256 amount1V3) = INonfungiblePositionManager(nonfungiblePositionManager).mint(
      INonfungiblePositionManager.MintParams({
        token0: params.token0,
        token1: params.token1,
        fee: params.fee,
        tickLower: params.tickLower,
        tickUpper: params.tickUpper,
        amount0Desired: amount0V2ToMigrate,
        amount1Desired: amount1V2ToMigrate,
        amount0Min: params.amount0Min,
        amount1Min: params.amount1Min,
        recipient: params.recipient,
        deadline: params.deadline
      })
    );
```

then the code hits the [NonfungiblePositionManager.sol#mint](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/NonfungiblePositionManager.sol#L188)

```solidity
  /// @inheritdoc INonfungiblePositionManager
  function mint(MintParams calldata params)
    external
    payable
    override
    checkDeadline(params.deadline)
    returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)
  {
    AuthorizationLib.checkPair(governance, params.token0, params.token1);
```

this is calling [che
Functions in AggregateRouter dont allow reverts
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/base/Dispatcher.sol#L31-L51


# Vulnerability details

## Proof of Concept

The `AggregateRouter` contract allows functions to fail and still have the other functions succeed. This is implemented via a failure check implemented in the code.

```solidity
if (!success && successRequired(command)) {
        revert ExecutionFailed({ commandIndex: commandIndex, message: output });
      }

function successRequired(bytes1 command) internal pure returns (bool) {
    return command & Commands.FLAG_ALLOW_REVERT == 0;
  }
```

So a user can decide to allow a function call to fail, by setting a particular bit in the command.

The `command` is passed in as a byte array, where each byte represents a single command. 
Of that 1 byte, 2 masks are used to define the operation. 

A `COMMAND_TYPE_MASK` of 0x3f (00111111) is used to define the operation. This is the last 6 bits of the byte. A `FLAG_ALLOW_REVERT` of 0x80 (10000000) is used to define the allow revert. This is the first bit of the byte.

So a user can presumable set an operation with the last 6 bits, and then set an allowrevert in the first bit, to allow execution of other functions. These two things are independent, and can be chosen by the user.

The issue is that the allowRevert functionality mostly doesn't work.

This is because in the `Dispatcher.sol` contract, the `success` value is updated in only one function out of the 14 implemented, and none of the function calls use `try-catch` pattern to check for success.

The `success` value is set in the contract to `true`, and then never updated.

```solidity
    success = true;


    // 0x00 <= command < 0x08
    if (command < Commands.FIRST_IF_BOUNDARY) {
      if (command == Commands.V3_SWAP_EXACT_IN) {
        // Reading inputs
        //@audit no try catch used. Reverts on failure and completely halts execution.
        v3SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer);
```

As seen in the snippet above, the swap function is called directly, so any reverts in the swap function reverts the main `execute` function, without giving any opportunity for a graceful failure. 

Thus the whole mechanism of using the first bit to define `FLAG_ALLOW_REVERT` has been ignored.

Only the implementation of the `BALANCE_CHECK_ERC20` command updates success correctly.

```solidity
success = (ERC20(token).balanceOf(owner) >= minBalance);
if (!success) output = abi.encodePacked(BalanceTooLow.selector);
```

Thus the dispatcher contract does not allow for any soft failures. Since the devs intend to allow for soft failures as shown by the usage of the `FLAG_ALLOW_REVERT` mask, this is an issue.

## Recommended Mitigation Steps

The function calls in the dispatcher should be done with `try-catch` statements. If failed, the `success` value should be updated accordingly and returned.


## Assessed type

Other