xanoutas/solidity-security-findings huggingface dataset:
mulDiv() function in full_math library does not correspond to the same UniswapV3 function at all
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The current version of `mulDiv()` function does not correspond to the function from the same library in UniswapV3 Math. This leads to a situation where using of such library function can result in the issues when making calculations in the core functionality of the protocol.

## Proof of Concept

The library implements `mulDiv()` function the following way:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/maths/full_math.rs#L32-51
```

pub fn _mul_div(a: U256, b: U256, mut denom_and_rem: U256) -> Result<(U256, bool), Error> {
    if denom_and_rem == U256::ZERO {
        return Err(Error::DenominatorIsZero);
    }

    let mut mul_and_quo = a.widening_mul::<256, 4, 512, 8>(b);

    unsafe {
        ruint::algorithms::div(mul_and_quo.as_limbs_mut(), denom_and_rem.as_limbs_mut());
    }

    let limbs = mul_and_quo.into_limbs();
    if limbs[4..] != [0_u64; 4] {
        return Err(Error::DenominatorIsLteProdOne);
    }

    let has_carry = denom_and_rem != U256::ZERO;

    Ok((U256::from_limbs_slice(&limbs[0..4]), has_carry))
}

```

Now let's take a look at the UniswapV3 functionality:

https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol#L14-106

As you can see here, the current version of the function misses several key elements including calculations and checks associated with computations of the product, usage of Chinese Remainder theorem and Newton-Raphson iteration (for precision).

As stated in the docs:

```
We should follow Uniswap V3's math faithfully.

```

## Tools Used

Manual review.

## Recommended Mitigation Steps

Consider implementing the `mulDiv()` functionality as it's done in this open-source library:

https://github.com/0xKitsune/uniswap-v3-math/blob/c46783bd0190ec68836345ac7a7104c335f8151e/src/full_math.rs#L8-102




## Assessed type

Other
fee_protocol is not set anywhere in pool.rs
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/pool.rs#L31
https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/pool.rs#L49-L72


# Vulnerability details

## Impact

Pools cannot earn fees. 

## Proof of Concept

In pools.rs, there is a struct `StoragePool` which defines all the values for an AMM pool.

```
#[solidity_storage]
pub struct StoragePool {
    pub enabled: StorageBool,
    // immutables
    pub fee: StorageU32,
    pub tick_spacing: StorageU8,
    pub max_liquidity_per_tick: StorageU128,

    // packed token0[4], token1[4]
    fee_protocol: StorageU8,
    fee_growth_global_0: StorageU256,
    fee_growth_global_1: StorageU256,
    protocol_fee_0: StorageU128,
    protocol_fee_1: StorageU128,

    pub liquidity: StorageU128,
    pub cur_tick: StorageI32,
    pub sqrt_price: StorageU256,

    pub positions: position::StoragePositions,

    ticks: tick::StorageTicks,
    tick_bitmap: tick::StorageTickBitmap,
}
```

Some of these values are defined in the initialized function, some of the values are set during calls to different functions. The `fee_protocol` value is not set anywhere.

Values like `sqrt_price`, `fee` is set in `init()`:

```
  pub fn init(
        &mut self,
        price: U256,
        fee: u32,
        tick_spacing: u8,
        max_liquidity_per_tick: u128,
    ) -> Result<(), Revert> {
        assert_eq_or!(
            self.sqrt_price.get(),
            U256::ZERO,
            Error::PoolAlreadyInitialised
        );

>        self.sqrt_price.set(price);
         self.cur_tick
            .set(I32::lib(&tick_math::get_tick_at_sqrt_ratio(price)?));

>       self.fee.set(U32::lib(&fee));
        self.tick_spacing.set(U8::lib(&tick_spacing));
        self.max_liquidity_per_tick
            .set(U128::lib(&max_liquidity_per_tick));

        Ok(())
    }
```

`fee_protocol` is used for calculating fees, but it is not set, and so it will return 0. 

```
     // select either the high or low 4 bits
        let fee_protocol = match zero_for_one {
>           true => self.fee_protocol.get().sys() % 16,
>           false => self.fee_protocol.get().sys() >> 4,
        };

    ...
    // set fees
            if fee_protocol > 0 {
                let delta = step_fee_amount.wrapping_div(U256::from(fee_protocol));
                step_fee_amount -= delta;
                state.protocol_fee += u128::try_from(delta).or(Err(Error::FeeTooHigh))?;
            }

```

Fees will not be calculated for the pool.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Set `fee_protocol` in the `init()` function.


## Assessed type

Context
Incorrect `require` statement will reject valid transfers and allow invalid ones
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L74-L97


# Vulnerability details


The `OwnershipNFTs` contract is designed to track ownership of positions in the `Seawater` contract. One of its functions, [`_onTransferReceived`](https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L74-L97), is responsible for ensuring that the recipient of an NFT can handle ERC721 tokens. This is done by calling the `onERC721Received` function on the recipient contract and checking its return value.

