xanoutas/solidity-security-findings huggingface dataset:
Users cannot remove liquidity when the pool is disabled.
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/lib.rs#L685-L688
https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/pool.rs#L90-L93


# Vulnerability details

## Impact
When the pool is disabled, users cannot remove liquidity.

## Proof of Concept
From the comments above function update_position_internal(), we can get our protocol's thoughts: When pools are disabled, we cannot increase positions' liquidity, but removing liquidity is allowed. 
This design is reasonable that when something wrong happens, we do not wish that LP holders to add more liquidity into the pool, but allow LP holders to remove liquidity from this pool.
The problem is that current pool's `update_position()` function will be reverted if the pool is disabled no matter the operation is increasing liquidity or removing liquidity.
This behavior will block users' removing liquidity when the pool is disabled.
```javascript
    /// # Errors
    /// Requires token approvals to be set if adding liquidity. Requires the caller to be the
    /// position owner. Requires the pool to be enabled unless removing liquidity.

    pub fn update_position_internal(
    ...
```
```javascript
    pub fn update_position(&mut self, id: U256, delta: i128) -> Result<(I256, I256), Revert> {
        // the pool must be enabled
        assert_or!(self.enabled.get(), Error::PoolDisabled);
    ......
}
```
## Tools Used
Manual

## Recommended Mitigation Steps
In pool's `update_position()` function, allow removing liquidity if the pool is disabled.


## Assessed type

Context
_onTransferReceived will revert on correct data result
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
NFTs won't be sent to contracts that have the correct implementation to handle them and will be sent to ones that don't have the necessary methods implemented thus resulting in NFTs being permanently blocked.

## Proof of Concept
The comment on the _onTransferReceived function states that if the callback doesn't return the selector for onERC721Received then we should revert - https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L65-L67 .The same is mentioned in the IERC721TokenReceiver's doc - https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/IERC721TokenReceiver.sol#L6-L9 , however when the callback is finished, in the require statement we expect that the data returned is not equal to the onERC721Received selector which will result in a revert in the wrong case - https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L92-L95

## Tools Used
Manual review

## Recommended Mitigation Steps
```
require(
-           data != IERC721TokenReceiver.onERC721Received.selector,
+           data == IERC721TokenReceiver.onERC721Received.selector,
            "bad nft transfer received data"
        );


## Assessed type

ERC721
_onTransferReceived() does not work as intended
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
`_onTransferReceived` does not work as intended or described in the fucntion [natspec](https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L65-L67).
 - It will not revert when the recipient does not implement `onERC721Received()` function correctly (does not return `onERC721Received().selector`). 
- It will revert when the recipient implements `onERC721Received()` function correctly and as described/specified by the [EIP 712](https://eips.ethereum.org/EIPS/eip-721#specification) (returns `onERC721Received().selector`). 
- This will prevent transfers to contracts that have correctly implemented the `ERC721TokenReceiver` interface to accept safe token transfers via `safeTransferFrom()`.

## Proof of Concept
`_onTransferReceived()` has a require statement that will pass when the recipient does not return the `IERC721TokenReceiver.onERC721Received()` selector. [EIP 721](https://eips.ethereum.org/EIPS/eip-721#specification) defines that if a recipient is a contract, it should implement the `IERC721TokenReceiver.onERC721Received()` function and that function must return the `IERC721TokenReceiver.onERC721Received()` selector for it to be recognized as a valid erc721 token receiver. 

Snippet of the faulty require statement below 
https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L82-L95
```solidity
        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"
        );
