xanoutas/solidity-security-findings huggingface dataset:
Invariant Broken: Pools can be created with `upper tick <= lower tick`
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/pool.rs#L75


# Vulnerability details

## Impact 
The function `create_position()` is used to create a new position in the pool. The function takes lower tick and upper tick as input params to create a new position in the pool. The checks which verifies the ticks aren't invalid are: 

```rust
        assert_or!(low >= min_tick && low <= max_tick, Error::InvalidTick);
        assert_or!(up >= min_tick && up <= max_tick, Error::InvalidTick);
```

But the issue is this implementation is differs from the Uniswap V3 [implementation](https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L126) and allows ticks to be invalid as function accepts `low > up`

Uniswap V3 implementation:
```solidity
    /// @dev Common checks for valid tick inputs.
    function checkTicks(int24 tickLower, int24 tickUpper) private pure {
        require(tickLower < tickUpper, 'TLU');
        require(tickLower >= TickMath.MIN_TICK, 'TLM');
        require(tickUpper <= TickMath.MAX_TICK, 'TUM');
    }
```
The contest's README states the main invariants of the protocols as:
> Only valid tick ranges, and fee structures, are accessible.

This vulnerability allows the function caller to put invalid tick ranges by providing a `upper tick < lower tick`.

## Proof of concept
If we look at the function `create_position`
```rust
    pub fn create_position(&mut self, id: U256, low: i32, up: i32) -> Result<(), Revert> {
        ...
        ...

        let min_tick = tick_math::get_min_tick(spacing);
        let max_tick = tick_math::get_max_tick(spacing);
        
        assert_or!(low >= min_tick && low <= max_tick, Error::InvalidTick);
        assert_or!(up >= min_tick && up <= max_tick, Error::InvalidTick);

        // @audit check is incorrect. should be low <= up
        // and up >= low
        // or add : low <= up
        
        self.positions.new(id, low, up);
        Ok(())
    }
```
It clearly lacks the validation of `up > low` similar to Uniswap V3 implementation.

## Tools Used
Manual Review 

## Recommendation
We recommend to add the checks to verify that the `up > low` similar to Uniswap V3 core contract implementation like this:
https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L126










## Assessed type

Invalid Validation
Incorrect Role Update in `update_emergency_council_7_D_0_C_1_C_58` Function  of `lib.rs` to Disable the Pools in Case of an Emergency
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/lib.rs#L1121


# Vulnerability details

## Impact

The inability to update the `emergency_council` in `update_emergency_council_7_D_0_C_1_C_58` Function means that the contract's emergency management system will not function as intended. If an emergency occurs, the correct council may not have the authority to disable pools, which could lead to significant security or operational risks.

## Proof of Concept

