xanoutas/solidity-security-findings huggingface dataset:
Anyone Can Purchase Without Being Whitelisted Due to Incorrect Access Control
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/Whitelist.sol#L204


# Vulnerability details

# [H-01] Anyone Can Purchase Without Being Whitelisted Due to Incorrect Access Control
The `Whitelist::checkWhitelist` function is designed to verify if an address is whitelisted by comparing the `_whitelistIndex[to]` against the `_allowedWhitelistIndex`. However, due to a logical oversight, the condition fails to properly exclude non-whitelisted users because it does not account for addresses with a `_whitelistIndex` of zero. As a result, anyone can bypass the whitelist requirement and purchase without proper authorization.
The vulnerability arises from the following conditional check within the `checkWhitelist` function:
This condition incorrectly assumes that any address not explicitly whitelisted (i.e., those with a `_whitelistIndex` of zero) is automatically excluded. However, the absence of a check for `_whitelistIndex[to] == 0` means that non-whitelisted users can still pass the check.

```javascript
function checkWhitelist(
        address from,
        address to,
        uint256 amount
    ) external onlyVultisig {
        console2.log("checkWihitelist before if");

        if (from == _pool && to != owner()) {
            console2.log("checkWihitelist after if");

            // We only add limitations for buy actions via uniswap v3 pool
            // Still need to ignore WL check if it's owner related actions
            if (_locked) {
                revert Locked();
            }

            if (_isBlacklisted[to]) {
                revert Blacklisted();
            }
            console2.log(_allowedWhitelistIndex, _whitelistIndex[to], to);
            if (
@>              //_whitelistIndex[to] == 0 || //e this line should be added
@>              _allowedWhitelistIndex == 0 ||
                _whitelistIndex[to] > _allowedWhitelistIndex 
            ) {
                revert NotWhitelisted();
            }

            // // Calculate rough ETH amount for VULT amount
            uint256 estimatedETHAmount = IOracle(_oracle).peek(amount);
            if (_contributed[to] + estimatedETHAmount > _maxAddressCap) {
                revert MaxAddressCapOverflow();
            }

            _contributed[to] += estimatedETHAmount;
        }
    }
```
## Impact 
This flaw renders the whitelist mechanism ineffective, allowing unrestricted access to purchasing functionalities. It undermines the security and integrity of the system, potentially enabling unauthorized users to exploit the contract.
## Proof of Concept
A simple test case demonstrates the vulnerability:

```javascript 
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Test, console2} from "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {VultisigWhitelisted} from "../src/extensions/Vultisig
Adversaries can manipulate sqrtPriceX96 in an empty UniswapV3 pool and use single-sided liquidity to block the project launches
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOManager.sol#L187-L198


# Vulnerability details

## Impact
- Block project from being launched

## Description
Project's admin specify an initial pool price when initiate a new project.  
When all conditions in all pools are met, anyone can call `launch` function in `ILOManager.sol` to launch their ILO.  

Inside `launch` function, there is a check that current pool's `sqrtPriceX96` is equal to the initial pool price set during project initiation.  
The check ensures that ILO launches at the project specified price.  
See: [ILOManager.sol#L187-L198](https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOManager.sol#L187-L198)
```solidity
function launch(address uniV3PoolAddress) external override {
    require(block.timestamp > _cachedProject[uniV3PoolAddress].launchTime, "LT");
    (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(uniV3PoolAddress).slot0();
    require(_cachedProject[uniV3PoolAddress].initialPoolPriceX96 == sqrtPriceX96, "UV3P"); // <-- current sqrtPriceX96 must stay the same
    address[] memory initializedPools = _initializedILOPools[uniV3PoolAddress];
    require(initializedPools.length > 0, "NP");
    for (uint256 i = 0; i < initializedPools.length; i++) {
        IILOPool(initializedPools[i]).launch();
    }

    emit ProjectLaunch(uniV3PoolAddress);
}
```
### Manipulate sqrtPriceX96 of UniswapV3 pool
UniswapV3 pool stores `sqrtPriceX96` in `slot0`. It is the current price corresponding with the current tick of the pool.  
It changes when the swap operation cross the tick.  

However, in an empty pool (liquidity=0), after pool's initialization with `sqrtPriceX96`, UniswapV3 pool behaves weirdly.  
Calling `swap` function on the pool will not revert.  
`swap(address recipient, bool zeroForOne, int256 amountSpecified, uint160 sqrtPriceLimitX96, bytes calldata data)`  
In turn, the pool will cross to the corresponding tick of the specified `sqrtPriceLimitX96`, requiring no tokens transfer.  

### Single-sided liquidity  
Providing liquidity in Uniswap V3 pool outside of current price/tick requires only single token of the pair.  
Reference: [Single-sided Liquidity](https://support.uniswap.org/hc/en-us/articles/20902968738317-What-is-single-sided-liquidity)  

### Attack scenario
Let's say Project A wants to launch a new ILO for their new token, $NEW and pair it with $USDC. They proceed to init their project and various pools.  
Supposed that the newly deployed UniswapV3 pool from `initProject` has `token0=$USDC` and `token1=$NEW` and they choose `k` as initial price, equals to tick=`t_k`.  

Adversaries can manipulate `sqrtPriceX96` (lower it down) to a value `x; x < k`, by calling `swap` function directly as mentioned in the previous section. Then, provide single-sided liquidity of token0 (USDC) by specifying the tick range just between the price `x` and `k`.  

Then, when `block.timestamp` reaches launch time, pool's admin will not be
Flaw in Whitelist.sol: Unauthorized Users Can Bypass Whitelist Check and Receive Funds
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/Whitelist.sol#L204-L228


# Vulnerability details

## Impact

`Whitelist.sol` file has a function `checkWhitelist()` to check whether the `to` address is whitelisted or not. It is not correctly checking for whitelist address. When the `_whitelistIndex[to]` is equal to zero which is default value it still passes this `checkWhitelist()` check This flaw can result in unauthorized users bypassing the whitelist restrictions. But it should revert in this case. Confirmed by the sponsors.

## Proof of Concept
[Code](https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/Whitelist.sol#L204-L228)

## Tools Used
Manual Review

## Recommended Mitigation Steps

Add this check also

```
if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] > _allowedWhitelistIndex
++     _whitelistIndex[to] == 0 
    ){ 
                revert NotWhitelisted();
   }
