xanoutas/solidity-security-findings huggingface dataset:
`Owner` can remove `holder` from `HolderAllowList` while distribution is running
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L116-L119


# Vulnerability details

## Impact
`Owner` can remove `holder` from `HolderAllowList` while distribution is running.

## Bug
Only transfers, mints, and burns functions are locked while distribution is running. Which gives `Owner` opportunity to remove any `holder` from `HolderAllowList` by front running `distribution`'s transaction such that `holder` cannot get any tokens.

There is no Access Control in `disapproveHolder` function to prevent owner from changing the `HolderAllowList` while distribution is running.

## Tools Used
Foundry

## Recommended Mitigation Steps
Add these lines in `disapproveHolder` function of `LiquidInfrastructureERC20` contract.
```javascript
    function disapproveHolder(address holder) public onlyOwner {
        require(isApprovedHolder(holder), "holder not approved");
++        require(!LockedForDistribution, "cannot disapprove during distribution");
        HolderAllowlist[holder] = false;
    }
```


## Assessed type

Access Control
An approved holder with zero balance can be pushed to the `holders` array many times by anyone
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L142-L145


# Vulnerability details

## Impact
The issue can be exploited to receive additional amounts of the distributed assets and/or to disrupt the core functionality of the protocol.

## Proof of Concept
The `LiquidInfrastructureERC20` contract inherits Open Zeppelin's `ERC20` implementation. So it is possible to transfer zero values. There is additional restriction in the `LiquidInfrastructureERC20._beforeTokenTransfer` huk that the receiver should be approved to hold the token:
```solidity
            require(
                isApprovedHolder(to),
                "receiver not approved to hold the token"
            );
```
In case the receiver's `balanceOf` is zero the receiver's address is pushed to the `holders` array:
```solidity
        bool exists = (this.balanceOf(to) != 0);
        if (!exists) {
            holders.push(to);
        }
```
So if someone sends zero value to an approved address with zero balance the address is pushed to the `holders` array. Since the address balance is still zero the operation can be repeated again.

The `holders` array is used in the `distribute` function to distribute ERC20s tokens among the protocol's tokens holders:
```solidity
        for (i = nextDistributionRecipient; i < limit; i++) {
            address recipient = holders[i];
            if (isApprovedHolder(recipient)) {
                uint256[] memory receipts = new uint256[](
                    distributableERC20s.length
                );
                for (uint j = 0; j < distributableERC20s.length; j++) {
                    IERC20 toDistribute = IERC20(distributableERC20s[j]);
                    uint256 entitlement = erc20EntitlementPerUnit[j] *
                        this.balanceOf(recipient);
                    if (toDistribute.transfer(recipient, entitlement)) {
                        receipts[j] = entitlement;
                    }
                }

                emit Distribution(recipient, distributableERC20s, receipts);
            }
        }                
```
Several includings at the `holders` array lets the holder receive the additional amount of assets in case of sufficient assets balance.
When the assets balance of the contract becomes insufficient (because of transferring additional amounts) the `distribute` function throws an error and the core functionality of the protocol is blocked by the `LockedForDistribution` status. It can only be changed at the `distribute` function by calling the `_endDistribution`:
```solidity
        nextDistributionRecipient = i;

        if (nextDistributionRecipient == holders.length) {
            _endDistribution();
        }
```
Then only the contract owner can solve the accident by removing tokens from `distributableERC20s` array or holders from the `HolderAllowlist` mapping.

## Tool
Anyone can call withdrawFromAllManagedNFTs and withdrawFromManagedNFTs
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L351
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L359


# Vulnerability details

## Impact
Anyone is able to call this function, leading to unexpected behavior.

## Proof of Concept
Reference Links to Affected Code

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use onlyOwner modifier








## Assessed type

Access Control
pausable tokens
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L225


# Vulnerability details

## Impact
The `distribute` can fail and all user tokens can be locked in the contract . 
The function will revert if the token the specific token transfer is locked or 
paused when transferring is disabled for a period of time.

## Tools Used
manual

## Recommended Mitigation Steps
check to see if token is a pausable tokens before accenting it.


## Assessed type

DoS
Anyone can call withdrawFromManagedNFTs as there are no permission checks or access control
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L359


# Vulnerability details

## Impact

Missing access controls allows any address to call `withdrawFromManagedNFTs` hence, performing withdrawals from the ManagedNFTs collection which might not be the intentions of the owner of the ManagedNFTs collection

## Proof of Concept

