xanoutas/solidity-security-findings huggingface dataset:
When minting or increasing liquidity through NonfungiblePositionManager, excess ETH sent to the contract is not refunded
Audit: 2024-10-ronin-findings
Severity: HIGH

# 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#L181


# Vulnerability details

When minting or increasing liquidity through `NonfungiblePositionManager`, excess ETH sent to the contract is not refunded to the caller and stays in the contract. This results in a permanent loss for the user.
## Proof of Concept
The `NonfungiblePositionManager` serves as the primary entry point for users interacting with pools in the protocol. Users can mint new positions or increase liquidity in existing ones . 

When either the functions [mint](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/NonfungiblePositionManager.sol#L181) or [increaseLiquidity](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/NonfungiblePositionManager.sol#L246) is called, these functions trigger [addLiquidity](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/base/LiquidityManagement.sol#L47), which in turn invokes the mint function on the respective pool. This interaction triggers  [katanaV3MintCallback](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/base/LiquidityManagement.sol#L25), which executes the [pay](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/base/PeripheryPayments.sol#L48) function for each token in the pool to initiate the token  transfers

```solidity

    function pay(
        address token,
        address payer,
        address recipient,
        uint256 value
    ) internal {
        if (token == WETH9 && address(this).balance >= value) {
            // pay with WETH9
            IWETH9(WETH9).deposit{value: value}(); // wrap only what is needed to pay
            IWETH9(WETH9).transfer(recipient, value);
        } else if (payer == address(this)) {
            // pay with tokens already in the contract (for the exact input multihop case)
            TransferHelper.safeTransfer(token, recipient, value);
        } else {
            // pull payment
            TransferHelper.safeTransferFrom(token, payer, recipient, value);
        }
    }
```


If one of the tokens in the pool is WETH `(WETH9)` and the user send ETH with his call to be used for deposit, the `NonfungiblePositionManager` will wrap the exact amount of required ETH to WETH and transfer it to the pool.

It is nearly impossible to send in the exact amount of ETH to be exchanged to WETH since the current price in the pool at the moment the transaction will be executed might have changed since the submission of the transaction. This means, to ensure
Use of `slot0` to get `sqrtPriceX96` can lead to price manipulation.
Audit: 2024-10-ronin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description

In `KatanaV3Pool`, the function `swap` use `UniswapV3.slot0` to get the value of `sqrtPriceX96`, which is used to perform the swap. However, the `sqrtPriceX96` is pulled from `Uniswap.slot0`, which is the most recent data point and can be manipulated easily via `MEV` bots and `Flashloans` with sandwich attacks; which can cause the loss of funds when interacting with the `Uniswap.swap` function.

## Proof of Concept
See [here](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/periphery/lens/MixedRouteQuoterV1.sol#L53C3-L62C73) and [here](https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L727C7-L735C6)   where the `sqrtPriceX96` can be manipulated during a `swap`.

An attacker can simply manipulate the `sqrtPriceX96` and if the `Uniswap.swap` function is called with the `sqrtPriceX96`, the token will be bought at a higher price and the attacker would run the transaction to sell; thereby earning gains but causing a loss of funds to whoever called those functions.

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








## Assessed type

Oracle
V3SwapRouter.V3SwapExactOutput() has the wrong implementation since it applies the 
Audit: 2024-10-ronin-findings
Severity: HIGH

# Lines of code

V3SwapRouter.sol#L191-L202


# Vulnerability details

## Proof of Concept
First of all, let's understand how a multi-hop exact output swap should work. 

In a multi-hop exact output swap, we specify the exact amount of tokens we want to receive as the final output in the last pool, and the swap logic works backward from the last pool to calculate the input needed at each preceding pool to meet this exact output requirement.

The following specifies ```amountOut``` as the exact amount for the output token for the LAST pool:

```javascript

function v3SwapExactOutput(
    address recipient,
    uint256 amountOut,
    uint256 amountInMaximum,
    bytes calldata path,
    address payer
  ) internal {
    maxAmountInCached = amountInMaximum;
    (int256 amount0Delta, int256 amount1Delta, bool zeroForOne) =
      _swap(-amountOut.toInt256(), recipient, path, payer, false);        // first argument is the exact output amount as ```-amountOut```

    uint256 amountOutReceived = zeroForOne ? uint256(-amount1Delta) : uint256(-amount0Delta);

    if (amountOutReceived != amountOut) revert V3InvalidAmountOut();

    maxAmountInCached = DEFAULT_MAX_AMOUNT_IN;
  }
```

It calls _swap() using a negative integer ```-amountOut``` as the first argument. 

The problem lies in the implementation in _swap(): 

```javascript
function _swap(int256 amount, address recipient, bytes calldata path, address payer, bool isExactIn)
    private
    returns (int256 amount0Delta, int256 amount1Delta, bool zeroForOne)
  {
    (address tokenIn, uint24 fee, address tokenOut) = path.decodeFirstPool();

    zeroForOne = isExactIn ? tokenIn < tokenOut : tokenOut < tokenIn;

    (amount0Delta, amount1Delta) = IKatanaV3Pool(computePoolAddress(tokenIn, tokenOut, fee)).swap( // slippage control is relaxed
      recipient, zeroForOne, amount, (zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1), abi.encode(path, payer)
    );
  }
```

Instead of passing the first argument ```amount```, which is supposed to be the exact amount for the output token for the LAST pool, it applies it as the argument for calling the swap function of the FIRST pool. As a result, This results in the first pool using ```amountOut``` as the required output, causing incorrect calculations across the multi-hop path.

Impact: loss of funds due to wrong calculation or non-function of the transaction. 

## Recommended Mitigation Steps
Start with the Last Pool: Decode and apply amountOut to the last pool in the path, calculating backward to determine the required input for each previous pool.




## Assessed type

Math
Unvalidated `msg.value` in `AggregateRouter::execute()` Allows Unauthorized `WRAP_ETH` and `UNWRAP_WETH` Operations, Potentially Leading to Contract Fund Drain
Audit: 2024-10-ronin-findings
Severity: HIGH

# Lines of code

https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/AggregateRouter.sol#L37
https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/base/Dispatcher.sol#L164-L180


# Vulnerability details

## Summary
A vulnerability exists in the `AggregateRouter::execute()` function when handling the `Commands.WRAP_ETH command` on the Dipacther.sol. This vulnerability allows an attacker to attempt wrapping Ether without sending the corresponding amount of Ether (msg.value), potentially leading to unexpected behavior or financial loss.

## Vulnerability Details
The dispatch function within the Dispatcher contract, specifically when processing the Commands.WRAP_ETH command.
```javascript
function dispatch(bytes1 commandType, bytes calldata inputs) internal returns (bool success, bytes memory output) {
    uint256 command = uint8(commandType & Commands.COMMAND_TYPE_MASK);
    //... 
    // @audit msg.value not checked
    else if (command == Commands.WRAP_ETH) {
        // equivalent: abi.decode(inputs, (address, uint256))
        address recipient;
        uint256 amountMin;
        assembly {
          recipient := calldataload(inputs.offset)
          amountMin := calldataload(add(inputs.offset, 0x20))
        }
        Payments.wrapETH(map(recipient), amountMin);
}
```
```javascript
  function wrapETH(address recipient, uint256 amount) internal {
    if (amount == Constants.CONTRACT_BALANCE) {
      amount = address(this).balance;
    } else if (amount > address(this).balance) {
      revert InsufficientETH();
    }
    if (amount > 0) {
      WETH9.deposit{ value: amount }();
      if (recipient != address(this)) {
        WETH9.transfer(recipient, amount);
      }
    }
  }

```
The attacker prepares the `WRAP_ETH command` with a fake amount.
The attacker can call the [execute](https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/AggregateRouter.sol#L37) function without sending the required Ether (`msg.value`). Since the `dispatch` function does not validate `msg.value`, the `wrapETH` function could be executed with an incorrect amount, potentially leading to unintended WETH transfers. A similar issue exists with the `UNWRAP_WETH` command, which could lead to the contract's funds being completely drained.

## Impact
this could lead to incorrect Ether wrapping, resulting in financial discrepancies or loss.

## Tools Used
Manual Review

## Recommendations
Add a check to ensure that the amount to be wrapped does not exceed `msg.value`.

Use msg.value directly if applicable, to ensure consistency.


## Assessed type

ETH-Transfer
Liquidity migrations from v2 to v3 for LPers will not work
Audit: 2024-10-ronin-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Proof of Concept
For migrations of Liquidity from the v2 katana pools contract to the v3 contracts, LPers can migrate such liquidity by executing the `migrate` function of the `V3Migrator` contract which burns the liquidity from the msg.sender by transferring it to the `V3Migrator` contract and then burning it before ultimately minting a new LP position to the user for v3 pools. However, due to a wrong recipient in the `safeTransferFrom` call argument, all liquidity migration calls from v2 to v3 will fail

```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); // @audit recipient issue here

@>    (uint256 amount0V2, uint256 amount1V2) = IKatanaV2Pair(params.pair).burn(address(this)); // @audit issue here

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

    // if necessary, clear allowance and refund dust
    if (amount0V3 < amount0V2) {
      if (amount0V3 < amount0V2ToMigrate) {
        TransferHelper.safeApprove(params.token0, nonfungiblePositionManager, 0);
      }

      uint256 refund0 = amount0V2 - amount0V3;
      if (params.refundAsETH && params.token0 == WETH9) {
        IWETH9(WETH9).withdraw(refund0);
        TransferHelper.safeTransferETH(msg.sender, refund0);
      } else {
        TransferHelper.safeTransfer(params.token0, msg.sender, refund0);
      }
    }
    if (amount1V3 < amount1V2) {
      if (amount1V3 < amount1V2ToMigrate) {
        TransferHelper.safeApprove(params.token1, nonfungiblePositionManager, 0);
      }

      uint256 refund1 = amo
V3SwapRouter.sol#checkAuthorizedV3Path cannot decode all token involved
Audit: 2024-10-ronin-findings
Severity: MEDIUM

# Lines of code

https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/modules/katana/v3/V3SwapRouter.sol#L143
https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/base/Dispatcher.sol#L51
https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/modules/katana/v3/V3SwapRouter.sol#L93
https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/libraries/Constants.sol#L36
https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/modules/katana/v3/V3Path.sol#L14
https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/modules/katana/v3/V3SwapRouter.sol#L48


# Vulnerability details

# Proof of concept

The code add [more authorization](https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/modules/katana/v3/V3SwapRouter.sol#L143) based on the uniswap v3 code in V3SwapRouter.sol

```solidity
  function checkAuthorizedV3Path(bytes calldata path) internal view { // @audit this must have bugs
    uint256 length = path.length / Constants.NEXT_V3_POOL_OFFSET + 1;
    address[] memory tokens = new address[](length);
    for (uint256 i; i < length; ++i) {
      tokens[i] = path.decodeFirstToken();
      if (i + 1 < length) path = path.skipToken();
    }
    if (!IKatanaGovernance(KATANA_GOVERNANCE).isAuthorized(tokens, msg.sender)) revert V3UnauthorizedSwap();
  }
```

this is used for path validation in [Dispatcher code](https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/base/Dispatcher.sol#L51)

```solidity
bytes calldata path = inputs.toBytes(3);
    address payer = payerIsUser ? lockedBy : address(this);
    v3SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer);
    checkAuthorizedV3Path(path); // place the check here to avoid stack too deep error
}
```

However, the checkAuthorizedV3Path fails to correctly get how many token involved.

A reference implementation can be [V3SwapRouter.sol#v3SwapExactInput](https://github.com/ronin-chain/katana-operation-contracts/blob/27f9d28e00958bf3494fa405a8a5acdcd5ecdc5d/src/aggregate-router/modules/katana/v3/V3SwapRouter.sol#L93)

```solidity
  function v3SwapExactInput(
    address recipient,
    uint256 amountIn,
    uint256 amountOutMinimum,
    bytes calldata path,
    address payer
  ) internal {
    // use amountIn == Constants.CONTRACT_BALANCE as a flag to swap the entire balance of the contract
    if (amountIn == Constants.CONTRACT_BALANCE) {
      address tokenIn = path.decodeFirstToken();
      amountIn = ERC20(tokenIn).balanceOf(address(this));
Oracle Miscalculation During Zero Liquidity Periods Leads to Inaccurate AMM Data
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#L40


# Vulnerability details

## Description

The `transform` function in the Oracle system of Katana V3 is responsible for updating key observations of pool states over time. These updates are critical for accurate price feeds, liquidity calculations, and other dependent mechanisms such as reward distributions.

However, an issue arises when the `liquidity` parameter is zero. The current implementation uses a ternary operator to prevent a division-by-zero error, but this leads to incorrect accumulation of the `secondsPerLiquidityCumulativeX128` value during periods where the pool has no liquidity.

### Problematic Code
```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
    });
  }
```

### Issue

The critical line is:
```solidity
((uint160(delta) << 128) / (liquidity > 0 ? liquidity : 1))
```
When `liquidity` is zero, the code divides by `1` instead of `0` to avoid a runtime error. While this prevents the contract from reverting, it introduces a logical flaw by accumulating time as if there was 1 unit of liquidity, even when the pool is empty. This results in incorrect values being recorded in the Oracle, leading to inaccurate liquidity data.

## Impact

1. **Inaccurate Liquidity Tracking**: During zero liquidity periods, the Oracle erroneously records liquidity, causing an overestimation of time-weighted average liquidity.
   
2. **Misleading Oracle Data**: Systems depending on accurate liquidity data from this Oracle, such as trading platforms or risk assessment algorithms, could make faulty decisions based on inflated liquidity metrics.

### Scenario

Imagine a pool with the following liquidity changes:
- **T=0**: Liquidity is 1000
- **T=100**: Liquidity drops to 0
- **T=200**: Liquidity increases to 2000
- **T=300**: An observation is recorded

With the current implementation:
- Between **T=0 and T=100**, the liquidity is tracked correctly.
- Between **T=100 and T=200**, the Oracle incorrectly assumes 1 unit of liquidity and accumulates data.
- Between **T=200 and T=300**, the liquidity is tracked correctly again.

This results in an overestimated average liquidity for the entire period.

## Fix

The solution is to modify the `transform` function to skip the accumulation of `secondsPerLiquidityCumulativeX128` when liquidity is zero.

### Proposed Fix:
```solidity
function transform(
    Observation memory last,
    uint32 blockTi
immutable variables are not initialised, breaks functionality
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-L114
https://github.com/ronin-chain/katana-v3-contracts/blob/03c80179e04f40d96f06c451ea494bb18f2a58fc/src/core/KatanaV3Pool.sol#L117-L131


# Vulnerability details

## Proof of Concept
In the `KatanaV3Pool::constructor`, the factory variable is initialised with address(1). 

```
  constructor() {
    // disable immutables initialization
    factory = address(1);
  }
```
At this point the state variables are not yet initialized. 

example: The below two state variables are not yet initialized. 

```
 address public override governance;
 address public override positionManager;

```

But, these variables are initialized via `initializeImmutables` function as below.

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

But, the initialise function is disabled because of the require validation in the function that expects the factory address to be address(0), while the factory was assigned adddress(1) in the constructor,

As as result the `initializeImmutables` function will not work as expected and would revert.

## Recommended Mitigation Steps

In the constructor, initialise the factory as zero address.
 factory = address(0);


## Assessed type

Invalid Validation