```


## Assessed type

Invalid Validation
DoS attack making projects unable to launch
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOManager.sol#L190


# Vulnerability details

## Impact
The `lunch` function of ILOManager requires that the initialPoolPriceX96 which was used to create and initialize the uniswap pool most be equal to the current pool's squarePriceX96 which can be changed when some one add liquidity to the pool. An attacker can add single-sided liquidity to the pool that will change the squarePriceX96 of the pool using other means in order to make the `launch` function always fail.

The line where the vulnerability is was indicated using @> in the below code.
```javascript
function launch(address uniV3PoolAddress) external override {
   require(block.timestamp > _cachedProject[uniV3PoolAddress].launchTime, "LT");
   (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(uniV3PoolAddress).slot0();
@> require(_cachedProject[uniV3PoolAddress].initialPoolPriceX96 == sqrtPriceX96, "UV3P");
   address[] memory initializedPools = _initializedILOPools[uniV3PoolAddress];
   require(initializedPools.length > 0, "NP");
   for (uint256 i = 0; i < initializedPools.length; i++) {
     IILOPool(initializedPools[i]).launch();
   }

   emit ProjectLaunch(uniV3PoolAddress);
}
```

## Proof of Concept
Below points decscribes how the attack can be made.
1. Project owner initiate a new project using correct parameters.
2. While initiating the new project uniswap pool was created and initialized, Its address saved in Project.uniV3PoolAddress struct.
3. An attacker can read the address from the struct field.
4. The attacker add single-sided liquidity to the pool that made changes to the squarePriceX96 value.
5. Now the project can not be launched due to the said require check in the `launch` function.


## Tools Used
Manual review

## Recommended Mitigation Steps
Its better to subtract squarePriceX96 return by slot0 from the initialPoolPriceX96 then use the result to calculate the tokens amount and liquidity. 


## Assessed type

DoS
Improper validation of whitelist member inside  `checkWhitelist()` which leads to non whitelist member to contribute
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/Whitelist.sol#L216
https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/extensions/VultisigWhitelisted.sol#L28


# Vulnerability details

## Impact
Any non-whitelisted user can bypass the validation of the `checkWhitelist()` function. This results, an attacker can easily contribute `estimatedETHAmount` to the `VultiSig` protocol without being in whitelist. 

## Proof of Concept
The vulnerability is in the `checkWhitelist()` of `Whitelist` contract which is called by the `_beforeTokenTransfer()` hook of the VultiSig. 
https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/Whitelist.sol#L216 

```solidity
if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {
   revert NotWhitelisted();
}
```

Attack explained, 
Let's say, two accounts were whitelisted `otherAccount` and `batchedAccount` by owner respectively. i.e, `whitelistCount` = 2. Let's say owner only allowing first account in the whitelist. i.e, `allowedWhitelistIndex` = 1.

1. If `checkWhitelist()` function is called with `to` address as `otherAccount` the call be succeeded as `otherAccount` is in the whitelist and within the `allowedWhitelistIndex`.

2. When `checkWhitelist()` function is called with `to` address as `batchedAccount`  call will be reverted with `NotWhitelisted()` error. Because this account is not within the `allowedWhitelistIndex`.

```solidity
// false || true => true => revert()
if (1 == 0 || 2 > 1) { 
   revert NotWhitelisted();
}
```

3. If the `checkWhitelist()` function is called with `to` address as `attackerAccount` this still passes the check. Because there is no check that `_whitelistIndex[to] != 0`. In the mapping of the `_whitelistIndex` all the uninitialised address keys will get zero index as value.Since the `attackerAccount` is not whitelisted `_whitelistIndex[to]=_whitelistIndex[attackerAccount] = 0`. The `checkWhitelist()` function does the following check in this case, 

```solidity
// false || false => false => whitelist check bypassed
if (1 == 0 || 0 > 2){
}
```

In this way any user who is not whitelisted can be easily perform the whitelisted actions.

## POC
Add this test case to the `hardhat-vultisig/test/unit/Whitelist.ts` to run with `npx hardhat test`

```javascript 
// @audit : POC
  describe.only("Checkwhitelist Bypass", function () {

    it("Not whitelisted account passing checkWhitelist check", async function () {
      const { whitelist, pool, otherAccount, batchedAccount, mockContract, mockOracleSuccess, attackerAccount} = await loadFixture(deployWhitelistFixture);
      await whitelist.setVultisig(mockContract);
      await whitelist.setLocked(false);
      await whitelist.setOracle(mockOracleSuccess);

      await whitelist.addWhitelistedAddress(o
Front-running pool’s initialization can lead to failed launch
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOManager.sol#L187-L198


# Vulnerability details



## Impact
In `ILOManager.sol`, the `launch` function is using `UniswapV3.slot0` where it retrieves the current pool price `sqrtPriceX96`. However, the `sqrtPriceX96` is pulled from `Uniswap.slot0`, which is [the most recent data point](https://docs.uniswap.org/contracts/v3/reference/core/interfaces/pool/IUniswapV3PoolState#slot0) and this price can be manipulated by a malicious actor before the launch, potentially causing the project to fail launching a pool.

## Proof of Concept

`launch()` function:

```javascript
 function launch(address uniV3PoolAddress) external override {
    require(block.timestamp > _cachedProject[uniV3PoolAddress].launchTime, "LT");
@>  (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(uniV3PoolAddress).slot0();
    require(_cachedProject[uniV3PoolAddress].initialPoolPriceX96 == sqrtPriceX96, "UV3P");
    address[] memory initializedPools = _initializedILOPools[uniV3PoolAddress];
    require(initializedPools.length > 0, "NP");
    for (uint256 i = 0; i < initializedPools.length; i++) {
        IILOPool(initializedPools[i]).launch();
    }

    emit ProjectLaunch(uniV3PoolAddress);
}
```
A malicious actor can observe a transaction attempting to call launch and front-run it by submitting a transaction with a manipulated `sqrtPriceX96` value just before the original transaction is mined. 

If an attacker successfully manipulates `sqrtPriceX96` to a value different from `_cachedProject[uniV3PoolAddress].initialPoolPriceX96`, they can prevent launching the pool. 
This manipulation could lead to failed launches/preventing the intended functionality.

## Tools Used
Manual review

## Recommended Mitigation Steps
Use the `TWAP` function instead of `slot0` to get the value of `sqrtPriceX96`.


## Assessed type

Uniswap
Non-whitelisted users will still pass the whitelist check 
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/main/hardhat-vultisig/contracts/Whitelist.sol#L216-L218


# Vulnerability details

`Whitelist.sol: checkWhitelist()` is intended to check if a buyer is eligible for token transfers. However even if a user is not whitelisted he will still pass the whitelist check in this function.

## Impact
Possibly loss of funds for the protocol. Because `checkWhitelist()` is used in the `_beforeTokenTransfer` hook this means that non-whitelisted users will still be able to get token transfers.

## Proof of Concept
Here is the problematic if statement in `checkWhitelist()`:
```solidity
            if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {
                revert NotWhitelisted();
            }
```
The index for every whitelisted user is stored in the `_whitelistIndex` mapping. `_allowedWhitelistIndex` is used to determine how many users are allowed to be whitelisted. For example if the mapping contains indexes of 10 users but `_allowedWhitelistIndex` is 5 then only the first 5 whitelisted users are eligible. That's why we are comparing them.

The issue is that if the `to` address is not whitelisted the mapping will return zero and the check `_whitelistIndex[to] > _allowedWhitelistIndex` will be false and it will not revert like it should.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Check if `_whitelistIndex` mapping returns zero. This check should be sufficient because whitelisted user indexes start from 1 not 0.

```diff
-           if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {
+           if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {
                revert NotWhitelisted();
            }
```


## Assessed type

Other
Hard-coded slippage doesn't provide enough slippage protection
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/main/hardhat-vultisig/contracts/oracles/uniswap/UniswapV3Oracle.sol#L45


# Vulnerability details

## Impact

In the current implementation of `UniswapV3Oracle`, `peek()` function uses hard-coded slippage protection equal to 5%. The problem is that the slippage can be much and much more (or less - that will also make some problems) and this may block the `checkWhitelist()` function call.

## Proof of Concept

Let's take a look at the `peek()` function:

https://github.com/code-423n4/2024-06-vultisig/blob/main/hardhat-vultisig/contracts/oracles/uniswap/UniswapV3Oracle.sol#L45
```
 return (quotedWETHAmount * baseAmount * 95) / 1e20; // 100 / 1e18
```

It uses hard-coded 5% slippage which can create some potential problems. The function is used when calculating `estimatedETHAmount`:

https://github.com/code-423n4/2024-06-vultisig/blob/main/hardhat-vultisig/contracts/Whitelist.sol#L221
```
  uint256 estimatedETHAmount = IOracle(_oracle).peek(amount);
```
But in practice the slippage can be more or less and the users will not be able to use whitelist functionality as their tx will revert. Or, in other case, this slippage may be too much for some users as they are not willing to agree for 5% slippage tolerance.

## Tools Used

Manual review.

## Recommended Mitigation Steps

The slippage parameter should not be fixed and be user-supplied.


## Assessed type

Other
Anyone can buy tokens, without being within the whitelist
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/main/hardhat-vultisig/contracts/Whitelist.sol#L204-L228


# Vulnerability details

## Impact
The checkWhitelist function is supposed to only allow whitelisted addresses to buy Vultisig tokens from the pool. Only whitelisted addresses should buy when _isSelfWhitelistDisabled == true. The checkWhitelist function allows anyone to buy, no matter if they are set as whitelisted by the owner. 

This happens because the [condition](https://github.com/code-423n4/2024-06-vultisig/blob/main/hardhat-vultisig/contracts/Whitelist.sol#L216-L218) that allows whitelist functions checks if _allowedWhitelistIndex is set (above 0) and that the users list index is below that value. For non-whitelisted users, this value is always 0, thus the validation is incorrectly implemented and anyone can bypass it.

## Proof of Concept

- [function](https://github.com/code-423n4/2024-06-vultisig/blob/main/hardhat-vultisig/contracts/Whitelist.sol#L204).
- [condition](https://github.com/code-423n4/2024-06-vultisig/blob/main/hardhat-vultisig/contracts/Whitelist.sol#L216).

- initial state: _allowedWhitelistIndex is set and above 0. _pool is the DEX pool used. _locked is false, to allow for whitelisted users trading. _maxAddressCap is above zero (3 eth).
- function arguments: The user can create any random address and trade into the pool. Then "from" will be the DEX address (buying), "to" the user address and amount any amount of tokens which worth is below 3eth.

code snippet

```
    function checkWhitelist(address from, address to, uint256 amount) external onlyVultisig {
        if (from == _pool && to != owner()) {
            // We only add limitations for buy actions via uniswap v3 pool
            // Still need to ignore WL check if it's owner related actions
            if (_locked) {
                revert Locked();
            }

            if (_isBlacklisted[to]) {
                revert Blacklisted();
            }

            // HERE _whitelistIndex[to] = 0 for any random address. _allowedWhitelistIndex is > 0, thus _whitelistIndex[to] > _allowedWhitelistIndex === false (not reverting)
            if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {
                revert NotWhitelisted();
            }

            // // Calculate rough ETH amount for VULT amount
            uint256 estimatedETHAmount = IOracle(_oracle).peek(amount);
            if (_contributed[to] + estimatedETHAmount > _maxAddressCap) {
                revert MaxAddressCapOverflow();
            }

            _contributed[to] += estimatedETHAmount;
        }
    }
```

## Tools Used
manual review

## Recommended Mitigation Steps
change the logic of whitelisting into only allowing users where this condition holds: 1 < _whitelistIndex[msg.sender] <= _allowedWhitelistIndex. This way only whitelisted users with index above 1 can participate


## Assessed type

Invalid Validation