The ERC721 standard specifies that the `onERC721Received` function should return a specific selector (`0x150b7a02`) to confirm that the contract can handle the token. If the returned value does not match this selector, the transfer should be reverted.

However, the current implementation of `_onTransferReceived` contains a bug. The `require` statement incorrectly uses `!=` instead of `==`, which means it will revert if the returned data is equal to the expected selector. This is the opposite of the intended behavior. The function should revert if the returned data is **not** equal to the expected selector.

The relevant code is as follows:

```solidity
File: OwnershipNFTs.sol
74:     function _onTransferReceived(
75:         address _sender,
76:         address _from,
77:         address _to,
78:         uint256 _tokenId
79:     ) internal {
80:         // only call the callback if the receiver is a contract
81:         if (_to.code.length == 0) return;
82: 
83:         bytes4 data = IERC721TokenReceiver(_to).onERC721Received(
84:             _sender,
85:             _from,
86:             _tokenId,
87: 
88:             // this is empty byte data that can be optionally passed to
89:             // the contract we're confirming is able to receive NFTs
90:             ""
91:         );
92:         // @audit - This should be == instead of !=. It's currently rejecting valid transfers and allowing invalid ones.
93:         require(
94:             data != IERC721TokenReceiver.onERC721Received.selector,
95:             "bad nft transfer received data"
96:         );
97:     }

```

## Impact
This causes the contract to reject valid NFT transfers and allow invalid ones. This could lead to a situation where NFTs are transferred to contracts that cannot handle them, potentially resulting in the loss of NFTs. Conversely, valid transfers to compliant contracts will be incorrectly reverted, disrupting the intended functionality of the contract.

## Proof of Concept
1. Alice attempts to transfer an NFT to Bob's contract, which correctly implements `onERC721Received`.
2. The `_onTransferReceived` function is called, and Bob's contract returns the correct selector (`0x150b7a02`).
3. The `require` statement incorrectly reverts the transaction because it checks for `!=` instead of `==`.
4. The valid transfer is rejected,
Wrong operation
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L93


# Vulnerability details

## Impact
In function _onTransferReceived
    ...
    require(
        data != IERC721TokenReceiver.onERC721Received.selector,
        "bad nft transfer received data"
    );
}

It's wrong logic. Need to check if data is selector. Use == instead of !=

## Recommended Mitigation Steps

require(
    data == IERC721TokenReceiver.onERC721Received.selector,
    "bad nft transfer received data"
);


## Assessed type

Invalid Validation
There is no "fallback" feature in the Rust implementation
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The current implementation of the `SeawaterAMM` smart contract uses Diamond-like proxy pattern directing calls to the implementation contracts via delegatecall. It enables the users to call different functions from different features. The problem is that the contract initializes `EXECUTOR_FALLBACK_SLOT` and assigns the corresponding smart contract address of the Rust implementation but there is no currently such feature in the Rust code.

## Proof of Concept

`EXECUTOR_FALLBACK_SLOT` is assigned to the corresponding address in the internal `_setProxies()` function:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/sol/SeawaterAMM.sol#L568
```
StorageSlot.getAddressSlot(EXECUTOR_FALLBACK_SLOT).value = address(executorFallback);
```

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/sol/SeawaterAMM.sol#L30
```
bytes32 constant EXECUTOR_FALLBACK_SLOT = bytes32(uint256(keccak256("seawater.impl.fallback")) - 1);
```

As you can see from the next lines, the "fallback" feature is called in the `fallback()` function if no any other feature does not correspond to the `msg.data` sent to the contract:


https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/sol/SeawaterAMM.sol#L520
```
        else directDelegate(_getExecutorFallback());
    }
```

`directDelegate` in its turn calls implementation address (in the terms of the protocol, it's "features" in the Rust code):


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

 // call a function with the same name and calldata on another contract
    // this ends execution!
    // proxy implementation adapted from openzeppelin
    function directDelegate(address to) internal 

```

As the comment above points out, the proxy implementation is adapted from the OZ meaning `directDelegate` is similar to the `_delegate()` function from the `Proxy` contract that calls implementation.

Therefore, the contract calls "fallback" feature but there is nothing initialized in the Rust code:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/lib.rs#L81-89
```
#[cfg(not(any(
    feature = "swaps",
    feature = "swap_permit2",
    feature = "quotes",
    feature = "positions",
    feature = "update_positions",
    feature = "admin",
    feature = "migrations"
)))]

