xanoutas/solidity-security-findings huggingface dataset:
Whitelist bypass
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

## Vulnerability Details
`checkWhitelist()` logic is incorrect.

```
            if (_allowedWhitelistIndex == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {
                revert NotWhitelisted();
            }
```
[Link](https://github.com/code-423n4/2024-06-vultisig/blob/main/hardhat-vultisig/contracts/Whitelist.sol#L216)

This function should also check that `_whitelistIndex[to]` was previously set, i.e. not zero.

## Impact
Any address not on the whitelist will pass the whitelist check as long as `_allowedWhitelistIndex` is more than 0. This violates a main invariant of `Whitelist.sol`.

## Proof of Concept
The following test should pass, but fails.
```
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { expect } from "chai";
import { ethers } from "hardhat";

describe("Whitelist", function () {
  async function deployWhitelistFixture() {
    const [owner, otherAccount, batchedAccount, mockContract, pool] = await ethers.getSigners();

    const Whitelist = await ethers.getContractFactory("Whitelist");
    const whitelist = await Whitelist.deploy();
    await whitelist.setPool(pool);

    const MockOracleSuccess = await ethers.getContractFactory("MockOracleSuccess");
    const MockOracleFail = await ethers.getContractFactory("MockOracleFail");

    const mockOracleSuccess = await MockOracleSuccess.deploy();
    const mockOracleFail = await MockOracleFail.deploy();

    return {
      whitelist,
      mockOracleSuccess,
      mockOracleFail,
      owner,
      otherAccount,
      batchedAccount,
      mockContract,
      pool,
    };
  }

  describe("WhitelistPoC", function () {
    it("Should revert when whitelist index is non-zero, but account is not on the whitelist", async function () {
      const { whitelist, pool, otherAccount, mockContract } = await loadFixture(deployWhitelistFixture);

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

      // Enable whitelist.
      await whitelist.setAllowedWhitelistIndex(1);

      // otherAccount is not on the whitelist.
      await expect(whitelist.connect(mockContract).checkWhitelist(pool, otherAccount, 0)).to.be.revertedWithCustomError(
        whitelist,
        "NotWhitelisted",
      );
    });
  });
});
```


## Recommended Mitigation Steps

The check in `checkWhitelist()` should instead be the following:
```
            if (_whitelistIndex[to] == 0 || _whitelistIndex[to] > _allowedWhitelistIndex) {
                revert NotWhitelisted();
            }
```


## Assessed type

Access Control
Burning functionality is missing in Vultisig contract
Audit: 2024-06-vultisig-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Vultisig token is not burnable as expected.

## Proof of Concept
In [Official project documentation](https://docs.vultisig.com/vultisig-token/usdvltx), it says that Vultisig token should be burnable:
> 100,000,000 starting supply
> 80% in launch liquidity
> 20% allocated to an airdrop
> ERC20 with ERC777 extensions: approveAndCall() and _beforeTransferHook()
> Ownable, but not mintable (to set launch params)
> __Burnable__

However in Vultisig contract, no such function is provisioned, this means Vultisig token cannot be burned even if it is expected. Since no statement from sponsor team says that the official doc is outdated / invalid, I believe this should be a valid medium.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Add Burning functionality in Vultisig contract.














## Assessed type

ERC20
Inconsistency in Token Burn Functionality of `Vultisig` Contract
Audit: 2024-06-vultisig-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

### Summary

The `Vultisig` contract is supposed to represent a burnable ERC20 token as per the `project documentation`. However, the current implementation does not include any public methods to call the internal `_burn` function, leading to a discrepancy between the documentation and the actual smart contract code.

https://docs.vultisig.com/vultisig-token/usdvltx#the-vultisig-token-usdvult

```
The Vultisig Token $VULT
- 100,000,000 starting supply
- 80% in launch liquidity
- 20% allocated to an airdrop
- ERC20 with ERC777 extensions: approveAndCall() and _beforeTransferHook()
- Ownable, but not mintable (to set launch params)
- Burnable
```

```solidity
contract Vultisig is ERC20, Ownable {
    constructor() ERC20("Vultisig Token", "VULT") {
        _mint(_msgSender(), 100_000_000 * 1e18);
    }

    function approveAndCall(address spender, uint256 amount, bytes calldata extraData) external returns (bool) {
        // Approve the spender to spend the tokens
        _approve(msg.sender, spender, amount);

        // Call the receiveApproval function on the spender contract
        IApproveAndCallReceiver(spender).receiveApproval(msg.sender, amount, address(this), extraData);

        return true;
    }
}
```

### Impact

1. **Functionality Mismatch**: Users and developers relying on the project documentation may expect to have the ability to `burn` tokens. The absence of this functionality in the contract can cause confusion and mistrust.
2. **Reduced Token Utility**: Burning tokens is a critical feature for managing token supply and implementing various tokenomics strategies. The lack of this feature can limit the token's utility and flexibility.

### Severity

**Medium**: While this issue does not pose a direct security threat, it significantly affects the usability and transparency of the token as described in the project documentation.

### Tools Used

- Manual Review


### Recommended Mitigation Steps

To resolve this issue, there are two potential approaches:

1. **Update the Documentation**: If the intention is not to support token burning, the project documentation should be revised to remove any references to this functionality.

2. **Implement Token Burning Functionality**: If token burning is a desired feature, the contract should inherit from `ERC20Burnable` and implement the necessary methods to support burning tokens. 

Revised implementation using `ERC20Burnable`:

```solidity
abstract contract ERC20Burnable is Context, ERC20 {
    using SafeMath for uint256;

    function burn(uint256 amount) public virtual {
        _burn(_msgSender(), amount);
    }

    function burnFrom(address account, uint256 amount) public virtual {
        uint256 decreasedAllowance = allowance(account, _msgSender()).sub(amount, "ERC20: burn amount exceeds allowance");
VULT lacks burn function
Audit: 2024-06-vultisig-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
According to [the documentation](https://docs.vultisig.com/vultisig-token/usdvltx#the-vultisig-token-usdvult), VULT is supposed to have a burn function and should be burned when bought. However, this function was not implemented in the contract, meaning VULT cannot be burned.

## Proof of Concept
In the [Vultisig contract](https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/Vultisig.sol#L11), there is no burn function implementation. Tokens cannot be destroyed. In addition, [although the VultisigWhitelisted contract has a buy judgment](https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/Whitelist.sol#L205-L227), the buy-burn model is not implemented in the judgment logic.
```solidity
        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;
        }
```

## Tools Used
manual

## Recommended Mitigation Steps
It is recommended to add a burn function and add buy-burn logic.


## Assessed type

Context
Vultisig should be burnable
Audit: 2024-06-vultisig-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/hardhat-vultisig/contracts/Vultisig.sol#L1-L25


# Vulnerability details

The Vultisig token, as described in its documentation, is expected to include a burnable feature. However, the current implementation of the Vultisig token contract lacks the necessary functions to support token burning. This report identifies the impact of this missing functionality and provides a recommended solution to implement the burn feature. The vultisig stated that they forgot to add this functionality.
## Impact

**Non-compliance with Documentation:** Users and developers relying on the documentation will expect burn functionality, leading to confusion and potential loss of trust when they find it missing.
## Proof of concept 

As you can see from the code below function for burning is missing, consider adding it to the code. 

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IApproveAndCallReceiver} from "./interfaces/IApproveAndCallReceiver.sol";

/**
 * @title ERC20 based Vultisig token contract
 */
contract Vultisig is ERC20, Ownable {
    constructor() ERC20("Vultisig Token", "VULT") {
        _mint(_msgSender(), 100_000_000 * 1e18);
    }

    function approveAndCall(
        address spender,
        uint256 amount,
        bytes calldata extraData
    ) external returns (bool) {
        // Approve the spender to spend the tokens
        _approve(msg.sender, spender, amount);

        // Call the receiveApproval function on the spender contract
        IApproveAndCallReceiver(spender).receiveApproval(
            msg.sender,
            amount,
            address(this),
            extraData
        );

        return true;
    }
}

```

## Recommended Mitigation Steps
To address this issue, the following burn functions should be added to the Vultisig contract:

1. **Burn Function:**
    
    - Allows token holders to destroy a specified amount of their own tokens.
2. **Burn From Function:**
    
    - Allows an account to burn tokens from another account, given that the caller has sufficient allowance.

Here is the modified contract with the added burn functionality:
Something like this can be added to the `Vultisig` code:

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

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IApproveAndCallReceiver} from "./interfaces/IApproveAndCallReceiver.sol";