```
We can see that the require statement expects the data returned from the erc721 token receiver to **not be equal** to `IERC721TokenReceiver.onERC721Received.selector`. This means that if the recipient implements the `onERC721Received` function correctly and returns `IERC721TokenReceiver.onERC721Received.selector`, the require statement in function `_onTransferReceived()` will revert the whole transfer. 



### Coded POC 

- create foundry test repo and run the file below in test folder. 

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";

/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.
interface IERC721TokenReceiver {
    /// @notice Handle the receipt of an NFT
    /// @dev The ERC721 smart contract calls this function on the recipient
    ///  after a `transfer`. This function MAY throw to revert an
Unnecessary assertion in `swap_2_internal` causes valid swaps to fail
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#L198-L251


# Vulnerability details

## Impact
User is unable to perform a swap even though liquidity is sufficient in both pools involved in the swap for the `min_out` amount to be outputted.

## Proof of Concept
Let's have a look at the [`swap_2_internal`](https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/lib.rs#L198-L251) function below

```javascript
    fn swap_2_internal(
        pools: &mut Pools,
        from: Address,
        to: Address,
        amount: U256,
        min_out: U256,
    ) -> Result<(U256, U256, U256, I256, i32, i32), Revert> {
        let original_amount = amount;

        let amount = I256::try_from(amount).map_err(|_| Error::SwapResultTooHigh)?;

        // swap in -> usdc
        let (amount_in, interim_usdc_out, final_tick_in) = pools.pools.setter(from).swap(
            true,
            amount,
            // swap with no price limit, since we use min_out instead
            tick_math::MIN_SQRT_RATIO + U256::one(),
        )?;

        // make this positive for exact in
        let interim_usdc_out = interim_usdc_out
            .checked_neg()
            .ok_or(Error::InterimSwapPositive)?;

        // swap usdc -> out
        let (amount_out, interim_usdc_in, final_tick_out) = pools.pools.setter(to).swap(
            false,
            interim_usdc_out,
            tick_math::MAX_SQRT_RATIO - U256::one(),
        )?;

        let amount_in = amount_in.abs_pos()?;
        let amount_out = amount_out.abs_neg()?;

        #[cfg(feature = "testing-dbg")]
        dbg!((
            "inside swap_2_internal",
            interim_usdc_out,
            interim_usdc_in,
            amount_out.to_string(),
            min_out.to_string()
        ));

        assert_eq_or!(interim_usdc_out, interim_usdc_in, Error::InterimSwapNotEq);
        assert_or!(amount_out >= min_out, Error::MinOutNotReached);
        Ok((
            original_amount,
            amount_in,
            amount_out,
            interim_usdc_out,
            final_tick_in,
            final_tick_out,
        ))
    }
```

this function performs a 2-step swap by swapping the `from` token to `usdc` and then `usdc` to the `to` token, given a `min_out` amount inputted by the user and corresponding to the minimum amount the user is willing to receive from this swap.

we notice the assertion on line 241

```javascript
        assert_eq_or!(interim_usdc_out, interim_usdc_in, Error::InterimSwapNotEq);
