xanoutas/solidity-security-findings huggingface dataset:
Dangerous use of `slot0` allows pool admin to use a `initialPoolPrice` that manipulates the pool price calculations
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOManager.sol#L109-L121
https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOManager.sol#L88-L90
https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L72-L76
https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L155-L159
https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L242-L248


# Vulnerability details

## Impact
Users can create projects by calling `ILOManager::initProject` and specifying a `Uniswap V3 pool` for their project.

If the `Uniswap V3 pool` doesn't exist, a new pool is created with a ratio `sqrtPriceX96` specified by the caller. If it does exist, `slot0` is used to fetch the `sqrtPriceX96`.

This is dangerous as `slot0` returns the pool’s spot price, which can be manipulated by buying and selling assets within the pool using flash loans, since this price is given by the current ratio of reserves in the pool.

An attacker can make the spot price favorable for them to acheive their desired outcome. 

The `initial pool price` is the ratio of the pool at the time the project was initialized, which is used for price calculations, such as the amount of liquidity investors earn from the `raise tokens` sent and the amount of liquidity minted to the project.

A malicious project admin can manipulate these price calculations by manipulating the `slot0` value.

## Proof of Concept
When projects are initialized, the pool values are cached, including the `initialPoolPriceX96`, which represents the current ratio of the `Uniswap V3 pool`.

