xanoutas/solidity-security-findings huggingface dataset:
Security Vulnerabilities and Access Control Issues in Governance.sol Smart Contract
Audit: 2024-03-zksync-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-zksync/blob/main/code/contracts/ethereum/contracts/governance/Governance.sol/README.md?plain=1#L249


# Vulnerability details

1. Access Control and Permissions:

Error: The updateDelay and updateSecurityCouncil functions can be called by the contract itself (onlySelf modifier), which poses a security risk if these functions are not intended for internal use only.

Solution: Ensure that only authorized users or contracts can call these functions by implementing proper access control mechanisms. Remove the onlySelf modifier or adjust it to restrict access to authorized users.

function updateDelay(uint256 _newDelay) external onlyOwner {
    emit ChangeMinDelay(minDelay, _newDelay);
    minDelay = _newDelay;
}

function updateSecurityCouncil(address _newSecurityCouncil) external onlyOwner {
    emit ChangeSecurityCouncil(securityCouncil, _newSecurityCouncil);
    securityCouncil = _newSecurityCouncil;
}



2. Potential Reentrancy Vulnerability:

Error: The _execute function does not have proper error handling for failed external calls, which may lead to reentrancy vulnerabilities if external calls fail unexpectedly.


Solution: Implement proper error handling in the _execute function to revert the transaction if an external call fails. This prevents potential reentrancy attacks. Use the require(success, "External call failed"); statement after each external call to revert the transaction in case of failure.

function _execute(Call[] calldata _calls) internal {
    for (uint256 i = 0; i < _calls.length; ++i) {
        (bool success, bytes memory returnData) = _calls[i].target.call{value: _calls[i].value}(_calls[i].data);
        require(success, "External call failed");
    }
}


3. Gas-related Vulnerabilities:

Error: The _execute function does not check the gas consumption of external calls, which may lead to out-of-gas errors or vulnerabilities if the gas limit is exceeded.


Solution: Perform gas estimation for external calls in the _execute function to ensure that the gas limit is not exceeded. Consider using the gasleft() function to estimate gas consumption and handle gas-related issues appropriately.


function _execute(Call[] calldata _calls) internal {
    for (uint256 i = 0; i < _calls.length; ++i) {
        // Estimate gas consumption for external calls
        uint256 gasLimit = gasleft() - 5000; // Adjust gas limit as needed
        (bool success, bytes memory returnData) = _calls[i].target.call{value: _calls[i].value, gas: gasLimit}(_calls[i].data);
        require(success, "External call failed");
    }
}


4. Potential Timestamp Manipulation:

Error: The _schedule function allows operations to be scheduled in the past if _delay is set to a negative value, which may lead to timestamp manipulation vulnerabilities.


