xanoutas/solidity-security-findings huggingface dataset:
`LiquidInfrastructureERC20::distribute90` - Should have checks to ensure zero values for `entitlement` and/or `this.balanceOf(recipient)` are not processed, to rather skip over these cases via use of `continue;` and `break;` respectively.
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#L194


# Vulnerability details

When either `entitlement` or `this.balanceOf(recipient)` is zero, there is no reason to process those cases and spend the additional gas in an already highly gas constrained function.
Recommended improvements below.

Impact:
- Not implementing the suggestions below would contribute to the risk of out of gas errors.

Recommendations:

At the start of this `for` loop cache `erc20EntitlementPerUnit[j]` to local variable `_erc20EntitlementPerUnit` then do `if (_erc20EntitlementPerUnit == 0) continue;` and use the cached `_erc20EntitlementPerUnit` in this line; ideally do similar(but use `break;` instead) for `recipient` balance of this ERC20 in case their balance is zero.

```diff
        for (i = nextDistributionRecipient; i < limit; i++) {
        address recipient = holders[i];
        if (isApprovedHolder(recipient)) {
        	uint256[] memory receipts = new uint256[](distributableERC20s.length);
+			uint256 thisBalanceOfRecipient = this.balanceOf(recipient);
+			if (thisBalanceOfRecipient == 0) break;
    		for (uint256 j = 0; j < distributableERC20s.length; j++) {
+		    	uint256 _erc20EntitlementPerUnit = erc20EntitlementPerUnit[j];
+				if (_erc20EntitlementPerUnit == 0) continue;
        		IERC20 toDistribute = IERC20(distributableERC20s[j]);
-		        uint256 entitlement = erc20EntitlementPerUnit[j] * this.balanceOf(recipient);
+		        uint256 entitlement = _erc20EntitlementPerUnit * thisBalanceOfRecipient; /// GAS optimization
        		if (toDistribute.transfer(recipient, entitlement)) {
            		receipts[j] = entitlement;
        		}
```



## Assessed type

Invalid Validation
`LiquidInfrastructureERC20::mint()` - Combined with the fact that owner can make himself a holder, the fact that owner can mint as many LI ERC20 tokens as he wants to himself, enables a vulnerability where he can game the system and during distributions get the biggest share of the rewards, effectively taking rewards that belong to other holders, due to his unfair advantage in terms of total tokens held.
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#L279


# Vulnerability details

LOW severity if trusted role, otherwise at least medium severity.

A rogue/untrusted owner can mint themselves LI ERC20 tokens to an address of their choice(or to address(this)) and make himself a holder, thereby enabling his participation in rewards distribution, but since he has full control over how many tokens he can mint, he can game the system this way to unfairly receive the majority of the rewards during distributions.

There are no checks to prevent this.

```solidity
    function mint(address account, uint256 amount) public onlyOwner nonReentrant {
        _mint(account, amount);
    }
```

Recommendation:

- At the very least owner should not be allowed to make his own `msg.sender` address or `address(this)` a holder, to help maintain trust in the protocol.


## Assessed type

Other
`LiquidInfrastructureERC20::_isPastMinDistributionPeriod()` - L220: should use `>` and not `>=`.
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#L220
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L181-L182
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L268-L269
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L290-L291
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L303-L304


# Vulnerability details

It's clear from the definition of the state variable `MinDistributionPeriod` that it `"Holds the minimum number of blocks required to elapse before a new distribution can begin"`, which clearly translates to `>` and not `>=` for the following check on L220:
```solidity
return (block.number - LastDistribution) >= MinDistributionPeriod;
```

To boost clarity in understanding this:
A minimum number of blocks need to ELAPSE before a new distribution can begin. The key here is `ELAPSED`. This clearly means that `>=` is incorrect because it doesn't exclude the last block from being used for the new distribution, where `>` would ensure that the new distribution can only start AFTER the last block of the previous distribution period.

Affected areas/functions of the contract:
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L181-L182
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L268-L269
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L290-L291
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/3adc34600561077ad4834ee9621060afd9026f06/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L303-L304

IMPACT:

I deem this to be at least medium severity because of the below impacts which violates some important invariants.

The problem with the above is two things:
- can begin new distribution AT the last block of the minimum distribution period, which is not permitted according to protocol logic.
- new distribution period's start block will overlap with previous distribution's last block
- mint, burn, transfer of tokens can happen during last block of minimum distribution period