The `update_emergency_council_7_D_0_C_1_C_58` function in the `lib.rs` contract of the Seawater package is intended to update the emergency council. The role of the emergency council is to disable the pools in case of an emergency. However, instead of correctly calling `self.emergency_council.set(emergency_council)` to update the emergency council, the function incorrectly calls `self.nft_manager.set(manager)`. As a result, the contract fails to update the `emergency_council` address, which is responsible for activating and disabling emergency mode functionality.
[pkg/seawater/src/lib.rs:update_emergency_council_7_D_0_C_1_C_58-L-L1121](https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/lib.rs#L1121)

```rust
1109 @audit=>   /// Update the emergency council that can disable the pools.
                #[allow(non_snake_case)]
                pub fn update_emergency_council_7_D_0_C_1_C_58(
                    &mut self,
                    manager: Address,
                ) -> Result<(), Revert> {
                    assert_eq_or!(
                        msg::sender(),
                        self.seawater_admin.get(),
                        Error::SeawaterAdminOnly
                    );

1121 @audit=>       self.nft_manager.set(manager);

                    Ok(())
                }

```

## Tools Used

Manual Review

## Recommended Mitigation Steps

It is recommended to modify the `update_emergency_council_7_D_0_C_1_C_58` function to ensure that it correctly updates the emergency council by calling `self.emergency_council.set(emergency_council)`. This will enable the contract to update the emergency council address and ensure that the emergency functionality works as expected.



## Assessed type

Error
Fix for fees calculations
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/tick.rs#L125-L246


# Vulnerability details

## Description
If we executed the test function `incr_position_fee_growth_tick` as the team said in Contest description, it will revert with ```called Result::unwrap() on an Err value: [70, 101, 101, 32, 103, 114, 111, 119, 116, 104, 32, 115, 117, 98, 32, 111, 118, 101, 114, 102, 108, 111, 119, 32, 116, 105, 99, 107]``` when we decode this error it will give us the message `Fee growth sub overflow tick`.This means the calculation of the fee growth isnt working correctly, aftrer some digging i found that Uniswap V3 code is with solidity < 0.8.0 so that overflows can happen, this design of uni isnt wrong and the fees calculated are correct because of this overflow. If we look at the function responsible for calculating feees in the context of superposition `get_fee_growth_inside` in tick.rs all substraction operations in this function are used with `checked_sub`.
The function checked_sub in rust performs the subtraction and also checks if an overflow or underflow occurs. If the subtraction would result in an overflow or underflow, checked_sub returns None instead of wrapping around like in the uniswap v3 code version. We can use instead wrapping_sub to mimic exactly how uniswap handles the fee calculation and see if it will solve the issue.
## Proof of Concept
To test our POC below please copy the function get_fee_growth_inside I put in Recommended Mitigation Steps and paste it instead of the incorrect one in tick.rs.

You can add the test function below in pkg/seawater/tests/lib.rs 
```rust
fn test_incr_position_fee_growth_tick_working() {

    test_utils::with_storage::<_, Pools, _>(
            Some(address!("feb6034fc7df27df18a3a6bad5fb94c0d3dcb6d5").into_array()), // sender
            Some(hashmap! {
                    "0x0000000000000000000000000000000000000000000000000000000000000000" => "0x000000000000000000000000feb6034fc7df27df18a3a6bad5fb94c0d3dcb6d5",
                    "0x2094fc11ba78df2b7ed9c7631680af7cf7bd4803bac5c7331fb2686e5c11e38e" => "0x00000000000000000000000000000000d3c21bcecceda10000003c00000bb801",
                    "0x2094fc11ba78df2b7ed9c7631680af7cf7bd4803bac5c7331fb2686e5c11e38f" => "0x000000000000000000000000000002946e618fc1c100eb2ece23c766dc7fe332",
                    "0x2094fc11ba78df2b7ed9c7631680af7cf7bd4803bac5c7331fb2686e5c11e390" => "0x00000000000000000000000000000000000003e430416229f385847e3438b6a0",
                    "0x2094fc11ba78df2b7ed9c7631680af7cf7bd4803bac5c7331fb2686e5c11e392" => "0x000000000000000000000000fffc729800000000000000000e000843c6b7e857",
                    "0x3c79da47f96b0f39664f73c0a1f350580be90742947dddfa21ba64d578dfe600" => "0x0000000000000000000000000000000000000000000000000000000000000000",
                    "0x441db1f4a15f63dd7988db6d518ae69b05a4bd5f528ae8589d82253ea85c9bcb" => "0x000000000000000000000000000000000000000000000000fffc7660ff
Creation of positions with invalid tick range
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/pool.rs#L75


# Vulnerability details

## Overview
A user can create a position in a pool using the function:

```solidity
    function mintPositionBC5B086D(
        address pool,
        int32 lower,
        int32 upper
    ) external returns (uint256 id);
```

This is present in the SeawaterAMM.sol:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/sol/SeawaterAMM.sol#L346

```solidity
    function mintPositionBC5B086D(address /* token */, int32 /* lower */, int32 /* upper */) external returns (uint256 /* id */) {
        directDelegate(_getExecutorPosition());
    }
```

This function delegate calls the ```mint_position_B_C5_B086_D()``` function in lib.rs:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/lib.rs#L495

```rust
    #[allow(non_snake_case)]
    pub fn mint_position_B_C5_B086_D(
        &mut self,
        pool: Address,
        lower: i32,
        upper: i32,
    ) -> Result<U256, Revert> {
        let id = self.next_position_id.get();
        self.pools.setter(pool).create_position(id, lower, upper)?;

        self.next_position_id.set(id + U256::one());

        let owner = msg::sender();

        self.grant_position(owner, id);

        #[cfg(feature = "log-events")]
        evm::log(events::MintPosition {
            id,
            owner,
            pool,
            lower,
            upper,
        });

        Ok(id)
    }
```

This function calls the ```create_position(&mut self, id: U256, low: i32, up: i32)``` function in pool.rs:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/pool.rs#L75

```rust
    pub fn create_position(&mut self, id: U256, low: i32, up: i32) -> Result<(), Revert> {
        assert_or!(self.enabled.get(), Error::PoolDisabled);
        let spacing = self.tick_spacing.get().sys() as i32;
        assert_or!(low % spacing == 0, Error::InvalidTickSpacing);
        assert_or!(up % spacing == 0, Error::InvalidTickSpacing);
        let spacing: u8 = spacing.try_into().map_err(|_| Error::InvalidTickSpacing)?;
        let min_tick = tick_math::get_min_tick(spacing);
        let max_tick = tick_math::get_max_tick(spacing);
        assert_or!(low >= min_tick && low <= max_tick, Error::InvalidTick);
        assert_or!(up >= min_tick && up <= max_tick, Error::InvalidTick);
        self.positions.new(id, low, up);
        Ok(())
    }
```

This function calls the ```new(&mut self, id: U256, low: i32, up: i32)``` function in position.rs:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/position.rs#L35

```rust
    #[allow(clippy::new_ret_no_self)]
    pub fn new(&mut self, id: U256, low: i32, up: i32) {
        let mut info = self.positions.setter(id);
        info.lower.set(I32::lib(&low));
        info.upper.set(I32::lib(&up));
    }
```

However, in the complete flow it has not been checked that:

```rust
asse
Parameter Misordering in Fee Collection Function Causes Denial of Service and Fee Loss
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/lib.rs#L1149
https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/lib.rs#L1150


# Vulnerability details

## **Impact**

A critical bug has been identified in the `collect_protocol_7540_F_A_9_F` function of the Seawater Automated Market Maker (AMM) protocol. This vulnerability affects the core functionality of fee collection, rendering the system unable to transfer protocol fees from liquidity pools to the Seawater Admin or any designated recipient. The bug stems from incorrect parameter handling during the invocation of the `transfer_to_addr` function, which results in a complete failure of the fee withdrawal process.

The impact of this issue is severe:

1. **Denial of Service (DoS)**: The system's inability to transfer protocol fees halts the entire fee collection process, effectively disabling this core feature. Without this function, the Seawater Admin cannot retrieve fees collected from liquidity pools, meaning the protocol cannot generate or distribute earnings from the token swapping activity in these pools.
  
2. **Financial Loss**: Since the protocol relies on fees for its economic model, this bug causes a direct financial loss. The protocol's earnings from fees generated by liquidity pools are locked and inaccessible, leading to loss of funds. Over time, the fees accumulate in the pools, but they cannot be withdrawn by the Seawater Admin or any authorized recipient.
  
3. **Erosion of User Trust**: Protocol participants expect proper fee collection and distribution. If this feature fails, users may lose confidence in the protocol's reliability, leading to reduced engagement or participation. For liquidity providers and investors, this issue directly affects expected returns, as fees cannot be withdrawn.
  
4. **Long-term Protocol Viability**: If not addressed, this bug could harm the protocol’s long-term viability. Since the protocol is unable to collect revenue from its own pools, its ability to cover operational costs or distribute rewards is compromised. This could lead to a shutdown or significant degradation of the protocol’s functionality.

In summary, the failure to collect protocol fees undermines the core value proposition of the AMM protocol, resulting in a loss of funds, DoS of essential functionality, and the potential collapse of the protocol’s economic model if left unresolved.

---

## **Proof of Concept**

The bug exists in the fee collection logic of the `collect_protocol_7540_F_A_9_F` function located in `lib.rs`. This function is responsible for transferring collected protocol fees (in the form of tokens) from an AMM pool to the Seawater Admin or another recipient. However, incorrect parameter handling within the `transfer_to_addr` function causes the entire process to fail.

Here’s a detailed breakdown of the problematic code
Tick `low` can be set higher than tick `up` when creating a position in `pool.create_position`
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/lib.rs#L495-L520
https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/pool.rs#L74-L87


# Vulnerability details

To create a new position in the pool, users must call the `mint_position_B_C5_B086_D` function with `pool: Address, lower: i32, upper: i32` parameters, where `lower` and `upper` define the tick range. Because this function does not perform any input validations, the checks are performed in the pool's function `create_position`:

```rust
    /// Creates a new position in this pool.
    pub fn create_position(&mut self, id: U256, low: i32, up: i32) -> Result<(), Revert> {
        assert_or!(self.enabled.get(), Error::PoolDisabled);
        let spacing = self.tick_spacing.get().sys() as i32;
        assert_or!(low % spacing == 0, Error::InvalidTickSpacing);
        assert_or!(up % spacing == 0, Error::InvalidTickSpacing);
        let spacing: u8 = spacing.try_into().map_err(|_| Error::InvalidTickSpacing)?;
        let min_tick = tick_math::get_min_tick(spacing);
        let max_tick = tick_math::get_max_tick(spacing);
        assert_or!(low >= min_tick && low <= max_tick, Error::InvalidTick);
        assert_or!(up >= min_tick && up <= max_tick, Error::InvalidTick);
        self.positions.new(id, low, up);
        Ok(())
    }
```
[pool.rs#L74-L87](https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/pool.rs#L74-L87)

According to the documentation, the protocol should follow Uniswap V3's math faithfully. However, Uniswap V3's function to check the ticks has different logic:

```solidity
    /// @dev Common checks for valid tick inputs.
    function checkTicks(int24 tickLower, int24 tickUpper) private pure {
        require(tickLower < tickUpper, 'TLU');
        require(tickLower >= TickMath.MIN_TICK, 'TLM');
        require(tickUpper <= TickMath.MAX_TICK, 'TUM');
    }
```

[UniswapV3Pool.sol#L125-L130](https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L125C1-L130C6)

As seen above, the`create_position`function has no explicit checks to ensure`low`is less than`up` (`tickLower < tickUpper`on Uniswap).

## Impact

This could allow a user to create a position where the lower tick is actually higher than the upper tick, leading to incorrect calculations and dangerous behavior due to the inverted price range.

## Tools Used

Manual review.

## Recommended Mitigation Steps

Add an explicit check to the`create_position`function in`pool.rs`to ensure that tick`low`is strictly less than`up`. This can be done by adding the following assertion after the existing checks:

```rust
    assert_or!(low < up, Error::InvalidTickRange);
```



## Assessed type

Math
Emergency Council Update Function Incorrectly Updates NFT Manager
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/lib.rs#L1111-L1124


# Vulnerability details

## Impact
- The emergency council address remains unchanged, potentially leaving an outdated or compromised address in control.
- The NFT manager address is unexpectedly changed, which could disrupt NFT-related operations.

## Proof of Concept

The `update_emergency_council_7_D_0_C_1_C_58` function is intended to update the emergency council address. However, it incorrectly updates the NFT manager address instead

```
pub fn update_emergency_council_7_D_0_C_1_C_58(
        &mut self,
        manager: Address,
    ) -> Result<(), Revert> {
        assert_eq_or!(
            msg::sender(),
            self.seawater_admin.get(),
            Error::SeawaterAdminOnly
        );

        self.nft_manager.set(manager); //this line is incorrect

        Ok(())
    }
```


## Tools Used
Manual Review

## Recommended Mitigation Steps

Modify the function to correctly update the emergency council address:
```solidity
pub fn update_emergency_council_7_D_0_C_1_C_58(
    &mut self,
    new_council: Address,
) -> Result<(), Revert> {
    assert_eq_or!(
        msg::sender(),
        self.seawater_admin.get(),
        Error::SeawaterAdminOnly
    );

    self.emergency_council.set(new_council);  // Correct update

    Ok(())
}

```



## Assessed type

Access Control
Emergency council address could not be updated by the seawater admin
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/lib.rs#L1111


# Vulnerability details

## Overview
The protocol provides a facility to the Seawater admin to update the emergency council address:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/sol/SeawaterAMM.sol#L207

```solidity
    function updateEmergencyCouncil7D0C1C58(address /* council */) external {
        directDelegate(_getExecutorAdmin());
    }
```

This delegate calls a function in lib.rs:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/lib.rs#L1111

```rust
    #[allow(non_snake_case)]
    pub fn update_emergency_council_7_D_0_C_1_C_58(
        &mut self,
        manager: Address,
    ) -> Result<(), Revert> {
        assert_eq_or!(
            msg::sender(),
            self.seawater_admin.get(),
            Error::SeawaterAdminOnly
        );

        self.nft_manager.set(manager);

        Ok(())
    }
```

However, as can be seen, the function wrongly updates the nft_manager and not the emergency council address. PoC has not been provided as the issue is obvious.

## Severity
If the emergency council address gets malicious, there would be no way for the seawater admin to change it. Moreover, if the admin calls the above function unknowingly, he would end up making the nft_manager malicious as well. 

Since, the seawater admin faces a loss of functionality that prevents him from updating the emergency council address, this issue is of Medium severity.

## Tools Used
Manual review

## Recommended Mitigation Steps
Replace the following in the ```update_emergency_council_7_D_0_C_1_C_58()``` function:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/lib.rs#L1111

```diff
    #[allow(non_snake_case)]
    pub fn update_emergency_council_7_D_0_C_1_C_58(
        &mut self,
        manager: Address,
    ) -> Result<(), Revert> {
        assert_eq_or!(
            msg::sender(),
            self.seawater_admin.get(),
            Error::SeawaterAdminOnly
        );

-       self.nft_manager.set(manager);
+       self.emergency_council.set(manager);

        Ok(())
    }
```


## Assessed type

Access Control
when NFT position is transferred, approvals are not cleared
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/lib.rs#L554-L569
https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/sol/OwnershipNFTs.sol#L109-L116


# Vulnerability details

## Impact

Approvals are not being cleared when `OwnershipNFT` is being transferred by owner. NFT can be sold and later on can be stolen back along with all the deposited tokens.

## Proof of Concept

Looking at the OZ ERC721 implementation we will see the following function being called in `transferFrom` and `transfer`:

[ERC721.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L253)

```solidity
function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) {
    address from = _ownerOf(tokenId);

    // Perform (optional) operator check
    if (auth != address(0)) {
        _checkAuthorized(from, auth, tokenId);
    }

    // Execute the update
    if (from != address(0)) {
        // Clear approval. No need to re-authorize or emit the Approval event
        _approve(address(0), tokenId, address(0), false);

        unchecked {
            _balances[from] -= 1;
        }
    }

    if (to != address(0)) {
        unchecked {
            _balances[to] += 1;
        }
    }

    _owners[tokenId] = to;

    emit Transfer(from, to, tokenId);

    return from;
}
```

As we can see approve with `address(0)` is called each time, which clears the `per token` and `operator approvals` of the previous owner, exactly to prevent the old approvers to steal back the NFT.

However, in `OwnershipNFT` approvals are not being cleared and will remain even after the position is transferred:

[OwnershipNFT.sol](https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/sol/OwnershipNFTs.sol#L109-L116)

```solidity
function _transfer(
      address _from,
      address _to,
      uint256 _tokenId
  ) internal {
      _requireAuthorised(_from, _tokenId);
      SEAWATER.transferPositionEEC7A3CD(_tokenId, _from, _to);
  }
  
function _requireAuthorised(address _from, uint256 _tokenId) internal view {
    // revert if the sender is not authorised or the owner
    bool isAllowed =
        msg.sender == _from ||
        isApprovedForAll[_from][msg.sender] ||
        msg.sender == getApproved[_tokenId];

    require(isAllowed, "not allowed");
    require(ownerOf(_tokenId) == _from, "_from is not the owner!");
}
```

Note that the `Rust` code, `lib::transfer_position_E_E_C7_A3_C_D` in particular, doesn’t manage the approvals, it cares only about adding/removing the position owner:

[lib.rs](https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/lib.rs#L554-L569)

```rust
pub fn transfer_position_E_E_C7_A3_C_D(
    &mut self,
    id: U256,
    from: Address,
    to: Address,
) -> Result<(), Revert> {
    assert_eq_or!(msg::sender(), self.nft_manager.get(), Error::NftManagerOnly);

    self.remove_position(from, id);
    self