```js
    /**
     * Performs withdrawals from the ManagedNFTs collection, depositing all token balances into the custody of this contract
     * @param numWithdrawals the number of withdrawals to perform
     */
    function withdrawFromManagedNFTs(uint256 numWithdrawals) public {
        require(!LockedForDistribution, "cannot withdraw during distribution");

        if (nextWithdrawal == 0) {
            emit WithdrawalStarted();
        }

        uint256 limit = Math.min(
            numWithdrawals + nextWithdrawal,
            ManagedNFTs.length
        );
        uint256 i;
        for (i = nextWithdrawal; i < limit; i++) {
            LiquidInfrastructureNFT withdrawFrom = LiquidInfrastructureNFT(
                ManagedNFTs[i]
            );

            (address[] memory withdrawERC20s, ) = withdrawFrom.getThresholds();
            withdrawFrom.withdrawBalancesTo(withdrawERC20s, address(this));
            emit Withdrawal(address(withdrawFrom));
        }
        nextWithdrawal = i;

        if (nextWithdrawal == ManagedNFTs.length) {
            nextWithdrawal = 0;
            emit WithdrawalFinished();
        }
    }
```

## Tools Used

- Manual Review

## Recommended Mitigation Steps

Include access control.

Example:

<details>

<summary>Code</summary>

```diff
-   function withdrawFromManagedNFTs(uint256 numWithdrawals) public {
+   function withdrawFromManagedNFTs(uint256 numWithdrawals) public LiquidInfrastructureNFT.onlyOwnerOrApproved(LiquidInfrastructureNFT.AccountId) {
        require(!LockedForDistribution, "cannot withdraw during distribution");

        if (nextWithdrawal == 0) {
            emit WithdrawalStarted();
        }

        uint256 limit = Math.min(
            numWithdrawals + nextWithdrawal,
            ManagedNFTs.length
        );
        uint256 i;
        for (i = nextWithdrawal; i < limit; i++) {
            LiquidInfrastructureNFT withdrawFrom = LiquidInfrastructureNFT(
                ManagedNFTs[i]
            );

            (address[] memory withdrawERC20s, ) = withdrawFrom.getThresholds();
            withdrawFrom.withdrawBalancesTo(withdrawERC20s, address(this));
            emit Withdrawal(address(withdrawFrom));
        }
        nextWithdrawal = i;

        if (nextWithdrawal == ManagedNFTs.length) {
            nextWithdrawal = 0;
            emit WithdrawalFinished();
        }
    }
```

</details>


## Assessed type

Access Control
Decimal Difference can lead to loss of funds
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L257


# Vulnerability details

## Description
The _beginDistribution() function in the LiquidInfrastructureERC20.sol calculates the entitlement by multiplying with 1e18 token decimals with the assumption that the distributableERC20s always has 18 decimals. 

## Impact

Incorrect entitlements if token has different decimals
Over/under distribution of tokens due to wrong decimals
Code maintenance headaches when integrating new tokens

## Proof of Concept
```solidity
function _beginDistribution() internal {
        require(!LockedForDistribution, "cannot begin distribution when already locked");
        LockedForDistribution = true;

        // clear the previous entitlements, if any
        if (erc20EntitlementPerUnit.length > 0) {
            delete erc20EntitlementPerUnit;
        }

        // Calculate the entitlement per token held
        uint256 supply = this.totalSupply();
        console.log("supply", supply);

        for (uint256 i = 0; i < distributableERC20s.length; i++) {
            uint256 balance = IERC20(distributableERC20s[i]).balanceOf(address(this));

// >>> The distributable ERC20s can be of any decimals and it will lead to an //incorrect value in the calculation below.

            uint256 entitlement = balance / supply;
            erc20EntitlementPerUnit.push(entitlement);
        }

        nextDistributionRecipient = 0;
        emit DistributionStarted();
    }

```

## Tools Used
Manual Review

## Recommended Mitigation Steps
Fetching the decimals() of each token dynamically
Using the fetched decimal in the calculation
Adding validation to ensure supported decimals


## Assessed type

Decimal
Natspec mentions that the function allows only approved sender to perform operation but it has no access control implementation
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L344


# Vulnerability details

## Impact

Natspec states that *Convenience function that allows an approved sender to distribute...*

Code implementation does not contain any access control allowing only approved sender to perform call

## Proof of Concept

**Proof of Code**

```js
    /**
     * Convenience function that allows an approved sender to distribute when necessary and then burn the approved tokens right after
     *
     * @notice attempts to distribute to every holder in this block, which may exceed the block gas limit
     * if this fails then first call distribute() enough times to finish a distribution and then call burnFrom()
     */
    function burnFromAndDistribute(address account, uint256 amount) public {
        if (_isPastMinDistributionPeriod()) {
            distributeToAllHolders();
        }
        burnFrom(account, amount);
    }
```