Solution: Add a check in the _schedule function to ensure that _delay is a non-negative value to prevent scheduling operations in the past. Use require(_delay >= 0, "D
Incorrect token-in transfer amount in swap_2_internal_erc20
Audit: 2024-10-superposition-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Proof of Concept

In the `swap_2_internal` function, if the first pool experiences a liquidity depletion, it could result in `amount_in` being less than `original_amount`. In this case, the contract takes `amount_in` tokens from the user and returns `original_amount - amount_in` tokens back to the user. However, the correct calculation should take `original_amount` tokens from the user first. The pool loses `original_amount - amount_in` tokens in such cases.

```rust
let (
    original_amount,
    amount_in,
    amount_out,
    _interim_usdc_out,
    _final_tick_in,
    _final_tick_out,
) = Self::swap_2_internal(pools, from, to, amount, min_out)?;

#[cfg(feature = "testing-dbg")]
dbg!((
    "swap 2 internal erc20 after internal",
    amount_in.to_string(),
    amount_out.to_string()
));

// transfer tokens
erc20::take(from, amount_in, permit2)?;
erc20::transfer_to_sender(to, amount_out)?;

if original_amount > amount_in {
    erc20::transfer_to_sender(
        to,
        original_amount
            .checked_sub(amount_in)
            .ok_or(Error::TransferToSenderSub)?,
    )?;
}
```

## Recommended Mitigation Steps

```diff
-        erc20::take(from, amount_in, permit2)?;
+        erc20::take(from, original_amount, permit2)?;
```


## Assessed type

Math
Incorrect token pull logic in `swap_2_internal_erc20()`
Audit: 2024-10-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L287-L297


# Vulnerability details

## Proof of Concept
In the [`swap_2_internal_erc20()`](https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L287-L297) function, the contract pulls only the `amount_in` (*the amount required for the swap*) from the user, but it attempts to refund the difference between `original_amount` and `amount_in`. But, since only `amount_in` is pulled, there should be no excess tokens to  refund.

```solidity
    // Pulling only `amount_in` from the user
    erc20::take(from, amount_in, permit2)?;

    // Attempting to refund the difference between `original_amount` and `amount_in`
    if original_amount > amount_in {
        erc20::transfer_to_sender(
            to,
            original_amount
                .checked_sub(amount_in)
                .ok_or(Error::TransferToSenderSub)?,
        )?;
    }
```

While the refund may still succeed if the pool holds sufficient tokens, it is not guaranteed to work as intended because the contract only pulls `amount_in` from the user. The contract may end up refunding tokens that were not pulled from the user, possibly relying on leftover tokens in the contract or if none, a revert will occur.

## Impact
Users may receive refunds from the pool of tokens in the contract, which may belong to other users or serve other purposes, leading to inconsistencies in token accounting.

## Recommended Mitigation Steps
The contract should pull the entire `original_amount` from the user. This ensures that the refund logic functions as intended, and any excess tokens can be safely and accurately returned to the user.
```diff
-   erc20::take(from, amount_in, permit2)?;
+   erc20::take(from, original_amount, permit2)?;
```




## Assessed type

Error
Protocol is wrongly refunding too many funds in a swap
Audit: 2024-10-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L290-L297


# Vulnerability details

## Impact

AMM is providing excessive refunds for each swap, which may result in the funds being drained.

## Proof of Concept

This previous issue wasn't fixed properly:
https://github.com/code-423n4/2024-08-superposition-findings/issues/12


With the current new logic, users are refunded with an excessive amount of tokens:

```rust
        erc20::take(from, amount_in, permit2)?;
        erc20::transfer_to_sender(to, amount_out)?;

        if original_amount > amount_in {
            erc20::transfer_to_sender(
                to,
                original_amount
                    .checked_sub(amount_in)
                    .ok_or(Error::TransferToSenderSub)?,
            )?;
        }
```
https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L290-L297


The issue was that an user was paying `original_amount` even if the actual swap was only `amount_in`, but now:

1) User pays `amount_in` (the actual swap amount)
2) User receives `amount_out` (the actual swap amount)
3) If the original intended payment is higher than the actual swap amount, users are 'refunded' with the difference between these values

This can be abused to drain all the funds from the AMM.

## Recommended mitigation steps

Consider removing the last transfer:

```diff
        erc20::take(from, amount_in, permit2)?;
        erc20::transfer_to_sender(to, amount_out)?;

-       if original_amount > amount_in {
-           erc20::transfer_to_sender(
-               to,
-               original_amount
-                   .checked_sub(amount_in)
-                   .ok_or(Error::TransferToSenderSub)?,
-           )?;
-       }
```
Token is refunded wrongly in the two-step swap
Audit: 2024-10-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L290-L297


# Vulnerability details

## Proof of Concept

The `swap_2_internal_erc20` function in the Seawater AMM refunds excess tokens are refunded in the wrong token type. The happens when the original amount provided by the user is greater than the amount actually used by the AMM.

The problem here is that the function is attempting to refund the excess amount using the wrong token. When refunding excess tokens, it's using the destination token (`to`) instead of the source token (`from`). This is incorrect because the user provided `original_amount` of the source token (`from`), the AMM used `amount_in` of that token for the swap. Also, if there's a difference, the excess should be refunded in the original source token (`from`), not the destination token (`to`).

#### Bug location:  
https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L290-L297

```rust
if original_amount > amount_in {
    erc20::transfer_to_sender(
        to,  // Critical bug: Using destination token for refund
        original_amount
            .checked_sub(amount_in)
            .ok_or(Error::TransferToSenderSub)?,
    )?;
}
```

The function flows thus:
- User provides `original_amount` of token `from`
- AMM uses `amount_in` for the swap
- If `original_amount > amount_in`, excess should be refunded

Refund is executed using destination token (`to`) instead of source token (`from`)

Consider a scenario given:

- User wants to swap TokenA for TokenB
- User approves 100 TokenA
- AMM only requires 80 TokenA
Expected:
- Refund should be 20 TokenA
Actual (buggy):
- Protocol attempts to refund 20 TokenB

As a result, users receive refunds in wrong token type



## Recommendation:
```diff
if original_amount > amount_in {
    erc20::transfer_to_sender(
-         to,
+        from,
        original_amount
            .checked_sub(amount_in)
            .ok_or(Error::TransferToSenderSub)?,
    )?;
}
```
`createPoolD650E2D0` will not work due to mismatch in solidity and stylus function definitions
Audit: 2024-10-superposition-findings
Severity: HIGH