```

which intends to check that the whole out usdc amount from the first swap (`interim_usdc_out`) is being used entirely by the swap into the other pool, hence equal to the input amount `interim_usdc_in`.
However, this need not be the case as long as the `min_out` amount chosen by the user is met.

Note that this is the reason for the `ethers_suite_orchestrated_uniswap_two` test to fail as the second liqudity pool does not have enough
`bytes data` param is not passed to ERC721 recipient as expected by EIP-721
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


## Bug Description
ERC-721 standard has two variations of the `safeTransferFrom()`, one with no `bytes data` param and the other with a `bytes data` param. The second variation that accepts a `bytes data` param is meant to pass that data param to the token recipient during the `IERC721TokenReceiver. onERC721Received()` call. This `bytes data` param is usually an encoding of extra variables which is used by the recipient contract for the extra logic it will perform as it receives a token. 

[OwnershipNFTs.sol](https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/sol/OwnershipNFTs.sol) is ERC721 compliant and so it has two `safeTransferFrom()` functions but the  second `safeTransferFrom()` has a `bytes data` param which is not passed into the recipient during `IERC721TokenReceiver. onERC721Received()` call to recipient. It accepts the `bytes data` param but doesnt use it. 

```solidity
    /// @inheritdoc IERC721Metadata
    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes calldata /* _data */
    ) external payable {
        _transfer(_from, _to, _tokenId);
        _onTransferReceived(msg.sender, _from, _to, _tokenId);
    
```
As we can see above the bytes param is declared as an arbitrary param, perhaps just to conform to IERC721 spec but the bytes param is not used in the logic at all, and it ought to be used as  defined [here](https://eips.ethereum.org/EIPS/eip-721#specification)  in the EIP.
>     /// @param data Additional data with no specified format, sent in call to `_to`



For contracts that integrate OwnershipNFTs or receive transfers, if they have logic which require the additional bytes passed in via the second `safeTransferFrom()`, they will be unable to execute this logic and may reject the transfers or revert as this `bytes data` supplied by the caller of the `safeTransferFrom()` is not passed into them via the `IERC721TokenReceiver.onERC721Received()` call. Instead, empty bytes is passed into `IERC721TokenReceiver.onERC721Received()` as seen [here](https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L89).  

Below is a POC which shows how passing empty bytes by default instead of passing the bytes specified by the sender into a tokenReceiver may cause the transfer to fail.

- run with `forge test`

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";

/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02.
interface IERC721TokenReceiver {
    /// @notice Handle the receipt of an NFT
    /// @dev The ERC721 smart contract calls this function on the recipient
    ///  after a `transfer`. This function MAY throw to revert an
It is not possible to set protocol fees, which results in the protocol losing funds
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


# Vulnerability details

## Impact
It's impossible to set protocol fees (which are a fraction of swap fees that go to the protocol instead of the liquidity providers), so any potential revenue from it will always be zero.

## Proof of Concept
Each pool has a `fee_protocol` parameter that indicates how much percentage of the swap fee is taken on withdrawal:
https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/pool.rs#L31

The issue is that this parameter is not set anywhere (and it can't be set afterward), so it will default to zero.

Like Uniswap, this is a way the protocol would earn a stream of revenue; but since it's hard set to zero, the protocol would earn nothing from it.

By searching for `fee_protocol` usage, it can be easily seen that it is only read, as it's never set anywhere:

https://github.com/search?q=repo%3Acode-423n4%2F2024-08-superposition+fee_protocol&type=code

Essentially, the project is currently missing a function to set the `fee_protocol`.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider implementing a function like Uniswap's `setFeeProtocol` so it's possible to set the protocol fees:

```solidity
    function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external override lock onlyFactoryOwner {
        require(
            (feeProtocol0 == 0 || (feeProtocol0 >= 4 && feeProtocol0 <= 10)) &&
                (feeProtocol1 == 0 || (feeProtocol1 >= 4 && feeProtocol1 <= 10))
        );
        uint8 feeProtocolOld = slot0.feeProtocol;
        slot0.feeProtocol = feeProtocol0 + (feeProtocol1 << 4);
        emit SetFeeProtocol(feeProtocolOld % 16, feeProtocolOld >> 4, feeProtocol0, feeProtocol1);
    }
```

https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L837-L845


## Assessed type

Access Control
Improper implementation of `_onTransferReceived` hook
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 `_onTransferReceived` is improperly implemented, as a result NFT transfers to contracts inheriting the `onERC721Received` interface will fail, while transfers to contracts lacking the interface will succeed potentially leading to NFT loss.


### Proof of Concept

1. Context

To confirm that a receiver can receive NFTs through the `safeTransferFrom` method, the function is required to call a hook which checks for the `onERC721Received` hook in the function. This hook helps the sender ensure that the reciever can actually support ERC721 tokens as the EIP721 standard enforces this rule on every contract that aims to handle NFTs. The check for this hook can be found in the two `safeTransferFrom` functions, which both, after calling the `_transfer` method, calls the `_onTransferReceived` hook.

```solidity
    function safeTransferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        bytes calldata /* _data */
    ) external payable {
        _transfer(_from, _to, _tokenId);
        _onTransferReceived(msg.sender, _from, _to, _tokenId);
    }