## Tools Used

- Manual Review

## Recommended Mitigation Steps

```diff
- function burnFromAndDistribute(address account, uint256 amount) public {
+ function burnFromAndDistribute(address account, uint256 amount) public LiquidInfrastructureNFT.onlyOwnerOrApproved(LiquidInfrastructureNFT.AccountId()) {
        if (_isPastMinDistributionPeriod()) {
            distributeToAllHolders();
        }
        burnFrom(account, amount);
    }

    function withdrawFromAllManagedNFTs() public {
        withdrawFromManagedNFTs(ManagedNFTs.length);
```





## Assessed type

Access Control
Loss of funds in a dangerous Math calculation
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L275
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L359


# Vulnerability details

## Impact
Loss of funds because of a bad and dangerous calculation. There is a potential vulnerability that leads to Holders receiving nothing cause `entitlement` is **0**. 

## Proof of Concept
There is a statement in the `_beginDistribution` function that calculates the `entitlement`:

```js
for (uint i = 0; i < distributableERC20s.length; i++) {
            uint256 balance = IERC20(distributableERC20s[i]).balanceOf(
                address(this)
            );
            // q What if supply is greater than balance? 
 @>           uint256 entitlement = balance / supply;
            erc20EntitlementPerUnit.push(entitlement);
        }
```

the `balance` is the total balance of one ERC20 token from all of the `ManagedNFTs` that were sent to the `LiquidInfrastructureERC20` contract via the `withdrawFromManagedNFTs` function. all the assets were aggregated to this contract so it seems that `balance` is always greater than `supply`.
if the `balance` is assigned to just one **ERC20** from one member of `ManagedNFTs` then the value becomes smaller which potentially causes the `entitlement` to **0**. then it would push 0 to the `erc20EntitlementPerUnit` array.
And of course, if the owner keeps minting, this will happen more and more
these will lead to increasing the chances of making the `entitlement` value to zero.
The impact lies in the for loop of the `distribute` function:

```js
for (uint j = 0; j < distributableERC20s.length; j++) {
                    IERC20 toDistribute = IERC20(distributableERC20s[j]);
  @>                  uint256 entitlement = erc20EntitlementPerUnit[j] *
                        this.balanceOf(recipient);
                    if (toDistribute.transfer(recipient, entitlement)) {
                        receipts[j] = entitlement;
                    }
                }
```
The `entitlement` will be **0** too in this function and the result is sending nothing to the holders. note that at the end and start of the distribution, the `erc20EntitlementPerUnit` array will be deleted.

now the question is How exactly a malicious user can make the `balance` variable smaller?
*The answer lies in the `withdrawFromManagedNFTs` function*:

*Suppose we have these assumptions*:

- `MinDistributionPeriod` = `X`
- `ManagedNFTs.length` = 5
- `withdrawERC20s` (or `distributableERC20s`) = [Dai, USDT, USDC]

***Here is the step-by-step scenario***:

1. A malicious user will call the `withdrawFromManagedNFTs(1)` function in the `X - 1` block number. the result is that all of the `withdrawERC20s` tokens in the first member of the `ManagedNFTs` are sent
Improper of use of .transfer leads to locking of funds and incorrect accounting
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L198-L237


# Vulnerability details

## Impact 
Locked Funds and Incorrect Accounting
Within the distribute function, recipients are transferred amounts of various ERC20 tokens held in the distributableERC20s array.
The use of .transfer instead of .safeTransfer creates two issues:
1st. If a token within in distributableERC20s either returns false despite a successful transfer or returns nothing at all, the contract doesn't handle either case leading to unexpected behavior which may include locking of funds.
2nd. The result of such unexpected behavior is that receipts[j] is never set to 'entitlement'. This leads to incorrect accounting.

## Proof of Concept
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L224-L226

## Tools Used
Manual Review

## Recommended Mitigation Steps
Create a whitelist of ERC20 tokens or use OpenZeppelin SafeERC20 and .safeTransfer function.








## Assessed type

ERC20
DoS: Blacklisted user may prevent withdrawExcessRewards()
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L223


# Vulnerability details

## Impact
If one user becomes blacklisted or otherwise cannot be transferred funds in the `distribute` tokens or the deposit token will not be able to be sent to the address that sent it to the contract. Tokens can be locked in the contract because the `distribute` function uses a for loop and a single user can not withdraw
 