The above is exactly what `>
Holders unable to claim accurate revenue
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

## Description
The liquidity infrastructure calculates holder revenue based on the token balance held by each holder and the revenue generated by the controlled NFTs. This revenue is then distributed among holders through the `LiquidInfrastructureERC20` contract.
``` solidity
uint256 entitlement = erc20EntitlementPerUnit[j] * this.balanceOf(recipient);
```
The current method of calculating `erc20EntitlementPerUnit` in `_beginDistribution()` introduces inaccuracies because of Solidity's integer math. Specifically, dividing the ERC20 token balance by the total supply in this manner truncates any decimals, leading to a loss of precision.
``` solidity
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);
}
```
##### Example:
If the `balance` is `9e18` and the `supply` is `1e19`, the correct entitlement should be `0.9` but the code erroneously calculates it as `0` because the division discards the decimal part.

## Impact
* Holders are unable to claim actual revenue and loss tokens in that distribution round.

## Proof of Concept
``` solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
import {LiquidInfrastructureNFT} from "@contracts/LiquidInfrastructureNFT.sol";
import {LiquidInfrastructureERC20} from "@contracts/LiquidInfrastructureERC20.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract USDT is ERC20{
    constructor() ERC20("USDT mock", "USDT") {}

    function mint(address to, uint256 amount) public {
        _mint(to, amount);
    }
}

