xanoutas/solidity-security-findings huggingface dataset:
Reward tokens with less than 18 decimals will not be distributed due to precision loss.
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Vulnerability details
Tokens inteded to be used as reward tokens (i.e. `distributableERC20s`), mentioned by the sponsor, such as USDC and USDT have only 6 decimals and `LiquidInfrastructureERC20` has 18 decimals. As a result, the calculation for `entitlement` in [LiquidInfrastructureERC20.sol#L275](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L275) will result in 0 due to precision loss and such tokens will never be properly distributed.

```solidity
// File: althea-l1-pocs/src/LiquidInfrastructureERC20.sol::_beginDistribution
269:         // Calculate the entitlement per token held
270:         uint256 supply = this.totalSupply();
271:         for (uint i = 0; i < distributableERC20s.length; i++) {
272:             uint256 balance = IERC20(distributableERC20s[i]).balanceOf(
273:                 address(this)
274:             );
275: @>          uint256 entitlement = balance / supply; // will result in zero
276:             erc20EntitlementPerUnit.push(entitlement);
277:         }
```

## Imapact
Tokens with less than 18 decimals will never be distributed due to precision loss.

## Proof of Concept
The code bellow illustrates how tokens with less than than 18 decimals, such as USDC, will never be distributed due to precision loss:

```solidity
01: //SPDX-License-Identifier: Apache-2.0
02: pragma solidity 0.8.12;
03: 
04: import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
05: import "../src/LiquidInfrastructureNFT.sol";
06: import "../src/LiquidInfrastructureERC20.sol";
07: import "forge-std/Test.sol";
08: import "forge-std/console2.sol";
09: 
10: contract DT is ERC20 {
11:     address private owner;
12: 
13:     constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {
14:         owner = msg.sender;
15:     }
16: 
17:     function mint(address to, uint256 amount) public {
18:         require(msg.sender == owner, "not allowed");
19:         _mint(to, amount);
20:     }
21: }
22: 
23: contract USDC is ERC20 {
24:     address private owner;
25: 
26:     constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {
27:         owner = msg.sender;
28:     }
29: 
30:     function mint(address to, uint256 amount) public {
31:         require(msg.sender == owner, "not allowed");
32:         _mint(to, amount);
33:     }
34: 
35:     function decimals() public view override returns (uint8) {
36:         return 6;
37:     }
38: }
39: 
40: 
41: contract LiquidInfrastructureERC20Test is Test {
42:     address owner = 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496;
43:     address alice = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
44:     address bob   = 0x70997970C51
Owner can block all withdrawals by partially removing ManagedNFTs
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

In `LiquidInfrastructureERC20.sol`, the function `withdrawFromManagedNFTs()` serves to withdraw ERC20 assets from the managed NFTs that are held by the contract. This function has no control access, therefore anyone can call it. This function will pull `numWithdrawals` funds and save the current index. However, if the owner of the contract removes an amount of managed NFTs that put the current index out of range, the function will always revert.

## Impact
The owner will DoS the withdrawal of assets to the contract, regardless of intention. Submitting as a medium since in theory the admin could add mock liquid infra contracts just to restart the loop.

## Proof of Concept
>Assume a LiquidInfrastructureERC20 holds 5 ManagedNFTs contracts, and the distribution process has begun
>User calls `withdrawFromManagedNFTs(4)`
>The owner proceeds to remove the last 2 elements from the managed NFTs array, leaving 3 managed NFTs remaining, with indexes 0, 1 and 2.
>Next time `withdrawFromManagedNFTs()` is called, the function will revert because it's trying to access index 3, when the array only allows access up to index 2.

## Tools Used
Manual code reading

## Recommended Mitigation Steps
Before the loop begins, add a check to see if `nextWithdrawal` is higher than `numWithdrawals + nextWithdrawal` or `ManagedNFTs.length`.
```solidity
...
        uint256 limit = Math.min(
            numWithdrawals + nextWithdrawal,
            ManagedNFTs.length
        );

        if (nextWithdrawal > limit)
            nextWithdrawal = limit;

        uint256 i;
...
```


## Assessed type

DoS
LiquidInfrastructureERC20:_afterTokenTransfer , This bug causes the loss of the original element at the last position of the array and duplicates the element at index i at the end before removing it, resulting in unintended data loss and duplication.
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
It overwrites the element at index i with the last element and then removes the last element. This is indeed a bug because the element that was originally at the last position in the array is lost, and the element at index i is duplicated at the end of the array, which later got deleted.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Using proper swapping between the index having balance '0' and the last element index and then pop(deleting) the holders array.


## Assessed type

Error
zero address check
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
nft can be lost if sent to a zero address
 
## Tools Used
manual

## Recommended Mitigation Steps
zero address check before the transfer of nft


## Assessed type

ERC721
Malicious Nfts could be added ! 
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
 function `addManagedNFT` is for adding infrastructure NFTs , However theres not enough validation regarding that . The only check the function has is "InfrastructureERC20 contract must already be the owner of the `nftContract`" . But this check can be bypassed easily by any NFT contract . 

## Proof of Concept

```solidity 
    function addManagedNFT(address nftContract) public onlyOwner {
        LiquidInfrastructureNFT nft = LiquidInfrastructureNFT(nftContract);
        address nftOwner = nft.ownerOf(nft.AccountId());
        require(
            nftOwner == address(this),
            "this contract does not own the new ManagedNFT"
        );
        ManagedNFTs.push(nftContract);
        emit AddManagedNFT(nftContract);
    }

```
PoC : 

1. Alice creates a NFT with similar function to infrastructureNFT  but with different logical flow . 
2. alice sets the tokenID 1  to InfrastructureERC20 contract . 
3. Just doing this can successfully bypass the check present in the addManagedNFT function . 

Even alice's NFT is listed in InfrastructureERC20 contract , Alice has the full control over his contract . He can deny giving revenues in that particular NFT . 
## Tools Used
None ! 
## Recommended Mitigation Steps

Implement More checks to validate the NFT token properly . 


## Assessed type

Context
`addManagedNFT()` - Due to how the adding of new NFT contract instances works, can add same LI NFT contract address more than once to the `ManagedNFTs` array.
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L347-L353


# Vulnerability details

There are no checks to ensure same LI NFT contract cannot be added more than once:
```solidity
    function addManagedNFT(address nftContract) public onlyOwner { 
        LiquidInfrastructureNFT nft = LiquidInfrastructureNFT(nftContract);
        address nftOwner = nft.ownerOf(nft.AccountId());
        require(nftOwner == address(this), "this contract does not own the new ManagedNFT");
        ManagedNFTs.push(nftContract); 
        emit AddManagedNFT(nftContract);
    }
```

Impact:

- `withdrawFromManagedNFTs()` will revert due to zero balance in NFT contract at second withdrawal attempt from same LI NFT contract address, due to balance being less than amount to withdraw.

Recommendation:

- Add a check to `addManagedNFT()` to check if already added.
- use a for loop to cycle through the `ManagedNFTs` array to check for array entry with address == the new `nftContract` address.
- add this for loop above the `ManagedNFTs.push(nftContract);` line, L351.



## Assessed type

Invalid Validation
It appears that `mint()`, `burn()` and `burnFrom()` will be executed DURING the minimum distribution period if distribution not completed yet and any of the following functions are called: `mintAndDistribute()`, `burnAndDistribute()`, `burnFromAndDistribute()`.
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L267-L272
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L289-L294
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L302-L307


# Vulnerability details

Case above is when the `distribute()` function is called directly but not enough times to complete distribution yet, and then one of the above functions is called.

Alternatively, even if the distribution completes via `distributeToAllHolders()` before calling `mint()`, `burn()` and `burnFrom()` via any of these `mintAndDistribute()`, `burnAndDistribute()`, `burnFromAndDistribute()`, the minting/burning will happen DURING minimum distribution period, because they share the last block of distribution completion, which according to my understanding is not allowed?

Summary:
Clearly can mint/burn DURING minimum distribution period, i.e. when locked for distribution i.e. during (!_isPastMinDistributionPeriod == true). My current level of understanding tells me this is invalid.



## Assessed type

Other
The `if (_isPastMinDistributionPeriod())` check will prevent the distribution from completing before `mint()`/`burn()`/`burnFrom()` is called in the case where `distribute()` was called directly but not enough times to complete the distribution.
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L268
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L290
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L303


# Vulnerability details

Since this same check is inside the `distribute()` function itself, its use is pretty much redundant in the following functions:
`mintAndDistribute()`, `burnAndDistribute()`, `burnFromAndDistribute()`.

The below check appears in above three functions, and could be removed due to the same check in the `distribute()` function:
```solidity
        if (_isPastMinDistributionPeriod()) {
            distributeToAllHolders();
        }
```
Here it appears in the `distribute()` function:
```solidity
        if (!LockedForDistribution) {
            require(_isPastMinDistributionPeriod(), "MinDistributionPeriod not met");
            _beginDistribution();
        }
```
Recommendation:

Remove the ` if (_isPastMinDistributionPeriod())` check so that we only have this in the three affected functions:
```solidity
            distributeToAllHolders();
```


## Assessed type

Invalid Validation
User will get no rewards if liquidIntrastructureERC20 native balance is greater than its balance of the distribution token
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Approved Holders receive 0 rewards from distribution.

## Proof of Concept
The _beginDistribution() function calculates a holder's entitlement by dividing the contract's balance of the token to be distributed by its own totalSupply, then it multiplies the result by the user's liquidInfrastructure ERC20 token holding. However, this can represent a case of division before multiplication, and using this logic the holders get no rewards from distribution in most cases expecially as it keeps minting tokens to new holders.

```
    function test_distributions() public {
        vm.startPrank(owner);

        //create mock erc20s to be distributed
        address[] memory add1 = new address[](3);
        add1[0] = address(testERC20A);
        add1[1] = address(testERC20B);
        add1[2] = address(testERC20C);

        uint256[] memory amount1 = new uint256[](3);
        amount1[0] = 0;
        amount1[1] = 0;
        amount1[2] = 0;

        //add tokens to withdrawable ERC20s and set threshold
        liqNFT.setThresholds(add1, amount1);

        //transfer LiquidInfrastructureNFT to the ERC20 contract and add as managed NFT
        liqNFT.transferFrom(owner, address(liqERC20), 1);
        liqERC20.addManagedNFT(address(liqNFT));

        address user1 = address(45);
        address user2 = address(46);
        address user3 = address(47);

        //approve users to hold the infrastructure ERC20 and mint tokens to them

        liqERC20.approveHolder(user1);
        liqERC20.approveHolder(user2);
        liqERC20.approveHolder(user3);

        liqERC20.mint(user1, 15 ether);
        liqERC20.mint(user2, 13 ether);
        liqERC20.mint(user3, 11 ether);

        //send reward tokens to be distributed to the infrastructure NFT
        testERC20A.mint(address(liqNFT), 15 ether);
        testERC20B.mint(address(liqNFT), 13 ether);
        testERC20C.mint(address(liqNFT), 11 ether);



        liqERC20.withdrawFromAllManagedNFTs();

        console2.log(testERC20C.balanceOf(address(liqERC20)));

        //increase block number
        vm.roll(501);
        
        // user gets 0 rewards from this distribution
        // emit Distribution(recipient: 0x000000000000000000000000000000000000002F, tokens: [0xf6153FE820B65E2bD7348c280323F6d743a48Bc2, 0x4516910C9FfF823CD84B4Aed42E4Da84252bAc57, 0xA19689FF816E1d083788245DE8471EdE71BD1a4D], amounts: [0, 0, 0])
        liqERC20.distributeToAllHolders();
    }
```
The above POC shows that users receive no rewards after distribution even though they should have.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Multiply the contracts distribution token balance by the users LiquidInfrastructure token holdings before dividing by totalSupply.





## Assessed type

Math
wrong implementation of releaseManagedNFT
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
wrong implementation of releaseManagedNFT as we can call for any nftContract.
we are checking require(true) that is not verifying anything.we should define a local variable and then we should assign that variable to true when we remove the  nft.
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.


 function releaseManagedNFT(
        address nftContract,
        address to
    ) public onlyOwner nonReentrant {
        LiquidInfrastructureNFT nft = LiquidInfrastructureNFT(nftContract);
        nft.transferFrom(address(this), to, nft.AccountId());

        // Remove the released NFT from the collection
        for (uint i = 0; i < ManagedNFTs.length; i++) {
            address managed = ManagedNFTs[i];
            if (managed == nftContract) {
                // Delete by copying in the last element and then pop the end
                ManagedNFTs[i] = ManagedNFTs[ManagedNFTs.length - 1];
                ManagedNFTs.pop();
                break;
            }
        }
        // By this point the NFT should have been found and removed from ManagedNFTs
      @>  require(true, "unable to find released NFT in ManagedNFTs");

        emit ReleaseManagedNFT(nftContract, to);



## Tools Used

## Recommended Mitigation Steps
define a local variable realised and assigned it to true when you are removing an nft from  ManagedNFTs.
function releaseManagedNFT(
        address nftContract,
        address to
    ) public onlyOwner nonReentrant {
  @ ++   bool realeased
        LiquidInfrastructureNFT nft = LiquidInfrastructureNFT(nftContract);
        nft.transferFrom(address(this), to, nft.AccountId());

        // Remove the released NFT from the collection
        for (uint i = 0; i < ManagedNFTs.length; i++) {
            address managed = ManagedNFTs[i];
            if (managed == nftContract) {
                // Delete by copying in the last element and then pop the end
                ManagedNFTs[i] = ManagedNFTs[ManagedNFTs.length - 1];
                ManagedNFTs.pop();
           @++     realeased=true.
                break;
            }
        }
  @++ require(realeased, "unable to find released NFT in ManagedNFTs");

        emit ReleaseManagedNFT(nftContract, to);



## Assessed type

Context
`LiquidInfrastructureERC20::_beginDistribution()` - L242: It seems `balance / supply` will round down to zero every time due to fact that `balance` is always smaller than `supply`, and there seems to be nothing in place to handle this risk. 
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

```solidity
uint256 entitlement = balance / supply;
```
Since balance will always be <= to supply, the only time this can be non-zero is when there's only one ERC20 token available for distribution with a balance equal to supply.
That would not be the normal case and therefore this division would round down to zero all the time with the current implementation.

Impact:

- distributions wont send any tokens to holders because `entitlement` value for all the distributable ERC20 tokens will be zero.

Recommendation:

- To address potential rounding down to zero in the entitlement calculation, a scaling factor could be utilized to multiply the balance before division, ensuring precision and preventing incorrect distribution of entitlements.

- make use of OZ's SafeMath library and consider using FixedPointMath libraries to handle fractional values properly.



## Assessed type

Math
`Owner` can make changes in `distributableERC20s` while distribution is running
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
`Owner` can make changes in `distributableERC20s` while distribution is running.

## Bug
Only transfers, mints, and burns functions are locked while distribution is running. Which gives `Owner` opportunity to make changes such that -

1. `Owner` can replace high value ERC20 tokens with lower value to be distributed among `holders`.
2. `Owner` can rearrange `distributableERC20s` such that lower `erc20EntitlementPerUnit` value corresponds to higher `distributableERC20s` valued ERC20 tokens. This will result in uneven distribution of tokens with everyone getting lower valued tokens.

There is no Access Control in `setDistributableERC20s` function to prevent owner from changing the `distributableERC20s` while distribution is running.
`Owner` only needs to set distributableERC20s using `setDistributableERC20s` function to his desired list to target any and every holder in ditribution phase.

## Tools Used
Foundry

## Recommended Mitigation Steps
Add these lines in `disapproveHolder` function of `LiquidInfrastructureERC20` contract.
```javascript
    function setDistributableERC20s(
        address[] memory _distributableERC20s
    ) public onlyOwner {
++        require(!LockedForDistribution, "cannot make changes in distributableERC20s during distribution");
        distributableERC20s = _distributableERC20s;
    }
```


## Assessed type

Access Control
`LiquidInfrastructureERC20::distribute()` - L195: Should use a `safeTransfer()` along with a require check instead due to the use of ERC20 `transfer()` which doesn't guarantee a boolean return value, therefore default `false` would skip the logic of this `if` statement, which would result in inaccurate values for the `receipts` array.
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L195-L197


# Vulnerability details

Recommendations:

- Replace `transfer()` with OZ's `safeTransfer()`.
- Replace the `if` statement with a require statement instead.

```diff
-    if (toDistribute.transfer(recipient, entitlement)) {
-        receipts[j] = entitlement;
-    }
+	 (bool success) = toDistribute.safeTransfer(recipient, entitlement);
+	 require(success, "transfer failed");
+	 receipts[j] = entitlement;
```


## Assessed type

Other