```

2. Bug Location

The `_onTransferReceived` hook, however is not properly implemented. First the function the queries the `onERC721Received` in the receiving contract, and returns the selector code as data. It then compares the data with the expected `onERC721Received.selector`. However, as can be seen from the @audit tag below, due to an invalid validation, the function actually "requires" that the selector returned by the the receiver contract not match the expected selector, which is incorrect.

```solidity
    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, //@audit
            "bad nft transfer received data"
        );
    }
```
3. Final Effect
 
Due to this incorrect validation, any contract that accurately returns the expected selector, i.e a contract equipped to handle NFT, will be rejected and as such cannot receive the NFTs upon safe transfer. On the other hand, a contract that may not be equipped to handle NFTs, i.e lacking the expected selector will be incorrectly passed on, and allowed to receive the NFT. This will cause a loss of the NFT.

### Tools Used
Manual code review
 
### Recommended Mitigation Steps

C
Incorrect ERC721 receiver check causes safe transfers to always fail
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

## Impact
The `_onTransferReceived` function incorrectly checks the return value from the `onERC721Received` function call. This causes all safe transfers to contract addresses to fail, even when the receiving contract correctly implements the ERC721 receiver interface. 

## Proof of Concept
Take a look at the `_onTransferReceived` function: https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L73-L96

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

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

The `require` statement checks if the returned `data` is NOT equal to the `onERC721Received` selector. This is the opposite of what it should be doing. According to the ERC721 standard, the `onERC721Received` function should return its function selector to indicate successful receipt of the token. but the way it's currently implemented will revert the transaction when the receiver correctly implements the `onERC721Received` function.

This bug goes on to affect both `safeTransferFrom` functions in the contract, as they both call `_onTransferReceived`:

```solidity
function safeTransferFrom(
    address _from,
    address _to,
    uint256 _tokenId
) external payable {
    _transfer(_from, _to, _tokenId);  <--------
    _onTransferReceived(msg.sender, _from, _to, _tokenId);
}

function safeTransferFrom(
    address _from,
    address _to,
    uint256 _tokenId,
    bytes calldata /* _data */
) external payable {
    _transfer(_from, _to, _tokenId);
    _onTransferReceived(msg.sender, _from, _to, _tokenId);  <--------
}
```
## Tools used
Manual review

## Recommended Mitigation Steps
Change the `require` statement in the `_onTransferReceived` function to check for equality instead of inequality:

```diff
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,
        ""
    );

    require(
-       data != IERC721TokenReceiver.onERC721Received.selector,
-       "bad nft transfer received data"
+       data == IERC721TokenReceiver.onERC721Received.selector,
+       "ERC721: transfer to non ERC721Receiver implementer"
    );
}
```


## Assessed type

Error
Incorrect Validation in `_onTransferReceived` Function Leads to Stuck Tokens and Failed Transfers
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

There is an incorrect check in the `_onTransferReceived` function that results in improper validation of the recipient contract when transferring NFTs. Specifically, the function is designed to ensure that the recipient contract implements the `onERC721Received` interface by checking the returned selector. However, the current implementation incorrectly checks return data.

## Impact
The incorrect condition causes two major issues:

- **Failure to transfer tokens to legitimate contracts:** Contracts that correctly implement the ERC-721 interface and return the expected selector will fail the check, preventing legitimate transfers.

- **Unintentional transfers to non-legitimate contracts:** Contracts that do not return the correct selector are allowed to receive NFTs. If these contracts cannot handle the tokens, the NFTs may become irretrievably stuck.
## Proof of Concept
```solidity
    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
```diff
    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,
+           data == IERC721TokenReceiver.onERC721Received.selector,
            "bad nft transfer received data"
        );
    }
```


## Assessed type

Error