xanoutas/solidity-security-findings huggingface dataset:
`get_fee_growth_inside` in `tick.rs` should allow for `underflow`/`overflow` but doesn't
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

When operations need to calculate the `Superposition` position's fee growth, it uses a similar function implemented by [uniswap v3](https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/PositionValue.sol#L145-L166).

However, according to this known issue : [Uniswap/v3-core#573](https://github.com/Uniswap/v3-core/issues/573). The contract implicitly relies on underflow/overflow when calculating the fee growth, if underflow is prevented, some operations that depend on fee growth will revert.

## Proof of Concept

It can be observed that the current implementation of `tick::get_fee_growth_inside` does not allow underflow/overflow to happen when calculating `fee_growth_inside_0` and `fee_growth_inside_1` because the contract used [checked math](https://docs.rs/num/latest/num/trait.CheckedSub.html) with additional overflow/underflow checks, which will `Performs subtraction that returns None instead of wrapping around on underflow`:
[tick.rs](https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/tick.rs#L125C5-L246C6)

```rust
pub fn get_fee_growth_inside(
    &mut self,
    lower_tick: i32,
    upper_tick: i32,
    cur_tick: i32,
    fee_growth_global_0: &U256,
    fee_growth_global_1: &U256,
) -> Result<(U256, U256), Error> {
    // the fee growth inside this tick is the total fee
    // growth, minus the fee growth outside this tick
    let lower = self.ticks.get(lower_tick);
    let upper = self.ticks.get(upper_tick);

    let (fee_growth_below_0, fee_growth_below_1) = if cur_tick >= lower_tick {
        #[cfg(feature = "testing-dbg")]
        dbg!((
            "cur_tick >= lower_tick",
            current_test!(),
            lower.fee_growth_outside_0.get().to_string(),
            lower.fee_growth_outside_1.get().to_string()
        ));

        (
            lower.fee_growth_outside_0.get(),
            lower.fee_growth_outside_1.get(),
        )
    } else {
        #[cfg(feature = "testing-dbg")]
        dbg!((
            "cur_tick < lower_tick",
            current_test!(),
            fee_growth_global_0,
            fee_growth_global_1,
            lower.fee_growth_outside_0.get().to_string(),
            lower.fee_growth_outside_1.get().to_string()
        ));

        (
            fee_growth_global_0
                .checked_sub(lower.fee_growth_outside_0.get())
                .ok_or(Error::FeeGrowthSubTick)?,
            fee_growth_global_1
                .checked_sub(lower.fee_growth_outside_1.get())
                .ok_or(Error::FeeGrowthSubTick)?,
        )
    };

    let (fee_growth_above_0, fee_growth_above_1) = if cur_tick < upper_tick {
        #[cfg(feature = "testing-dbg")]
        dbg!((
            "cur_tick < upper_tick",
            current_test!(),
            upper.fee_growth_outside_0.get(
Upgraded Q -> 2 from #167 [1728301859344]
Audit: 2024-08-superposition-findings
Severity: MEDIUM

Judge has assessed an item in Issue #167 as 2 risk. The relevant finding follows:

 ## [LOW-5] Removal of liquidity also requires the pool to be enabled:
In LOC-687, lib.rs, it is said that liquidity removal from a position does not require the pool to be enabled:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/lib.rs#L687

```
    /// 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.
```

However, the function ```update_position()``` in pool.rs, which is called by ```update_position_internal()``` in lib.rs requires the pool to be enabled:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/pool.rs#L92

```rust
assert_or!(self.enabled.get(), Error::PoolDisabled);
```

### Recommended Mitigation Steps:
Consider allowing removal of liquidity even when the pool is not enabled, that is, when delta is negative
Upgraded Q -> 2 from #41 [1727128887972]
Audit: 2024-08-superposition-findings
Severity: MEDIUM

Judge has assessed an item in Issue #41 as 2 risk. The relevant finding follows:

 ## [02] The decrPosition09293696 function is missing a parameter.
### Link
github:[https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/SeawaterAMM.sol#L476](https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/SeawaterAMM.sol#L476)
### Description
The `decrPosition09293696` function is intended to call the `decr_position_09293696` function in the Rust contract, but it is missing the `pool` parameter.  

```solidity
    function decrPosition09293696(
        uint256 /* id */,
        uint256 /* amount0Min */,
        uint256 /* amount1Min */,
        uint256 /* amount0Max */,
        uint256 /* amount1Max */
    ) external returns (uint256, uint256) {
        directDelegate(_getExecutorUpdatePosition());
    }
```
```rust
    /// Refreshes and updates liquidity in a position, transferring tokens to the user with restrictions.
    /// See [Self::adjust_position_internal].
    #[allow(non_snake_case)]
    pub fn decr_position_09293696(
        &mut self,
        pool: Address,
        id: U256,
        amount_0_min: U256,
        amount_1_min: U256,
        amount_0_max: U256,
        amount_1_max: U256,
    ) -> Result<(U256, U256), Revert> {
        self.adjust_position_internal(
            pool,
            id,
            amount_0_min,
            amount_1_min,
            amount_0_max,
            amount_1_max,
            true,
            None,
        )
    }
}
```
### Recommendation
Add the `pool` parameter to the decrPosition09293696 function

```diff
    function decrPosition09293696(
+       address /* pool */,
        uint256 /* id */,
        uint256 /* amount0Min */,
        uint256 /* amount1Min */,
        uint256 /* amount0Max */,
        uint256 /* amount1Max */
    ) external returns (uint256, uint256) {
        directDelegate(_getExecutorUpdatePosition());
    }
```
Upgraded Q -> 2 from #167 [1727128717282]
Audit: 2024-08-superposition-findings
Severity: MEDIUM

Judge has assessed an item in Issue #167 as 2 risk. The relevant finding follows:

 ## [LOW-3] No function to set the ```fee_protocol``` of a pool:
Uniswap v3 has a function that let's the factory owner to set the protocol's fee denominator value for a pool. However, no such function exists in pool.rs. However, the pool storage contains the fee_protocol var:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/pool.rs#L31

```rust
fee_protocol: StorageU8,
```

Therefore, the protocol won't be able to collect fee on the swap operations that would take place.

### Recommended Mitigation Steps:
Add a function that would let the admin set the fee_protocol variable.



## [LOW-4] No use of seconds and tick_cumulative values of a tick:
The tick.rs contains the following in its storage:

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/seawater/src/tick.rs#L54

```rust
    tick_cumulative_outside: StorageI64,
    seconds_per_liquidity_outside: StorageU160,
    seconds_outside: StorageU32,
```

However, these values are never set and used by the protocol. These values are basically maintained by uniswap v3 for other external contracts to use.

### Recommended Mitigation Steps:
It is recommended to remove these storage vars if there are not to be used.
Upgraded Q -> 2 from #169 [1727128613860]
Audit: 2024-08-superposition-findings
Severity: MEDIUM

Judge has assessed an item in Issue #169 as 2 risk. The relevant finding follows:

 ### 2. OwnershipNFTs's approve function allows approved users to also perform approvals.

* Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L160-L163

#### Impact

OwnershipNFTs's approve function allows approved users to also perform approvals, which can lead to approval loss.

The approve function checks if `msg.sender` is authorized. 

```solidity
    function approve(address _approved, uint256 _tokenId) external payable {
        _requireAuthorised(msg.sender, _tokenId);
        getApproved[_tokenId] = _approved;
    }
```
This is enforced by the `_requireAuthorised` which ensures that caller is either owner, owner's operator or approved to spend the token.
```solidity
    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!");
    }
```
This is an issue because any user that had be approved to spend the token risks losing their approval after calling the `approve` function.

#### Recommended Mitigation Steps

Recommend limiting the approval to the owner and operator instead.
OwnershipNFTs contract has Inconsistent Callback Verification in safeTransferFrom Functions
Audit: 2024-08-superposition-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
Title: OwnershipNFTs contract has Inconsistent Callback Verification in safeTransferFrom Functions
•	Severity: Medium
•	Impact: The _onTransferReceived function does not correctly handle the callback verification, which could lead to the loss of NFTs if they are transferred to contracts that do not support the ERC721 standard.
•	Status: Unresolved
•	File: OwnershipNFTs.sol
•	Lines Affected: 138-157

The safeTransferFrom function in the OwnershipNFTs contract performs an internal _transfer followed by an _onTransferReceived call to verify that the recipient contract is able to handle the ERC721 token. However, the _onTransferReceived function does not correctly handle the callback verification, which could lead to the loss of NFTs if they are transferred to contracts that do not support the ERC721 standard.

Additionally, there are duplicate safeTransferFrom functions with different signatures but identical logic. Both functions lack proper handling of the expected selector from the onERC721Received callback.

The current implementation may result in NFTs being transferred to contracts that do not implement the ERC721 receiving interface properly. This could lead to tokens being irrecoverably locked in the recipient contract. Furthermore, the duplicate functions add unnecessary complexity and increase the risk of inconsistencies or errors.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

Here is the current implementation of the functions:
```sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

interface ISeawaterAMM {
    function positionOwnerD7878480(uint256 _tokenId) external view returns (address);
    function transferPositionEEC7A3CD(uint256 _tokenId, address _from, address _to) external;
}

interface IERC721TokenReceiver {
    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
}

contract OwnershipNFTs {
    ISeawaterAMM immutable public SEAWATER;

    constructor(ISeawaterAMM _seawater) {
        SEAWATER = _seawater;
    }

    function ownerOf(uint256 _tokenId) public view returns (address) {
        (bool ok, bytes memory rc) = address(SEAWATER).staticcall(abi.encodeWithSelector(
            SEAWATER.positionOwnerD7878480.selector,
            _tokenId
        ));
        require(ok, "position owner revert");
        (address owner) = abi.decode(rc, (address));
        return owner;
    }

    function _onTransferReceived(
        address _sender,
        address _from,
        address _to,
        uint256 _tokenId
    ) internal {
        if (_to.code.length == 0) return;

        bytes4 data = IERC721TokenReceive
Incorrect onERC721Received Selector Check in `_onTransferReceived()`
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
The incorrect check in `_onTransferReceived()` prevents `NFTs` from being transferred to contracts that correctly implement the `ERC721` token receiver interface.

## Proof of Concept
The [`_onTransferReceived()`](https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L73-L96) function is designed to ensure that when an `NFT` is transferred to a contract, the `recipient contract` correctly implements the `onERC721Received` function. This function should return a specific `selector` to confirm that it can handle `ERC721` tokens.
```solidity
    // if the callback doesn't return the selector, revert!
    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"
        );
    }
``` 
However, the [current implementation](https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/sol/OwnershipNFTs.sol#L92-L95) incorrectly checks for inequality rather than equality.
```solidity
        require(
            data != IERC721TokenReceiver.onERC721Received.selector,
            "bad nft transfer received data"
        );
```
This means that the function will `revert` if the `recipient contract` correctly returns the expected `selector`, which is the opposite of the intended behavior. As a result, any contract that correctly implements the `onERC721Received` function will be unable to receive `NFTs` from this contract.

## Tools Used
Manual Review

## Recommended Mitigation Steps
The `require` statement should be corrected to check for equality, ensuring that the recipient contract's `onERC721Received` function returns the `expected selector`.
```diff
        require(
-           data != IERC721TokenReceiver.onERC721Received.selector,
+           data == IERC721TokenReceiver.onERC721Received.selector, 
            "bad nft transfer received data"
        );
```


## Assessed type

Invalid Validation
OwnershipNFT transfer via `safeTransferFrom` to non-EOA will be reverted.
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
OwnershipNFTs.sol has custom implementation of `safeTransferFrom()`. After _transfer it calls `_onTransferReceived()`, which has require statement at the end. 
Requirement checks if returned data from _to-`IERC721TokenReceiver(_to).onERC721Received()` is equal to `IERC721TokenReceiver.onERC721Received.selector`. 

for success execution returned data and internal selector won't be equal, but thats incorrect. 


## 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"
        );
    }
```
if `_to` is not smart-contract  after 80 line if statement, function gets data from onERC721Received of `_to`. 

require statement on line 92-95 must be check if them are equal, but here we see opposite, so we can say that some non-EOA(e.x. safeWallet, gnosis) can't use `safeTransferFrom` function.

https://gist.github.com/alekoisaev/57ac98212ed14d5db21c2460d7974d09


simple contract for testing using remix. 
1. deploy both contracts using remix on local.
2. Call `testTransferWithReceived()` with any values except `_to`, which must be equal to address of `NFTReceiver` contract.

## Tools Used
Manual review, Remix.

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


## Assessed type

ERC721
Incorrect Logic in _onTransferReceived Function Causes Valid ERC721 Safe Transfers to 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#L93


# Vulnerability details

## Overview

The OwnershipNFTs contract, which implements the ERC721 standard for NFTs, contains a faulty implementation in its _onTransferReceived function. The current implementation incorrectly rejects valid transfers, potentially preventing legitimate transfers to smart contract wallets or other contracts designed to receive ERC721 tokens.

Here is the faulty implementation:
```
 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
            ""
        );
         @//audit -- inverted assertion, should be ==
        require(
            data != IERC721TokenReceiver.onERC721Received.selector,
            "bad nft transfer received data"
        );
    }