## Proof of Concept
It is possible for `toDistribute.transfer(recipient, entitlement)`; to fail for numerous reasons. Such as if a user has been blacklisted (in certain ERC20 tokens) or if a token is paused or there is an attack and the token is stuck.

This will prevent `distribute()` from being called.
## Tools Used
manual

## Recommended Mitigation Steps

check if an address is Blacklisted before the transfer of tokens is made. 


## Assessed type

DoS
`holder` can burn ERC20 tokens which leads DOS attack.
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L127-L146
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L142-L145


# Vulnerability details

## Impact
`holder` can burn ERC20 tokens which leads DOS attack.

## Bug
`holder` can burn ERC20 tokens via `burnAndDistribute`. This triggers the `_beforeTokenTransfer` hook, which increases the length of `holder` array by adding 0 addresses to the array.

Attacker can burn tokens in small amounts such as 1 wei a time to accumulate and increase the array length to a very large number, which in turn will make gas very costly to execute all major functions of the contract `LiquidInfrastructureERC20` such as `mint`, `burn` and `distribute`.

## Proof of Concept
Here is the foundry [test file](https://gist.github.com/0x0bserve/cc9b4203174979d8e81980efafed8be9)

Here is the test with main logic of the exploit:
```javascript
Codefunction testHigh() public {
        // Owner approves attacker and mints and transfers 1 ether to attacker
        vm.startPrank(owner);
        liquidERC20.approveHolder(attacker);
        liquidERC20.mint(attacker, 1 ether);

        // holders array length is 1
        assertEq(liquidERC20.holderLength(), 1);
        vm.stopPrank();

        // attacker burns 1 wei at a time to increase the length of the holders array
        vm.startPrank(attacker);
        liquidERC20.burnAndDistribute(1);

        // holders array length is 2
        assertEq(liquidERC20.holderLength(), 2);

        for (uint i = 0; i < 1000; i++) {
            liquidERC20.burnAndDistribute(1);
        }

        console.log("Holders Array Length: ", liquidERC20.holderLength());
        vm.stopPrank();
    }
```

## Tools Used
Foundry

## Recommended Mitigation Steps
Add these lines in `_beforeTokenTransfer` function of `LiquidInfrastructureERC20` contract.
```javascript
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
        require(!LockedForDistribution, "distribution in progress");
        if (!(to == address(0))) {
            require(
                isApprovedHolder(to),
                "receiver not approved to hold the token"
            );
        }
        if (from == address(0) || to == address(0)) {
            _beforeMintOrBurn();
        }
        bool exists = (this.balanceOf(to) != 0);
--        if (!exists) {
++       if (!exists && to != address(0)) {
            holders.push(to);
        }
    }
```


## Assessed type

DoS
Protocol doesn't take into account the different decimals of tokens like USDT, USDC.
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L270-L277


# Vulnerability details

## Impact
The project does not take into account the decimals of various tokens like USDC, USDT which have 6 decimals instead of 18. This will lead to `erc20EntitlementPerUnit` being 0 for these tokens and users will not receive anything during distribution.

## Proof of Concept
Look at the following block of code:

```
        // Calculate the entitlement per token held
        uint256 supply = this.totalSupply();
        for (uint i = 0; i < distributableERC20s.length; i++) {
            uint256 balance = IERC20(distributableERC20s[i]).balanceOf(
                address(this)
            );
            uint256 entitlement = balance / supply;
            erc20EntitlementPerUnit.push(entitlement);
        }

```

`supply` is defined as `this.totalSupply()` and `balance` is defined as `IERC20(distributableERC20s[i]).balanceOf(address(this));`. Here, supply has 18 decimals but `distributableERC20s` can have tokens like USDC, USDT that have 6 decimals. So, it is likely that the value of `entitlement` will be 0 for these tokens because of rounding.

Hence, when the distribute function is called, it will attempt to send 0 tokens to the holders.

```
                    IERC20 toDistribute = IERC20(distributableERC20s[j]);
                    uint256 entitlement = erc20EntitlementPerUnit[j] *
                        this.balanceOf(recipient);
                    if (toDistribute.transfer(recipient, entitlement)) {
                        receipts[j] = entitlement;
                    }
```

Also, this decimal issue can be looked at in another way:

In the code snippet above, `erc20EntitlementPerUnit` is multiplied by `this.balanceOf(recipient)`. Here, the balance will have 18 decimals. So, when a token like USDC is being used, a lot more tokens will be distributed than intended. This is a critical issue for the protocol.

## Tools Used
Manual review.

## Recommended Mitigation Steps
standardize token decimals to 18 decimals to avoid these rounding issues.








## Assessed type

Other