```

## Tools Used

Manual review.

## Recommended Mitigation Steps

Implement "fallback" feature in the Rust implementation code.


## Assessed type

Other
Incorrect Implementation of ERC721 Transfer Callback Verification
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L73-L97


# Vulnerability details

## Impact
High. The current implementation prevents all valid NFT transfers to contracts implementing the ERC721TokenReceiver interface.

## Proof of Concept
The ERC721 standard requires that contracts receiving NFTs must implement the ERC721TokenReceiver interface and return a specific selector from the onERC721Received function to indicate they can handle the NFT.
The current implementation reverts when the correct selector is returned, which is the opposite of what it should do. This prevents all valid transfers to contracts, effectively breaking the NFT transfer functionality to any contract recipient.

The _onTransferReceived function in the contract incorrectly checks the return value of the onERC721Received callback. The function reverts when the callback returns the correct selector, which is the opposite of the intended behavior.

Vulnerable Code
```
function _onTransferReceived(
    address _sender,
    address _from,
    address _to,
    uint256 _tokenId
) internal {
    if (_to.code.length == 0) return;
    bytes4 data = IERC721TokenReceiver(_to).onERC721Received(
        _sender,
        _from,
        _tokenId,
        ""
    );
    require(
        data != IERC721TokenReceiver.onERC721Received.selector, <@==
        "bad nft transfer received data"
    );
}
```
## Tools Used

## Recommended Mitigation Steps
change to this
```
require(
    data == IERC721TokenReceiver.onERC721Received.selector,
    "ERC721: transfer to non ERC721Receiver implementer"
);
```


## Assessed type

ERC721
Users unable to remove liquidity from disabled pools
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

The AMM contract contains a function `updatePositionC7F1F740` which allows users to add or remove liquidity from a position. However, there is a flaw in the logic that prevents users from removing their liquidity if the pool has been disabled. This function will call internally `update_position_internal` which in turn will call the following function `self.pools.setter(pool).update_position(id,  delta)?;` in the `pool.rs` contract. The `update_position` function checks if the pool is enabled before allowing any updates. The issue arises because this check is applied uniformly, regardless of whether the user is adding liquidity (`delta > 0`) or removing liquidity (`delta < 0`).

## Impact
Users are unable to withdraw their funds from disabled pools, effectively locking their assets indefinitely. 

## Proof of Concept
1. Alice adds liquidity to a pool.
2. The pool is later disabled by the admin for some reason.
3. Alice tries to remove her liquidity by calling `updatePositionC7F1F740()` with a negative `delta`.
4. The transaction reverts because the pool is disabled, preventing Alice from accessing her funds.

## Tools Used
Manual review

## Recommended Mitigation Steps
Modify the logic to allow liquidity removal even when the pool is disabled.


## Assessed type

Other
Incorrect ERC721 Token Receiver Validation
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L93


# Vulnerability details

The `OwnershipNFTs` contract implements the ERC721 standard for non-fungible tokens. One of the key features of ERC721 is the ability to safely transfer tokens to contracts by calling the `onERC721Received()` function on the receiving contract. However, the current implementation in the `_onTransferReceived()` function incorrectly validates the return value from this call.

In the `_onTransferReceived()` function, after calling `onERC721Received()` on the recipient contract, the function checks if the returned data is not equal to the `onERC721Received` selector:

``
require(
    data != IERC721TokenReceiver.onERC721Received.selector,
    "bad nft transfer received data"
);
``

This check is the opposite of what it should be. According to the ERC721 standard, the `onERC721Received()` function should return the function selector of `onERC721Received` to indicate that the contract is willing to accept the token transfer. The current implementation will revert when it receives a correct response and allow transfers when it receives an incorrect one.

## Impact
This issue has consequences such as:

1. Valid token transfers to contracts implementing ERC721 correctly will always fail.
2. Invalid transfers to contracts not properly implementing ERC721 might succeed, potentially leading to locked or lost tokens.


## Proof of Concept
1. Alice attempts to safely transfer her NFT to a contract that correctly implements `onERC721Received()`.
2. The receiving contract returns the correct selector (`IERC721TokenReceiver.onERC721Received.selector`).
3. The `_onTransferReceived()` function in `OwnershipNFTs` checks this return value.
4. The check incorrectly requires the return value to not equal the selector.
5. The transaction reverts with the error message "bad nft transfer received data".
6. Alice's valid transfer fails, even though the receiving contract is fully compliant with ERC721.

## Tools Used
Manual review

## Recommended Mitigation Steps
The condition in the `require` statement should be changed to check for equality instead of inequality.


## Assessed type

Other
Incorrect Validation in OwnershipNFTs.sol Prevents Valid NFT Transfers and Allows Invalid Ones
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L93


# Vulnerability details

## Impact
The bug in the `safeTransferFrom` function of the `OwnershipNFTs` contract leads to two critical issues:

1. **Denial of Service for Contracts with Correct Implementation**: If the recipient (`_to` address) is a contract that correctly implements the `onERC721Received` function, the transfer will revert. The root cause is an incorrect conditional check that causes the transfer to fail, even when the contract correctly returns the `onERC721Received` function selector. This results in a denial of service (DoS) for valid recipient contracts, preventing them from receiving NFTs.

2. **Transfer to Incorrectly Implemented Contracts**: Conversely, contracts that do not correctly implement the `onERC721Received` function or return arbitrary values can pass the flawed validation. This means NFTs can be mistakenly transferred to contracts that are not designed to hold NFTs, potentially leading to a loss of assets or other unforeseen consequences due to incorrect handling of NFTs.

These issues have significant implications for the integrity and reliability of the NFT transfer process, impacting both security and user trust in the system.

### Severity
- **High**: The vulnerability directly impacts the correctness of NFT transfers and may result in a loss of assets, disrupt intended functionality, and prevent valid transfers from occurring. The potential loss of user trust and system reliability due to failed transfers further raises the criticality of this issue.

## Proof of Concept

Here is a simplified proof of concept (PoC) demonstrating the issue with the `OwnershipNFTs` contract:

#### 1. `Test.t.sol`

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {Test} from "forge-std/Test.sol";
import {OwnershipNFTs} from "./../src/OwnershipNFTs.sol";
import {SeawaterAMM} from "./../src/SeawaterAMM.sol";
import "./../src/IERC721TokenReceiver.sol";

contract testNft is Test {
    OwnershipNFTs public ownershipnft;
    SeawaterAMM public seawateramm;
    MockContract public mockcontract;

    function setUp() public {
        mockcontract = new MockContract();
        seawateramm = new SeawaterAMM(address(this));
        ownershipnft = new OwnershipNFTs("","","", seawateramm);
    }

    function test_safetranser() public {
        // This transfer will fail due to the incorrect check in the OwnershipNFTs contract
        ownershipnft.safeTransferFrom(address(this), address(mockcontract), 1);
    }
}

contract MockContract {
    function onERC721Received(address, address, uint256, bytes memory) pure external returns(bytes4) {
        // MockContract correctly implements the function selector
        return IERC721TokenReceiver.onERC721Received.selector;
    }
}
```