```
As you can see, the inverted logic in the requirement check of the onERC721Received function's return value. The function currently reverts when it receives the correct selector, which is the opposite of the intended behavior.

Impact
The impact of this vulnerability is severe:

All safe transfers to contract addresses will fail, even if the receiving contract correctly implements the onERC721Received function.

## Proof of Concept
The test testIncorrectTransferRejection in the POC contract below shows that a transfer to a valid receiver (a contract correctly implementing onERC721Received) fails due to this bug.

here is the output logs:
```
Ran 1 test for src/test/Superposition:OwnershipNFTs.sol:OwnershipNFTsTest
[PASS] testIncorrectTransferRejection() (gas: 75369)
Logs:
  Transfer should have succeeded but failed. Token still owned by: 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496

ValidReceiver::onERC721Received(OwnershipNFTsTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], OwnershipNFTsTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 1, 0x)
    │   │   └─ ← [Return] 0x150b7a02
    │   └─ ← [Revert] revert: bad nft transfer received data

Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 720.04µs (162.46µs CPU time)

//test setup
 function setUp() public {
        seawater = new MockSeawaterAMM();
        nft = new OwnershipNFTs("TestNFT", "TNFT", "http://test-uri.com", ISeawaterAMM(address(seawater)));
        validReceiver = new ValidReceiver();
    }