# Lines of code

 https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/sol/SeawaterAMM.sol#L160-L168
https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L802


# Vulnerability details

### Proof of Concept

1. Context 
 
SeaWaterAMM.sol holds the `createPoolD650E2D0` which allows the admin to initialize a new pool. It calls the `create_pool_D650_E2_D0` function in the stylus.

2. Bug Location
 
As can be seen from the `create_pool_D650_E2_D0` function, it takes in the token address, sqrtPriceX96 and fee. 
 
```rust
    pub fn create_pool_D650_E2_D0(
        &mut self,
        pool: Address,
        price: U256,
        fee: u32,
    ) -> Result<(), Revert> {
 //...
     }
 }
```
 
But `createPoolD650E2D0`'s definition takens in more, token address, sqrtPriceX96, fee, tick spacing and maxLiquidityPerTick, causing a mismatch between the function definitions of the Solidity and Stylus contracts.

```solidity
    function createPoolD650E2D0( //@audit
        address /* token */,
        uint256 /* sqrtPriceX96 */,
        uint32 /* fee */,
        uint8 /* tickSpacing */,
        uint128 /* maxLiquidityPerTick */
    ) external {
        directDelegate(_getExecutorAdmin());
    }
```

 
3. Final Effect
 
Calls to the function will always fail, breaking SeawaterAMM.sol's functionality to create a pool position.


### Recommended Mitigation Steps
Remove the unneeded parameters.
 
```diff
    function createPoolD650E2D0( //@audit
        address /* token */,
        uint256 /* sqrtPriceX96 */,
        uint32 /* fee */,
-       uint8 /* tickSpacing */,
-       uint128 /* maxLiquidityPerTick */
    ) external {
        directDelegate(_getExecutorAdmin());
    }
```


## Assessed type

Context
Incorrect token refunded in `swap_2_internal_erc20()`
Audit: 2024-10-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L290-L297


# Vulnerability details

## Proof of Concept
From the previous audit, it was determined that the protocol may excessively pull tokens (`original_amount`) from the user during `swap_2` than actually is required (`amount_in`) and that in such a case, a refund should be made to the user.
This is then enforced in the code [as follows](https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L290-L297):
```solidity
    if original_amount > amount_in {
        erc20::transfer_to_sender(
>>          to,
            original_amount
                .checked_sub(amount_in)
                .ok_or(Error::TransferToSenderSub)?,
        )?;
    }
```
The `swap_2_internal_erc20()` function performs a token swap from `from` (input token) to `to` (output token). If the contract pulls more `from` tokens than required for the swap (`original_amount > amount_in`), it incorrectly refunds the excess in the form of `to` tokens, instead of returning the excess `from` tokens.

## Impact
The current refund logic is flawed because it assumes that the excess `amountX` of `from` tokens can simply be returned as `amountX` of `to` tokens. This assumption is incorrect because the exchange rate between from and to tokens is not guaranteed to be `1:1`.

---> This creates a situation where the user is refunded a different token, which may not match the original amount's value, leading to financial discrepancies for both the user and protocol.

## Recommended Mitigation Steps
The refund logic should return the excess `from` tokens back to the user, ensuring consistency with the swap flow. 
```diff
    if original_amount > amount_in {
        erc20::transfer_to_sender(
-           to,
+           from,
            original_amount
                .checked_sub(amount_in)
                .ok_or(Error::TransferToSenderSub)?,
        )?;
    }
```



## Assessed type

Error
Users are incorrectly refunded when liqudity is insufficient
Audit: 2024-10-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L287-L297


# Vulnerability details

### Proof of Concept

1. Context 

In `swap_2_internal`, if the first pool doesn't have enough liquidity, `amount_in`could be less than `original_amount`, and as expected, `amount_in` is taken from swapper. But the function still refunds `original_amount - amount_in` to the user if `original_amount` is more than `amount_in`.

2. Bug Location
 
From the function, we can see than `amount_in` is taken from swapper. Then the function checks if `original_amount` is more than `amount_in`, before which the difference is transferred back to the sender.
```rs
>>      erc20::take(from, amount_in, permit2)?;
        erc20::transfer_to_sender(to, amount_out)?;

>>      if original_amount > amount_in {
            erc20::transfer_to_sender(
                to,
                original_amount
>>                  .checked_sub(amount_in)
                    .ok_or(Error::TransferToSenderSub)?,
            )?;
        }
```
 
3. Final Effect
 
An unnecessary refund is processed leading to loss of funds for the protocol. Malicious users can take advantage of this to "rob" the protocol of funds through the refunds.