[ILOManager.sol#L109-L121](https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOManager.sol#L109-L121)
```javascript
    function _initUniV3PoolIfNecessary(PoolAddress.PoolKey memory poolKey, uint160 sqrtPriceX96) internal returns (address pool) {
        pool = IUniswapV3Factory(UNIV3_FACTORY).getPool(poolKey.token0, poolKey.token1, poolKey.fee);
        if (pool == address(0)) {
            pool = IUniswapV3Factory(UNIV3_FACTORY).createPool(poolKey.token0, poolKey.token1, poolKey.fee);
            IUniswapV3Pool(pool).initialize(sqrtPriceX96);
        } else {
@>          (uint160 sqrtPriceX96Existing, , , , , , ) = IUniswapV3Pool(pool).slot0();
            if (sqrtPriceX96Existing == 0) {
                IUniswapV3Pool(pool).initialize(sqrtPriceX96);
            } else {
                require(sqrtPriceX96Existing == sqrtPriceX96, "UV3P");
            }
        }
    }
```

As mentioned above, this value is manipulatable, since this price is given by the current ratio of reserves in the pool.

This price is used to verify the `tick range` when `ILOPools` of projects are initialized.

[ILOManager.sol#L88-L90](https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOManager.sol#L88-L90)
```javascript
    uint160 sqrtRatioLowerX96 = TickMath.getSqrtRatioAtTick(params.tickLower);
    uint160 sqrtRatioUpperX96 = TickMath.getSqrtRatioAtTick(par
Incorrect whitelist validation in the `checkWhitelist` function allows non-whitelisted users to bypass whitelisting.
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

## Impact
During the whitelist period, the `_beforeTokenTransfer` function calls the `checkWhitelist` function of the whitelist contract to verify if the receiver is allowed to receive tokens. However, due to a missing check, whitelisting can be bypassed unless the user is blocklisted.

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

If the value of `_allowedWhitelistIndex` is set, any non-whitelisted user can bypass the whitelisting because the default value of `_whitelistIndex` for a non-whitelisted user is `0`, allowing the check to be passed.

> As it will allow any non-whitelisted user to receive tokens, whitelisting can become ineffective.


## Proof of Concept
```solidity
  describe("Checkwhitelist Bug", function () {
    it("Non-Whitelisted will Pass", async function () {
      const { whitelist, pool, mockOracleSuccess, mockContract, otherAccount, batchedAccount, nonWhitelisted } = await loadFixture(deployWhitelistFixture);

      // Set Lock to false
      await whitelist.setLocked(false);
      // Set vultsig contract.
      await whitelist.setVultisig(mockContract);
      // Whitelist 2 accounts and only allow first one
      await whitelist.addBatchWhitelist([otherAccount, batchedAccount]);
      await whitelist.setAllowedWhitelistIndex(1);
      // Set oracle
      await whitelist.setOracle(mockOracleSuccess);
      // Checking `nonWhitelisted`
      await whitelist.connect(mockContract).checkWhitelist(pool, nonWhitelisted, 0);
      expect(await whitelist.contributed(nonWhitelisted)).to.eq(ethers.parseEther("1.5"));
    });
  });
```

## Tools Used
Manual Review, Hardhat

## Recommended Mitigation Steps
```diff
    function checkWhitelist(address from, address to, uint256 amount) external onlyVultisig {
       ...

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

       ...
    }
```




## Assessed type

Access Control
Launch can be prevented due to pool price manipulation.
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Vulnerability details

To launch a project there are some requirements that need to be fulfilled. One of those conditions is that the current price in the pool is the same as the initial price provided by the admin project.

```javascript
(uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(uniV3PoolAddress)
            .slot0();
        require(
            _cachedProject[uniV3PoolAddress].initialPoolPriceX96 ==
                sqrtPriceX96,
            "UV3P"
        );
```

This situation might not always be true. Protocols use different strategies to distribute their tokens. They often do it partialy. In a scenario where some amount of sale token is already distributed (airdrop for example) there is a risk that someone could add liquidity and the price will change overtime.

## Impact

Project will not be launched thus raised token and sold token will not be added as liquidity to the UniswapV3 pool. Even worse when refund deadline is past, the tokens will be stuck in ILOPools as the can't be refunded after that time and can't be claimed because they are not in Uniswap pool.

## Recommended Mitigation Steps

To prevent any tokens from being stuck do not allow projects which already have distributed any amount of their token as they can be used to lock remaining tokens in ILOPools.


## Assessed type

DoS
Non whitelisted addresses can bypass `Whitelist::checkWhitelist` and swap VULT tokens from pool
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


# Vulnerability details

## Impact
The `Whitelist::checkWhitelist` check intended to validate if an address is whitelisted can be bypassed by using addresses that were not initialized via `Whitelist::_addWhitelistedAddress` function. This allows non whitelisted users to swap VULT tokens from uniswap pool during IDO (after launch).

This can be very problematic as it can reduce VULT token liquidity for legitimate vestors. 



## Proof of Concept

To initialize a whitelist for an address :

```js
    function _addWhitelistedAddress(address whitelisted) private {
        if (_whitelistIndex[whitelisted] == 0) {
            _whitelistIndex[whitelisted] = ++_whitelistCount;
        }
    }
```

The mapping `_whitelistIndex` for an unlisted address defaults to 0. This allows non-whitelisted addresses to pass the whitelist check.

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

Non-whitelisted users can swap tokens from uniswap pool reducing the liquidity of the VULT token and increasing it's price. Legitimate vestors will get less VULT tokens as a result.

## Tools Used

Manual review

## Recommended Mitigation Steps

Check that an address in whitelisted before proceding :
```diff
    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 (_whitelistIndex[to] == 0) {
+               revert NotWhitelisted();
+           }
...
}
```


## Assessed type

Invalid Validation
Whitelist can be bypassed
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-L219


# Vulnerability details

In the function `Whitelist::checkWhitelist`, it is intended to assert whether an address is whitelisted for receiving tokens from the pool. If an address is not whitelisted, is blacklisted, or has already contributed more than the capped amount, this method should revert. However, it is missing a check to verify if the address is whitelisted, which should be indicated by a value greater than zero.

## Impact

The impact of this omission is that any user can receive tokens from the pool, regardless of whether they are whitelisted or not. This could lead to various issues, including unauthorized users receiving tokens and potential exploitation of the pool.
## Proof of concept

If we go through the function below, as you can see is missing the check is `_whitelistIndex[to] > 0`

```solidity
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();
            }

            if (
                _allowedWhitelistIndex == 0 ||
                _whitelistIndex[to] > _allowedWhitelistIndex
            ) {
                // audit-high missing zero check
                revert NotWhitelisted();
            }

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

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

## Tools Used
Manual review

## Recommended Mitigation Steps 

Add a check to verify that the `_whitelistIndex[to]` value is greater than zero to ensure that the address is whitelisted before proceeding with the rest of the function logic. The updated function should look like this:

```diff
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();
        }

        // Check if the address is whitelisted
+       if (_whitelistIndex[to] == 0) {
+           revert NotWhitelisted();
+       }

        if (
            _allowedWhitelistIndex =
Risk of DOS in `ILOManager::launch` if Some Project Pools Fail to Reach Soft Cap
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
https://github.com/code-423n4/2024-06-vultisig/blob/main/src/ILOPool.sol#L269-L274


# Vulnerability details

## Issue Description

A project pool, after being deployed, can enter two different states:

* **Launch**: If the pool sale's soft cap is reached and a refund is not triggered, the pool can be launched, and liquidity is deployed to UniV3.

* **Refund**: If a pool fails to reach the sale soft cap before the refund deadline, a refund can be triggered (by anyone). Buyers get back their Raise tokens by calling `ILOPool::claimRefund`, and the project admin gets refunded their Sale tokens by calling `ILOManager::claimRefund` (which invokes `ILOPool::claimProjectRefund` under the hood).

To understand the issue highlighted in this report, it is important to note that a single project can have multiple independent pools, which can be added by the project admin through `ILOManager::initILOPool`. The launch/refund state (explained above) is unique to each pool. For example, PoolA could get launched while PoolB fails to reach the soft cap and gets refunded.

The issue arises in projects with multiple pools (the intended use for projects), where some pools get launched, and some fail and must be refunded.

In this scenario, the project admin must invoke the `ILOManager::launch` function to launch the project pools:

```solidity
/// @inheritdoc IILOManager
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++) {
        //@audit could be DOSed if one pool doesn't reach softcap while others do
        IILOPool(initializedPools[i]).launch();
    }

    emit ProjectLaunch(uniV3PoolAddress);
}
```

The function iterates over all the initialized pools and attempts to launch each one. However, if some pools fail to reach the soft cap, calling `IILOPool::launch` on those pools will revert, causing the entire `ILOManager::launch` function to fail. This will prevent the successful pools from being launched.

The `IILOPool::launch` function, which checks the soft cap, will revert if the soft cap is not met as shown below:

```solidity
function launch() external override OnlyManager {
    require(!_launchSucceeded, "PL");
    // when refund triggered, we cannot launch pool anymore
    require(!_refundTriggered, "IRF");
    // make sure that soft cap requirement is met
    require(totalRaised >= saleInfo.softCap, "SC");
    
    //...
}
```

Th
Invalid check in WhiteList.checkWhitelist().
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


# Vulnerability details

## Impact

Unallowed users can receive Vultisig tokens. This could make the launching process not to work well, because vultisig token holders can change the exchange rates of the uniswap v3 pools which have vultisig tokens.

## Proof of Concept

L216 checks if `to` is allowed to receive vultisig tokens. However, it only works well when `_whitelistIndex[to] > 0`.
So, users with `_whitelistIndex` as `0` can pass this check.

https://github.com/code-423n4/2024-06-vultisig/blob/main/hardhat-vultisig/contracts/Whitelist.sol#L204-L228
```solidity
    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();
            }

