xanoutas/solidity-security-findings huggingface dataset:
User who is not whitelisted can still buy VULT token from Uniswap pool
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
If a user is not whitelisted (or blacklisted), they can buy VULT token from Uniswap pool regardless of whitelisted slots during whitelist launch period.

## Proof of Concept
When user buys VULT token from pool, if ___whitelistContract__ is not `address(0)`,  [checkWhitelist()](https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/Whitelist.sol#L204) of the ___whitelistContract__ is called to apply WL logic. One of the requirements is that user whitelist index should be within allowed index range:
```
            if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {
                revert NotWhitelisted();
            }
```
The problem is that ___whitelistIndex[to]__ is default to `0` if a user is not whitelisted, and the check will always pass if `_allowedWhitelistIndex` is larger than 0, this means the user who is not whitelisted can buy VULT token, renders the whitelist mechanism and ___allowedWhitelistIndex__ useless.


## Tools Used
Manual Review

## Recommended Mitigation Steps
Whitelist check should be as below:
```
            if (_whitelistIndex[to] == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {
                revert NotWhitelisted();
            }
```





## Assessed type

Access Control
Launch of the ILOPool can be reverted due to strict require statement
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

## Vulnerability details
When a project is initiated using `ILOManager::initProject()`, it creates a uniswap pool and initialize it with initial sqrtPriceX96(if pool is already not created or not initalize).
```solidity
    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");
            }
        }
    }
```
Now at the time of launch, initial sqrtPriceX96 is compared with sqrtPriceX96 from slot0
```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");
        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);
    }
```

This is problem because at the time of launch, sqrtPriceX96 form slot0 can change due to increase/decrease in `liquidity` or due to `swaps`.


## Impact
As result, launch will revert due to strict check 

## Proof of Concept
https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOManager.sol#L187C1-L199C1
https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOManager.sol#L109
https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOManager.sol#L57C1-L65C6

## Tools Used
Manual Review

## Recommended Mitigation Steps
Remove the require statement or adjust it to handle that situation





## Assessed type

DoS
Project will be unable to launch if any one of the ILOPool launch fails/reverts
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Vulnerability details
A project can launch/create multiple ILOPools to raise funds and all the ILOPools are stored in _initializedILOPools mapping
```solidity
    function initILOPool(InitPoolParams calldata params) external override onlyProjectAdmin(params.uniV3Pool) returns (address iloPoolAddress) {
        .....

        IILOPool.InitPoolParams memory initParams = IILOPool.InitPoolParams({
            uniV3Pool: params.uniV3Pool,
            tickLower: params.tickLower,
            tickUpper: params.tickUpper,
            sqrtRatioLowerX96: sqrtRatioLowerX96,
            sqrtRatioUpperX96: sqrtRatioUpperX96,
            hardCap: params.hardCap,
            softCap: params.softCap,
            maxCapPerUser: params.maxCapPerUser,
            start: params.start,
            end: params.end,
            vestingConfigs: params.vestingConfigs
        });
     @>   IILOPool(iloPoolAddress).initialize(initParams);
     @>  _initializedILOPools[params.uniV3Pool].push(iloPoolAddress);
    }
```
Once the launchTime is passed, project can ILOManager::launch() which loops over all the ILOPools to launch it
```solidity
 address[] memory initializedPools = _initializedILOPools[uniV3PoolAddress];
        require(initializedPools.length > 0, "NP");
        for (uint256 i = 0; i < initializedPools.length; i++) {
       @>     IILOPool(initializedPools[i]).launch();
        }
```
Now this is problematic because, if any one of the ILOPool launch fails it will revert the complete trx ie projects will not be able to launch due to failure of one ILOPool

There can be multiple reasons for a ILOPool launch fail:
1. totalRaised is not greater than softCap
2. refund is triggered
```solidity
        require(!_refundTriggered, "IRF");
        // make sure that soft cap requirement match
        require(totalRaised >= saleInfo.softCap, "SC");
```


## Impact
Project launch will be completely blocked if any one ILOPool fails

## Proof of Concept
https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOManager.sol#L193C8-L195C10
https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOPool.sol#L269C4-L329C10

## Tools Used
Manual Review

## Recommended Mitigation Steps
Instead of looping over all ILOPools, allow projects to launch single pool at a time





## Assessed type

DoS
`checkWhitelist()` function does not revert when the checked address is not whitelisted
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
In the `Whitelist::checkWhitelist()` function the `to` address is supposed to be checked for being whitelisted with this check:
```javascript
if (
                _allowedWhitelistIndex == 0 ||
                _whitelistIndex[to] > _allowedWhitelistIndex
            ) {
                revert NotWhitelisted();
            }
```
However, this statement only checks whether this whitelist index is smaller than the maximum allowed whitelist index, and whether the max index is not 0.

This is a critical issue, because this means that the `checkWhitelist()` call only checks if the whitelist is locked, the user is blacklisted, or whether they contributed the max amount, completely failing to check whether the address is whitelisted.


## Proof of Concept
To run the PoC paste the following code to the `describe("Checkwhitelist", function () {` branch of the `hardhat-vultisig/test/unit/Whitelist.ts` test file:
```
it.only("Doesn't revert when address is not whitelisted", async function () {
			const { whitelist, pool, otherAccount, batchedAccount, mockContract } =
				await loadFixture(deployWhitelistFixture);

			await whitelist.setVultisig(mockContract);

			await whitelist.setLocked(false);

			// Whitelist only one address - we'll see that call to checkWhitelist for batchedAccount DOESN'T revert with NotWhitelisted
			await whitelist.addWhitelistedAddress(otherAccount);
			await whitelist.setAllowedWhitelistIndex(1);
			const anotherAccount = await ethers.getSigners();

			await expect(
				whitelist.connect(mockContract).checkWhitelist(pool, batchedAccount, 0)
			).not.to.be.revertedWithCustomError(whitelist, "NotWhitelisted");
		});
```
and run `npx hardhat test` in the `hardhat-vultisig` directory. 

This test showcases that even though `batchedAccount` was not whitelisted, `checkWhitelist()` does not revert with `NotWhitelisted` custom error.

## Tools Used
Manual review, Hardhat

## Recommended Mitigation Steps
To mitigate this issue make the following change to the `checkWhitelist()` function:
```diff
if (
-               _allowedWhitelistIndex == 0 ||
+               _whitelistIndex[to] == 0 || 
                _whitelistIndex[to] > _allowedWhitelistIndex
            ) {
                revert NotWhitelisted();
            }
```
checking whether `_allowedWhitelistIndex == 0` is unnecessary, because if it wasn't set before this call, the `_whitelistIndex[to] > _allowedWhitelistIndex` will always fail.

The following note is only a recommendation, which is NOT required to mitigate this issue. 

Also, this concern in the `checkWhitelist()` function could be easily avoided by checking whether the `++_whitelistCount` value is greater than `_allowedWhitelistIndex` directly in `_addWhitelistedAddress`. The `addWhitelistedAddress()` and `addBatchWhitelist()` are
Missing null value check allows not whilisted users to buy VULT tokens from the pool
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

https://github.com/KYRDTeam/ilo-contracts/blob/9a40e550aff13542268054caf17f77e86870e416/hardhat-vultisig/contracts/Whitelist.sol#L216-L218


# Vulnerability details

## Impact
The null index in the array `Whitelist::_whitelistIndex` is kept for users that have not yet been added to the whitelist. When a user acquires tokens from the Uniswap pool, the function `Whitelist::checkwhitelsit` is called by the hook `_beforeTokenTransfer` and it checks whether the user is eligible to buy the tokens or not. However, this function is missing the check for the null index and therefore allows not whilisted users to claim tokens, breaking the core functionality.

```javascript
    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 
@>          ) {
                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;
        }
    }

```
## Proof of Concept
Add this to the "Checkwhitelist" describe in Whitelist.ts:

```javascript
    it("Does not revert when user is not whitelisted", async function () {

      const { whitelist, mockOracleSuccess, pool, otherAccount, batchedAccount, mockContract } = await loadFixture(deployWhitelistFixture);

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

      await whitelist.addBatchWhitelist([batchedAccount]);
      await whitelist.setAllowedWhitelistIndex(1);

      await expect(
        whitelist.connect(mockContract).checkWhitelist(pool, otherAccount, 0)
      ).to.not.be.reverted;

    });
```

## Tools Used
Manual review
## Recommended Mitigation Steps
Add missing check:

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


## Assessed type

Access Control
Non whitelisted user can bypass `checkWhitelist()`
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
https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/Whitelist.sol#L204-L228


# Vulnerability details

## Impact
A non whitelisted user can bypass the `checkWhitelist()` allowing anyone to buy tokens from the pool.

This issue is derived from a missing check in `checkWhitelist` where we dont check that `_whitelistIndex[to] != 0` when checking if the `to` param is whitelisted.

The root of the issue is in the following if statement

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


We can safely bypass `_allowedWhitelistIndex == 0` assuming it is set because it will not be zero if the vultisig is using a whitelist. We also bypass the second check of `_whitelistIndex[to] > _allowedWhitelistIndex` because solidity default's all unused mappings to 0, so in this case `_whitelistIndex[to]` will be equal to 0 which will bypass the check.

## Proof of Concept
```solidity
contract Base is Test {
    VultisigWhitelisted vultisig;
    Whitelist whitelist;

    // Mock addresses
    address public owner = address(100);
    address public pool = address(200);
    address public oracle = address(300);
    address public nonWhitelistedUser = address(400);

    function setUp() external {
      vm.startPrank(owner);
        whitelist = new Whitelist();
        vultisig = new VultisigWhitelisted();
        vultisig.setWhitelistContract(address(whitelist));

        // Configure the whitelist
        whitelist.setLocked(false);
        whitelist.setPool(pool);
        whitelist.setAllowedWhitelistIndex(1000);
        whitelist.setOracle(oracle);
        whitelist.setMaxAddressCap(1000);
        whitelist.setVultisig(address(vultisig));

        // Transfer from the owner to our mock pool
        vultisig.transfer(pool, 1000);
      vm.stopPrank();
  
    }
}


contract PoC is Base {
    function testNonWhitelistedUserCanBuyTokens() external {
        // Mock oracle returning 100 
        vm.mockCall(oracle, abi.encodeWithSignature('peek(uint256)', 1000), abi.encode(100));

        // In a real world scenario this would be the user buying tokens as the pool is transfering tokens to the user
        // Setting it up like this for simplicity
        vm.prank(pool);
        vultisig.transfer(nonWhitelistedUser, 1000);

        assertEq(vultisig.balanceOf(nonWhitelistedUser), 1000);
    }
}
```

## Tools Used

Manual Review

## Recommended Mitigation Steps
The simplest mitigation without refactoring the whitelisting system would be to add an extra check to the below code

```solidity
            if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {
                revert NotWhitelisted();
            }
``
`Whitelist.sol#checkWhitelist` function may return true for not whitelisted account when `_allowedWhitelistIndex` is bigger than zero.
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
`$VULT` tokens can be transferred to not whitelisted account when `allowedWhitelistIndex` set.
This causes serious unexpected problems to the protocol.

## Proof of Concept
`Whitelist.sol#checkWhitelist` function is the following.
```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;
        }
    }
```
if `_whitelistIndex[to] == 0`, `_whitelistIndex[to] > _allowedWhitelistIndex` holds false when `_allowedWhitelistIndex > 0` in `L216`.
This means that tokens can be transferred to not whitelisted acccount when `_allowedWhitelistIndex > 0`.

## Tools Used
Manual

## Recommended Mitigation Steps
Modify `Whitelist.sol#checkWhitelist` function as follows.
```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) {
++          if (_whitelistIndex[to] == 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;
        }
    }
```


## Assessed type

Error
Newly created project with new uni-swap pool could fail on launch leading locking of user funds
Audit: 2024-06-vultisig-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
New project which also creates a new uniswap pool could fail to launch due to the assumption that the price in uniswap's pool slot0 will remain same from the creation time of project to the launch time. 

As uniswap pool is a public pool, price could change due to interactions leading to failure to launch of the project due to the below check at the time of launch.

As the launch has potential to fail, the claim is also not possible leading to locking of user funds.

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

```
The above check assumes that the configured price at the time of launch and price returned by the uniswap pool will remain the same, which is an incorrect assumption.

## Proof of Concept
Anyone can initialize a project by calling initProject() function. As part of the initialization, the logic checks for existence of the pool in Uniswap factory and if not, creates a new pool in uniswap
and sets the initial sqrt price for the pool. One of the key parameter passed during the project creation is the launchTime which defines when the launch for the project can happen and when can the refund for the
pools under the project can be claimed.

All the key parameters are stored in the contract in _cachedProject mapping.

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

```
The owner can then start adding pools using initILOPool() function.

At the time of launch, one of the key conditions being checked is if the configured price is equal to 
Uniswap pool's slot 0 price. Incase the price does not match, the launch function will essentially revert.

Uniswap pool is a public accessible pool and any one should be able to interact with the Uniswap pool and  move the price for the pair directly impacting the slot0 price.

This will lead to failure of launch of the project.

```
 function launch(address uniV3PoolAddress) external override {
        require(block.timestamp > _cachedProject[uniV3PoolAddress].launchTime, "LT");
        (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3Pool(uniV3PoolAddress).slot0();
 ===>   require(_cachedProject[uniV3PoolAddress].initialPoolPriceX96 == sqrtPriceX96, "UV3P");// audit
        ... 
    }
```
There is a time window between when the project was created and when it will be launched