#### 2. `SeawaterAMM.sol`

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16
Inverted Logic in ERC721 Receiver Check
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L73-L96


# Vulnerability details

## Vulnerability Details
The `_onTransferReceived` function has an inverted logic in its check of the returned selector:
```
require(
    data != IERC721TokenReceiver.onERC721Received.selector,
    "bad nft transfer received data"
);
```
This check should be == instead of !=.

## Impact
This inverted check will cause all valid ERC721 receiver contracts to be rejected, and only invalid ones to be accepted. This breaks the safety check intended to prevent tokens from being sent to contracts that can't handle them.

## Proof of Concept
```
    function _onTransferReceived(
        address _sender,
        address _from,
        address _to,
        uint256 _tokenId
    ) internal {
        // only call the callback if the receiver is a contract
        if (_to.code.length == 0) return;


        bytes4 data = IERC721TokenReceiver(_to).onERC721Received(
            _sender,
            _from,
            _tokenId,


            // this is empty byte data that can be optionally passed to
            // the contract we're confirming is able to receive NFTs
            ""
        );


        require(
            data != IERC721TokenReceiver.onERC721Received.selector,
            "bad nft transfer received data"
        );
    }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Correct the check to use == instead of !=:
```
require(
    data == IERC721TokenReceiver.onERC721Received.selector,
    "ERC721: transfer to non ERC721Receiver implementer"
);
```


## Assessed type

ERC721
Incorrect Implementation of ERC721 Safe Transfer Check
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/superposition-finance/superposition-core/blob/main/pkg/sol/OwnershipNFTs.sol#L92-L95


# Vulnerability details

## Vulnerability detail
The `_onTransferReceived` function in the `OwnershipNFTs` contract incorrectly implements the check for the `onERC721Received` selector. The function reverts when it receives the correct selector and allows the transfer for any other value, which is the opposite of the intended behavior.

## Impact
This vulnerability can lead to several severe consequences:

- Loss of tokens: NFTs could be transferred to contracts that are not capable of handling ERC721 tokens, potentially resulting in permanent loss of these assets.
- Broken functionality: The `safeTransferFrom` function, which is designed to prevent accidental transfers to unprepared contracts, is rendered ineffective.

## Proof of Concept
```
require(
    data != IERC721TokenReceiver.onERC721Received.selector,
    "bad nft transfer received data"
);
```
This code checks if the returned data is NOT equal to the expected selector, which is the opposite of the correct implementation.
## Tools Used
Manual code review
## Recommended Mitigation Steps
The check should be corrected to verify that the returned data matches the expected selector:
```
require(
    data == IERC721TokenReceiver.onERC721Received.selector,
    "bad nft transfer received data"
);
```


## Assessed type

Invalid Validation