### Recommended Mitigation Steps

No need to process refunds since `amount_in` is already taken.

```diff
        erc20::take(from, amount_in, permit2)?;
        erc20::transfer_to_sender(to, amount_out)?;

-       if original_amount > amount_in {
-           erc20::transfer_to_sender(
-               to,
-               original_amount
-                   .checked_sub(amount_in)
-                   .ok_or(Error::TransferToSenderSub)?,
-           )?;
        }
```


## Assessed type

Context
No slippage control when withdrawing a position leads to loss of funds
Audit: 2024-10-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L749


# Vulnerability details

## Impact

An attacker can sandwich a user withdrawing funds as there is no way to put slippage protection, which will cause a large loss of funds for the victim.

## Proof of Concept

`decr_position_09293696` function was removed entirely. Now, the only way for users to withdraw funds is by calling [`update_position_C_7_F_1_F_740`](https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L749) with negative delta.

The issue is that in this way, users can't have any slippage protection. `decr_position` allowed users to choose an `amount_0_min` and `amount_1_min` of funds to receive, which is now zero.

This allows an attacker to sandwich their withdrawal to steal a large amount of funds.

## Recommended mitigation steps

Consider reintroducing a withdrawal function that offers slippage protection to users (they should be able to choose `amount_0_min, amount_1_min, amount_0_desired`, and `amount_1_desired`).
It's still not possible to set pool's protocol fees 
Audit: 2024-10-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L899


# Vulnerability details

## Impact
The admin can't set a pool's protocol fee because the function has not been implemented. 

## Proof of Concept
This previous issue wasn't fixed properly:
https://github.com/code-423n4/2024-08-superposition-findings/issues/8

The root cause is that the admin has no way to call `set_fee_protocol_C_B_D_3_E_C_35` through Seawater, as the function wasn't implemented.

As a result, the original issue persists because protocol fees cannot be set.

## Recommended Mitigation Steps
Consider adding the following function to `SeaWaterAMM.sol`:

```solidity
    function setFeeProtocolCBD3EC35(address /* pool */, uint8 /* feeProtocol0 */, uint8 /* feeProtocol1 */) external {
        directDelegate(_getExecutorAdmin());
    }
```



## Assessed type

Access Control
Tokens are pulled from  users without verifying pool status contrary to requirement
Audit: 2024-10-superposition-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L679-L687
https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L716-L738


# Vulnerability details

## Proof of Concept
Both the `update_position_internal()` and `adjust_position_internal()` functions are responsible for managing token positions, which involves taking tokens from users. However, there is a critical inconsistency in how each function verifies the operational status of the liquidity `pool` before performing token transfers from the user.

[`update_position_internal()`](https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L679-L687)
This function checks if the `pool` is `enabled` before taking tokens from the user.
```solidity
            // if we're TAKING, make sure that the pool is enabled.
            assert_or!(pool.enabled.get(), Error::PoolDisabled);
            ---SNIP---
            erc20::take(pool_addr, token_0.abs_pos()?, permit_0)?;
            erc20::take(FUSDC_ADDR, token_1.abs_pos()?, permit_1)?;
```
[`adjust_position_internal()`](https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/lib.rs#L716-L738)
This does not explicitly check whether the `pool` is `enabled` before proceeding.
```solidity
    let (amount_0, amount_1) =
            self.pools
                .setter(pool)
                .adjust_position(id, amount_0_desired, amount_1_desired)?;
    ---SNIP---
    erc20::take(pool, amount_0, permit_0)?;
    erc20::take(FUSDC_ADDR, amount_1, permit_1)?;
```
First, it calls `self.pools.setter(pool).adjust_position(...)` which has the following [comment](https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/pool.rs#L222-L225):
```solidity
    // [update_position] should also ensure that we don't do this on a pool that's not currently running
    self.update_position(id, delta)
```
The comment in the `adjust_position()` function implies that a check for the pool's operational state is necessary and should be enforced in [`update_position()`](https://github.com/code-423n4/2024-10-superposition/blob/7ad51104a8514d46e5c3d756264564426f2927fe/pkg/seawater/src/pool.rs#L86-L195). However, `update_position()` function does not make such enforcement as it does not check for pool status.

## Impact
Users could unintentionally have their tokens adjusted or transferred to a pool that is not operational which is not in accordance with protocol requirement. This also exposes users to risks in the event that there are potential issues with the pool.

## Recommended Mitigation Steps
Modify the `adjust_position_internal()` function to include a status check before executing the position adjustment:
```diff
    // Ensure the pool is enabled be