contract AltheaPOC is Test{

    LiquidInfrastructureNFT private nft;
    LiquidInfrastructureERC20 private erc20;
    USDT private usdt;

    // Accounts
    address public NFT_OWNER = makeAddr("NFT_OWNER");
    address public ERC20_OWNER = makeAddr("ERC20_OWNER");
    address public ALICE = makeAddr("ALICE");
    address public BOB = makeAddr("BOB");

    // Values
    uint256 public ALICE_HOLD_AMOUNT = 100e18;
    uint256 public BOB_HOLD_AMOUNT = 300e18;


    function setUp() public {
        // Deploy USDT mock.
        usdt = new USDT();

        // Deploy LiquidInfrastructureNFT and set thresholds.
        vm.startPrank(NFT_OWNER);
        nft = new LiquidInfrastructureNFT("TestAccount");
        address[] memory erc20s = new address[](1);
        uint256[] memory erc20sAmounts = new uint256[](1);
        erc20s[0] = address(usdt);
        erc20sAmounts[0] = 100e18;
        nft.setThresholds(erc20s, erc20sAmounts);
        usdt.mint(address
check return value of transferfrom .
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#L418


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

A call to transferFrom or transfer is frequently done without checking the results. For certain ERC20 tokens, if insufficient tokens are present, no revert occurs but a result of "false" is returned. So its important to check this. If you don't you could mint tokens without have received sufficient tokens to do so. So you could loose funds.


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

nft.transferFrom(address(this), to, nft.AccountId());
## Tools Used

## Recommended Mitigation Steps
use safetransferfrom or safe transfer.


## Assessed type

ERC20
`LiquidInfrastructureERC20.sol` disapproved holders keep part of the supply, diluting approved holders revenue.
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#L275
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L222


# Vulnerability details

## Impact
When `LiquidInfrastructureERC20` owner disapproves a holder, it prevents the holder from receiving any revenue from the contract. However, the disapproved holder will still keep part of the supply, diluting the revenue of the approved holders.

The dilution is a result of the calculation of the entitlements per token held, which is based on the division of the ERC20 balances held by the `LiquidInfrastructureERC20` contract and the total supply of the `LiquidInfrastructureERC20` token. 

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

```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();
        for (uint i = 0; i < distributableERC20s.length; i++) {
            uint256 balance = IERC20(distributableERC20s[i]).balanceOf(
                address(this)
            );
@>            uint256 entitlement = balance / supply;
            erc20EntitlementPerUnit.push(entitlement);
        }

        nextDistributionRecipient = 0;
        emit DistributionStarted();
    }
```

## Proof of Concept

Test Case:

```solidity
    function test_dilutedDistribution() public {
        address nftOwner1 = makeAddr("nftOwner1");
        uint256 rewardAmount1 = 1000000;
        nftOwners = [nftOwner1];

        vm.prank(nftOwner1);
        LiquidInfrastructureNFT nft1 = new LiquidInfrastructureNFT("nftAccount1");

        nfts = [nft1];

        // Register one NFT as a source of reward erc20s

        uint256 accountId = nft1.AccountId();

        thresholdAmounts = [0];

        // Transfer the NFT to ERC20 and manage

        vm.startPrank(nftOwner1);
        nft1.setThresholds(erc20Addresses, thresholdAmounts);
        nft1.transferFrom(nftOwner1, address(infraERC20), accountId);
        
        vm.stopPrank();
        assertEq(nft1.ownerOf(accountId), address(infraERC20));
        vm.expectEmit(address(infraERC20));
        emit AddManagedNFT(address(nft1));
        vm.startPrank(erc20Owner);
        infraERC20.addManagedNFT(address(nft1));
        vm.roll(1);

        // Allocate rewards to the NFT
        erc20A.transfer(address(nft1), rewardAmount1);
        assertEq(erc20A.balanceOf(address(nft1)), rewardAmount1
NFT tokens can be permanently lost due to unsafe use of `transferFrom` method
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#L413-L434
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L418


# Vulnerability details

## Impact
NFT tokens can be permanently lost due to unsafe use of `transferFrom` method.

## Bug
Not every contract address is capable of receiving NFTs. If NFT is tranferred to such a contract, which has no capabilities to handle and work with NFTs, without confirming, it will be permanently stuck in that contract.

In `LiquidInfrastructureERC20` contract's `releaseManagedNFT` function, `transferFrom` method is used.

## Tools Used
Foundry

## Recommended Mitigation Steps
`safeTransferFrom` should be used instead of `transferFrom` method.
Add these lines in `releaseManagedNFT` function of `LiquidInfrastructureERC20` contract.
```javascript
    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);
    }
```


## Assessed type

ERC721
Unchecked transfers
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#L225


# Vulnerability details

## Impact
Multiple calls to `transfer` are frequently done without checking the results. For certain ERC20 tokens, if insufficient tokens are present, no revert occurs but a result of “false” is returned. It’s important to check this. If you don’t, in this concrete case, some airdrop eligible participants could be left without their tokens. It is also a best practice to check this.


## Tools Used
Manual testing

## Recommended Mitigation Steps
Check the result of `transfer`. Although if this is done, the contracts will not be compatible with non standard ERC20 tokens like USDT. For that reason, I would rather recommend making use of SafeERC20 library: `safeTransfer`


## Assessed type

DoS
Accumulation of Unused Distributable Tokens when Disapprove Holders in LiquidInfrastructureERC20 Contract
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#L116-L119
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L216


# Vulnerability details

## Description
The `LiquidInfrastructureERC20` contract implements a mechanism to disapprove token holders from receiving further distributions. When the owner calls the `disapproveHolder` function, disapproved holders lose their entitlement to future distributions. However, the existing tokens share held by these disapproved holders remain in the contract, leading to an accumulation of tokens over time.

## Impact
The accumulation of tokens from disapproved holders can result in an imbalance in the distribution of rewards among remaining token holders. Disapproved holders' tokens are effectively locked in the contract, which may affect the fairness and effectiveness of the distribution mechanism. Moreover, the accumulation of unused tokens may also impact the overall token economics.
The accumulated tokens can not be retrieved even by the owner.

## Proof of Concept
1. (Foundry Setup) Add `require("@nomicfoundation/hardhat-foundry")` in `hardhat.config.js` and run this to be able to run the PoC:

```bash
npm install --save-dev @nomicfoundation/hardhat-foundry
npx hardhat init-foundry
```

2. Create a `test/LiquidInfrastructureERC20Test.t.sol` file containing the code below:

```solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.12;

import {Test, console} from "forge-std/Test.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {LiquidInfrastructureERC20} from "../contracts/LiquidInfrastructureERC20.sol";
import {LiquidInfrastructureNFT} from "../contracts/LiquidInfrastructureNFT.sol";

contract LiquidInfrastructureERC20Test is Test {
    LiquidInfrastructureERC20 public liqInfraERC20;
    address erc20Owner = makeAddr("ERC20_Owner");
    address alice = makeAddr("Alice");
    address bob = makeAddr("Bob");
    
    
    address[] erc20Addresses;
    address[] managedNFTs;
    address[] approvedHolders;

    function setUp() public {      
        // Deploy ERC20 contract and LiquidInfrastructureERC20 contract
        erc20Addresses = new address[](1);
        erc20Addresses[0] = address(new MockERC20());

        vm.startPrank(erc20Owner);
        liqInfraERC20 = new LiquidInfrastructureERC20(
            "Infra",
            "INFRA",
            managedNFTs,
            approvedHolders,
            500,
            erc20Addresses
        );

        // Approve holders and mint tokens, for Alice and Bob
        liqInfraERC20.approveHolder(alice);
        liqInfraERC20.mint(alice, 50);

        liqInfraERC20.approveHolder(bob);
        liqInfraERC20.mint(bob, 50);
        vm.stopPrank();

    }
Zero token transfer can cause a potential DoS in  ` distribute `
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#L223
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L198


# Vulnerability details

## Impact
Some ERC20 implementations revert on zero value transfers (see https://github.com/d-xo/weird-erc20#revert-on-zero-value-transfers). If at least one of the address tokens includes this behavior, then the current implementation may cause a denial of service, as a zero amount transfer in this token will block the whole action and revert the transaction.


## Tools Used
manual 

## Recommended Mitigation Steps
Check for zero amount before executing the transfer:

```solidity
  function distribute(uint256 numDistributions) public nonReentrant {
        require(numDistributions > 0, "must process at least 1 distribution");
        if (!LockedForDistribution) {
            require(
                _isPastMinDistributionPeriod(),
                "MinDistributionPeriod not met"
            );
            _beginDistribution();
        }

        uint256 limit = Math.min(
            nextDistributionRecipient + numDistributions,
            holders.length
        );

        uint i;
        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(entitlement>0)
                     if (toDistribute.transfer(recipient, entitlement)) {
                        receipts[j] = entitlement;
                    }
                }

                emit Distribution(recipient, distributableERC20s, receipts);
            }
        }
        nextDistributionRecipient = i;

        if (nextDistributionRecipient == holders.length) {
            _endDistribution();
        }
    }
```


## Assessed type

DoS