216:        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

`_allowedWhitelistIndex || _whitelistIndex[to] == 0` should be added.

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


## Assessed type

Other
CheckWhitelist fails to detect non-whitelisted users . 
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


# Vulnerability details

## Impact
`CheckWhitelist` fails to detect non-whitelisted users as mentioned in the comment  . 
```
/// @dev Revert if locked, not whitelisted, blacklisted or already contributed more than capped amount
```
## Proof of Concept
`checkWhitelist` is supossed to revert in case of any non-whitelisted user . 
```solidity 
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();
            }

@>            if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {//@audit-issue non-whitelisted accounts are supposed to revert too ! But even if not whitelisted , `contributed[to]` will return 0 which is smaller then allowedIndex always!  
                revert NotWhitelisted();//that means non-whitelisteds can do too! //Needs to add _whitelistIndex[to]==0 check extra for mitigation 
            }
               //.....
        }

```

But if `_allowedWhitelistIndex` is not 0; for Any non-whitelisted user,  `contributed[to]` will return 0 which is always smaller then `_allowedWhitelistIndex`. That means non-whitelisted users can easily pass the check which shouldnot be the case !    
## Tools Used
Manual review ! 
## Recommended Mitigation Steps
 add _whitelistIndex[to]==0 check extra for mitigation
```diff 
- if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) 
+ if (_allowedWhitelistIndex == 0 ||_whitelistIndex[to] ==0 || _whitelistIndex[to] > _allowedWhitelistIndex) 
```


## Assessed type

Access Control
Everyone not configured could buy without whitelist
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#L212-L218


# Vulnerability details

## Impact
VULT token is designed for whitelist presale only. But this validation could allow EVERYONE not in the blacklist can buy the token in presale stage.

## Proof of Concept
```
it("Shoud not pass this", async function () {
      const { whitelist, pool, otherAccount, batchedAccount, mockContract, randomAccount, mockOracleSuccess } = await loadFixture(deployWhitelistFixture);

      await whitelist.setVultisig(mockContract);
      await whitelist.setLocked(false);

      // Whitelist 2 accounts and only allow first one
      await whitelist.addBatchWhitelist([otherAccount, batchedAccount]);
      await whitelist.setAllowedWhitelistIndex(1);
      await whitelist.setOracle(mockOracleSuccess);
      expect(await whitelist.oracle()).to.eq(mockOracleSuccess);

      // mallicious account pass the check
      await expect(
        whitelist.connect(mockContract).checkWhitelist(pool, randomAccount, 0),
      ).to.be.revertedWithCustomError(whitelist, "NotWhitelisted");
    });
```

## Tools Used
hardhat

## Recommended Mitigation Steps
Check if index is zero then the address is not whitelisted


## Assessed type

Invalid Validation
The important `launch()` function can be DOS'd by manipulating the project's uniswap pool
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
Inside `IloManager.launch()` there is the following require statement

```solidity
(uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(uniV3PoolAddress).slot0();
require(_cachedProject[uniV3PoolAddress].initialPoolPriceX96 == sqrtPriceX96, "UV3P");
```

Since `sqrtPriceX96` is taken from slot0 it is easily manipulated by making a swap, this will break the == statement causing a revert. See the POC section for a step by step.


## Proof of Concept
1. Honest user sends a Tx calling `IloManager.launch()`
2. Attacker sees (1) in the mempool and frontruns it by making a swap in the uniswap pool so that `_cachedProject[uniV3PoolAddress].initialPoolPriceX96 != sqrtPriceX96`
3. The honest user's Tx will revert
4. The attacker can repeat this indefinitely 

## Tools Used
Manual review

## Recommended Mitigation Steps
do not rely on slot0


## Assessed type

DoS