// test function
function testIncorrectTransferRejection() public {
        address owner = address(this);
        address recipient = address(validReceiver);
        uint256 tokenId = 1;
Incorrect require statement in _onTransferReceived()
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
When one NFT is transferred to one contract, the function `_onTransferReceived()` will be reverted because of the incorrect require statement. 

## Proof of Concept
When one NFT is transferred to one contract via safeTransferFrom(), we will try to call `to` contract's `onERC721Received()` callback and check the return value's validation. The return value should be expected to `IERC721Receiver.onERC721Received.selector`. 

But in function `_onTransferReceived()`, the require makes mistakes. This will cause most contracts which support ERC721 callback will be reverted when one NFT is safeTransferFrom to one contract.

```javascript
    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

## Recommended Mitigation Steps
Correct the requirement in function `_onTransferReceived()`.


## Assessed type

DoS
Non-functional protocol fee
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#L344-L347


# Vulnerability details

## Impact
There is not one function to set `fee_protocol`. This will cause protocol fee is always zero. The protocol team cannot collect any protocol fee.

## Proof of Concept
In pool.rs, variable `fee_protocol` is used to record the protocol fee rate. When traders swap via pool, the swap fees will be splitted into two part. One part is for LP holders and another part is for protocol team.

The problem is that we don't initialize the variable `fee_protocol`. And what's more, we missed one setting function to set the variable `fee_protocol`.
```javascript
            if fee_protocol > 0 {
                // Split one part of swap fee to the protocol fee.
                let delta = step_fee_amount.wrapping_div(U256::from(fee_protocol));
                step_fee_amount -= delta;
                // Update protocol fee in the temporary state.
                state.protocol_fee += u128::try_from(delta).or(Err(Error::FeeTooHigh))?;
            }

```

## Tools Used
Manual

## Recommended Mitigation Steps
Add one onlyOwner function to set `fee_protocol`, just like Uniswap V3.


## Assessed type

Error