/**
 * @title ERC20 based Vultisig token contract
 */
contract Vultisig is ERC20, Ownable {
    constructor() ERC20("Vultisig Token", "VULT") {
        _mint(_msgSender(), 100_000_000 * 1e18);
    }

    function approveAndCall(
        address spender,
        uint256 amount,
M-1 There is no external function `burn()` in the VULT contract
Audit: 2024-06-vultisig-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The documentation says that the token VULT operates on a buy-burn model. 

[Link to the documentation 1](https://docs.vultisig.com/vultisig-token/usdvltx#value-accrual)

[Link to the documentation 2](https://docs.vultisig.com/other/faq#what-is-the-key-utility-of-usdvult-token-to-the-ordinary-users)

But `Vultisig.sol` and `VultisigWhitelisted.sol` contracts have no external `burn()` function. Its only have an internal `_burn` function that inherits from the `ERC20` Openzeppelin contract. Therefore, the functionality specified in the documentation cannot be realized. Since contracts are not upgradeable, the VULT Token contract will have to be re-deployed to add new functionality.

## Proof of Concept
None

## Tools Used
Manual

## Recommended Mitigation Steps
Add external function `burn(uint256 amount)` with modifier `onlyOwner` to the contract `Vultisig.sol`.


## Assessed type

Other
Internal _burn() function is not exposed externally in the Vultisig.sol contract
Audit: 2024-06-vultisig-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Summary

**Root cause:**
According to the [documentation](https://docs.vultisig.com/vultisig-token/usdvltx), the Vultisig token is expected to be Burnable. This is not true currently since the  internal ERC20 _burn() function is not exposed externally through another wrapped external/public function. 

**Impact:**
According to the [documentation](https://docs.vultisig.com/vultisig-token/usdvltx#value-accrual), the token is expected to have a "Value Accrual" model in the Vultisig system, which as stated is termed as the buy-burn model. 

From the docs:
```
All affiliate, router, and bridge fees accumulated from platform usage will be used to buy and burn the asset.

Through the buy and burn-burn model, the token automatically increases in value the more the app is used to swap and bridge.
```

As we can see, due to the missing burn functionality, the Vultisig token would fail to increase in value due to the supply not decreasing. This hampers the economic model/value of the token and fails to meet its expected value accrual design in the ecosystem.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Expose the internal _burn() function using a wrapper external/public function. Ensure that the msg.sender can burn his tokens only.


## Assessed type

Error
`Whitelist._maxAddressCap` can be bypassed when claiming `$VULT` tokens
Audit: 2024-06-vultisig-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

- When an `ILOPool` is initialized with the `$VULT` token being the sale token, users can buy shares of the liquidity before the project launch (via `ILOPool.buy()`) then claim their `VULT` token via `ILOPool.claim()` where the claimed sale token will be burnt from the pool liquidity and sent to the user.

```js
function claim(uint256 tokenId)
        external
        payable
        override
        isAuthorizedForToken(tokenId)
        returns (uint256 amount0, uint256 amount1)
    {

        //....
                // transfer token to the user
        TransferHelper.safeTransfer(_cachedPoolKey.token0, ownerOf(tokenId), amount0);
        TransferHelper.safeTransfer(_cachedPoolKey.token1, ownerOf(tokenId), amount1);

        //....
    }
```

The `VaultSigWhitelisted` token contract has a `_beforeTokenTransfer()` hook that checks the following before transferring tokens to the recipient:

- if the recipient account (`to`) is whitelisted.
- if the recipient contribution is less than the `_maxAddressCap`, where this is set to `3 ether` for each account:

```js
    /// @notice Before token transfer hook
    /// @dev It will call `checkWhitelist` function and if it's succsessful, it will transfer tokens, unless revert
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal override {
        if (_whitelistContract != address(0)) {
            IWhitelist(_whitelistContract).checkWhitelist(from, to, amount);
        }
        super._beforeTokenTransfer(from, to, amount);
    }
```

where:

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

but this `_contributed[to] + estimatedETHAmou
sqrtPriceX96 can change over time affecting project initialization
Audit: 2024-06-vultisig-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The ILOManager contract contains a bug inside `launch` function related to the handling of the initialPoolPriceX96 value during pools initialization. Specifically, the value of sqrtPriceX96 in Uniswap V3 pools can change over time, **which may prevent the successful initialization of a project**. This issue arises because the initialPoolPriceX96 value is compared with the current sqrtPriceX96 during validation, but sqrtPriceX96 is not static and fluctuates with market conditions.

## Proof of Concept
Validation https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOManager.sol#L190:
```solidity
require(_cachedProject[uniV3PoolAddress].initialPoolPriceX96 == sqrtPriceX96, "UV3P");
```

Initialization https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOManager.sol#L124-L147
```solidity
  function _cacheProject(
        address uniV3PoolAddress,
        address saleToken,
        address raiseToken,
        uint24 fee,
        uint160 initialPoolPriceX96,
        uint64 launchTime,
        uint64 refundDeadline
    ) internal {
        Project storage _project = _cachedProject[uniV3PoolAddress];
        require(_project.uniV3PoolAddress == address(0), "RE");

        _project.platformFee = PLATFORM_FEE;
        _project.performanceFee = PERFORMANCE_FEE;
        _project.admin = msg.sender;
        _project.saleToken = saleToken;
        _project.raiseToken = raiseToken;
        _project.fee = fee;
        _project.initialPoolPriceX96 = initialPoolPriceX96;
        _project.launchTime = launchTime;
        _project.refundDeadline = refundDeadline;
        _project.uniV3PoolAddress = uniV3PoolAddress;
        _project._cachedPoolKey = PoolAddress.getPoolKey(saleToken, raiseToken, fee);
    }
```

## Tools Used
Manual review.

## Recommended Mitigation Steps
Remove the condition that compares initialPoolPriceX96 with the current sqrtPriceX96, or add the ability to update initialPoolPriceX96 at any time for the project owner.





## Assessed type

DoS
Project launch is vulnerable to DoS attack
Audit: 2024-06-vultisig-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The project launch process can be subjected to a Denial of Service (DoS) attack by manipulating the Uniswap pool price. Malicious actors can exploit the requirement that the cached price must equal the current token price, leading to the failure of the project launch transaction.

## Proof of Concept
The `ILOManager::launch()` function requires the cached price (`_cachedProject[uniV3PoolAddress].initialPoolPriceX96`) to match the current token price, [ILOManager::launch()#L189-L190](https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOManager.sol#L189-L190):

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

An attacker can manipulate the token price by borrowing tokens and executing transactions to change the price. This can be done by sandwich attack.

The manipulated price causes the cached price check condition to fail, resulting in the transaction reverting.

After causing the transaction to fail, the attacker can back-running the transaction to return the borrowed tokens and restore the original price.

## Tools Used
vscode

## Recommended Mitigation Steps
Consider removing the L190.


## Assessed type

DoS
Investor can mint multiple nfts and bypass maxCapPerUser because of wrong validation
Audit: 2024-06-vultisig-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Vulnerability details
A whitelisted user can buy position in ILO using ILOPool::buy(), which mints a new nft and creates a vesting schedule for investor. To mint nft, it checks investor already have any position or not by checking balanceOf(recipient) 
```solidity
      // if investor already have a position, just increase raise amount and liquidity
        // otherwise, mint new nft for investor and assign vesting schedules
      @>  if (balanceOf(recipient) == 0) {
            _mint(recipient, (tokenId = _nextId++));
            _positionVests[tokenId].schedule = _vestingConfigs[0].schedule;
        } else {
            tokenId = tokenOfOwnerByIndex(recipient, 0);
        }

        Position storage _position = _positions[tokenId];
      @>  require(raiseAmount <= saleInfo.maxCapPerUser - _position.raiseAmount, "UC");
        _position.raiseAmount += raiseAmount

```
Now this is problem because, a malicious user can transfer his nft to a different address, which will set his balanceOf(recipient) = 0. And again malicious user can buy position which will mints new nft & creates a new vesting schedule

Also by doing this, investor can bypass the maxCapPerUser check(see above code)


## Impact
Multiple nft will be minted for same investor & maxCapPerUser will be bypassed 

## Proof of Concept
https://github.com/code-423n4/2024-06-vultisig/blob/cb72b1e9053c02a58d874ff376359a83dc3f0742/src/ILOPool.sol#L143C5-L148C10

## Tools Used
Manual Review

## Recommended Mitigation Steps
Instead of using balanceOf(), use internal accounting like mapping which will store the whitelisted investor and his tokenId





## Assessed type

Invalid Validation