xanoutas/solidity-security-findings huggingface dataset:
Managed NFTs can be added multiple times, blocking reward distribution and NFT removal
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#L394-L403


# Vulnerability details


## Vulnerability Details

`LiquidInfrastructureERC20.sol` allows the contract owner to add a managed NFT twice.  When this happens, the array `ManagedNFTs` contains duplicates of the
added NFT, see [addManagedNFT](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L394-L403). When the owner removes this NFT via `releaseManagedNFT`, only one copy of the NFT is removed, due to `break`, see [line
427](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L427).

## Impact


Once an NFT is released, its copy remains in `ManagedNFTs` and blocks [`withdrawFromAllManagedNFTs`](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L351) and its variants. Effectively, all coins associated with the NFTs are blocked in the contract.

Moreover, as we argue above, there is no way to remove the duplicate address from `ManagedNFTs`.

## Proof of Concept

We produce three unit tests in `test/poc2.ts`, an adaptation of the test `test/liquidERC20.ts`. The tests are as follows:

 1. Test `duplicateNftCannotBeRemovedTest` demonstrates than the contract owner can add an NFT two times, and this NFT cannot be competely removed afterwards.
 1. Test `duplicateNftBlocksWithdrawalsTest` demonstrates that once an NFT is added twice and removed once, it blocks ERC20 withdrawals.
 1. Test `withdrawalsWorkWhenNoDuplicatesTest` demonstrates the happy path scenario, when an NFT is added and removed exactly once, and it does not block ERC20 withdrawals. The purpose of this test is to compare it with `duplicateNftBlocksWithdrawalsTest`.

The source code of `test/poc2.ts`:

```ts
import chai from "chai";

import { mine } from "@nomicfoundation/hardhat-network-helpers";
import { ethers } from "hardhat";
import {
  deployContracts,
  deployLiquidERC20,
  deployLiquidNFT,
} from "../test-utils";
import {
  LiquidInfrastructureNFT,
  LiquidInfrastructureERC20,
} from "../typechain-types/contracts";
import { ERC20 } from "../typechain-types/@openzeppelin/contracts/token/ERC20";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
const {
  loadFixture,
} = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { expect } = chai;

// Proof-of-concept to demonstrate that the owner can add a managed NFT twice
// and this would block withdrawals.
async function liquidErc20Fixture() {
  const signers = await ethers.getSigners();
  const nftAccount1 = signers[0];
  const
Holders won't receive expected revenue due to wrong entitlement 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#L270-L276
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L222-L226


# Vulnerability details

## Impact

The `LiquidInfrastructureERC20.distribute()` function relies on the `LiquidInfrastructureERC2._beginDistribution()` function to calculate entitlements for users who have invested in real-world assets managed by Althea Liquid Infrastructure. According to the documentation, the `LiquidInfrastructureERC20` contract serves as a convenient layer for investment in real-world assets, with automatic revenue accrual for token holders.

However, during the [entitlement calculation process](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L270-L276), the protocol assumes that the underlying token always has 18 decimals, which is not always the case (e.g., USDC, which has 6 decimals). This discrepancy in decimals may lead to users receiving less than the expected amount. In some cases, if their investment amount is small, they may receive even 0 as a payment due to integer division rounding down.

Given that this issue results in fund loss for users, potentially tarnishing the protocol's reputation, and may lead to financial and growth opportunities loss for the protocol, it is marked as High severity.

## Proof of Concept
This PoC will demonstrate how and why we send the wrong entitlement amount to holders. The first three steps will describe the operations in the [LiquidInfrastructureERC20._beginDistribution()](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L270-L276) function, and the final step will illustrate the process of sending the incorrect entitlement amount in the [LiquidInfrastructureERC20.distribute()](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L221-L227) function.

1. Total supply of LiquidInfrastructureERC20 token: 10,000 units (with 18 decimals).
```
uint256 supply = this.totalSupply();
```
2. We get the accumulated balance of the underlying asset so far, and the distributableERC20[i] is USDC, therefore the balance is with 6 decimals.
```
uint256 balance = IERC20(distributableERC20s[i]).balanceOf(address(this));
```
3. Due to the difference in decimals between the two tokens and integer division rounding down, this will lead to underestimation of the entitlement. After the calculation, we will put the entitlement into ```erc20EntitlementPerUnit``
NFTs not owned by LiquidInfrastuctureERC20 can be added in the constructor
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#L456-L464


# Vulnerability details

## Impact
One of the inputs contained in the ```LiquidInfrastuctureERC20``` constructor is ```_managedNFTs``` which initializes the ```ManagedNFTs``` array by including the addresses defined in ```_managedNFTs``` without checking whether the contract is the owner of these NFTs.

In case any of these NFTs are not owned by the ```LiquidInfrastuctureERC20``` contract, it will cause a DoS since functions ```withdrawFromAllManagedNFTs``` and ```withdrawFromManagedNFTs``` would revert.

To be more precise, function ```withdrawFromAllManagedNFTs``` would always revert but invoking ```withdrawFromManagedNFTs``` would work for all the NFTs upstream the first one not owned by the contract.

For instance, let's say ```LiquidInfrastuctureERC20``` is deployed and five NFTs are added in the constructor as ```_managedNFTs```. Let's now consider that the third NFT included (i.e. ```ManagedNFTs[2]```) is not owned by the contract. If ```withdrawFromAllManagedNFTs``` is invoked it will always revert but invoking ```withdrawFromManagedNFTs(2)``` would still function properly. The tokens, however, downstream the one not owned (i.e.  ```ManagedNFTs[3]``` and ```ManagedNFTs[4]```) cannot be withdrawn.

## Proof of Concept

Run test ```testNonOwnedNFT``` in Foundry

       // SPDX-License-Identifie: MIT
    
    pragma solidity 0.8.12;
    
    import "../src/LiquidInfrastructureERC20.sol";
    import "forge-std/Test.sol";
    
    contract AltheaTesting is Test {
        LiquidInfrastructureERC20 public liquidInfrastructureERC20;
        LiquidInfrastructureNFT public NFT1;
        LiquidInfrastructureNFT public NFT2;
        LiquidInfrastructureNFT public NFT3;
        LiquidInfrastructureNFT public NFT4;
        LiquidInfrastructureNFT public NFT5;
    
        ERC20 public tokenA;
        ERC20 public tokenB;
        IERC20 public tokenC;
    
        address[] public approvedHolders;
        // number of blocks per month. Assuming 7_000 blocks per day
        uint256 public minDistributionPeriod = 7_000 * 30;
        address[] public distributableERCs;
        address[] public _managedNFTs;
    
        address public owner;
        address public approvedUser1;
        address public approvedUser2;
        address public approvedUser3;
    
        function setUp() public {
            // Creation of addresses
            owner = makeAddr("owner");
            approvedUser1 = makeAddr("approvedUser1");
            approvedUser2 = makeAddr("approvedUser2");
            approvedUser3 = makeAddr("approvedUser3");
    
            // Creation of different tokens and arrays
            tokenA = new ERC20("Token A", "TKNA");
            tokenB = new ERC20("Token B", "TKNB");
            tokenC = new ERC20("Token C", "TKNC");
    
            distributableERCs.push(address(tokenA)
attacker can steal all distributable ERC20 tokens by pushing same address in "holders" array multiple times
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#L142-L145
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L271-L277


# Vulnerability details

## Vulnerability details
Its possible to push same address into `holders` array multiple times. this issue lies in this code:

    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) {
            holders.push(to);
        }
    }

the condition `bool exists = (this.balanceOf(to) != 0);` is not sufficient to prevent same address from being added to `holders` as its not checking if `amount` parameter is `0` or not.
in case that `amount` is zero, `to` will be pushed to `holders` as many times as attacker wants to.

Scenario:
- Attacker has access to two different approved wallets with each one holding 100 tokens
- Attacker transfers all tokens of second wallet to first wallet, now second wallet is on longer in `holders` array
- Attacker transfers `0` tokens from his first wallet to his second wallet for 100 times and since `bool exists = (this.balanceOf(to) != 0);` is bypassed, his second wallet will be added to `holders` for 100 times.
- Attacker calls `distributeToAllHolders` function and rewards himself many times in a row untill contract is out of tokens

## Proof of Concept
firstly, create a contract named `attacker.sol` in `/contracts` folder:

    //SPDX-License-Identifier: Apache-2.0
    pragma solidity 0.8.12; // Force solidity compliance

    import "@openzeppelin/contracts/access/Ownable.sol";
    import {LiquidInfrastructureERC20} from "./LiquidInfrastructureERC20.sol";

    contract Attacker is Ownable {
        address attacker_wallet_1;

        constructor(address _attacker_wallet_1) {
            attacker_wallet_1 = _attacker_wallet_1;
        }

        function attack(
            address _infraERC20,
            address _attacker_wallet_2,
            uint _times
        ) public onlyOwner {
            for (uint i = 0; i < _times; i++) {
                LiquidInfrastructureERC20(_infraERC20).transferFrom(
                    attacker_wallet_1,
                    _attacker_wallet_2,
                    0
                );
            }
        }
    }

then add this two `view` function to the end of `LiquidInfrastructureERC20.sol`:

    function getHolderAtIndex(uint index) public view returns (address) {
Upgraded Q -> 2 from #132 [1709983798124]
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

Judge has assessed an item in Issue #132 as 2 risk. The relevant finding follows:

 # Lines of code
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L216

# Vulnerability details
## Impact
Funds will be stuck in the contract as there are no recovery methods to take out shares of `holders` which owns the token but was disapproved at a later stage.

## Proof of Concept
Main concept of `LiquidInfrastructureERC20` is to distribute the revenue accured from NFTs to it’s holders.

Owner can use `disapproveHolder` to remove already approved holder from allowlist. It can be called anytime which is a normal behaviour.

```

    function disapproveHolder(address holder) public onlyOwner {
        require(isApprovedHolder(holder), "holder not approved");
        HolderAllowlist[holder] = false;
    }
```

During distribution, it is validated that only the approved holder can receive the tokens.

```

 @->  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);
      }
```

Now consider the following scenario:

1. Alice was approved initially and owns some `LiquidInfrastructureERC20` tokens.
2. Owner wants to remove Alice because of some reason so Owner calls `disapproveHolder` which is a normal behaviour.
3. As Alice already owns the tokens, her share of distribution tokens will always be stuck in the contract which no one can ever withdraw.

## Tools Used
VS Code

## Recommended Mitigation Steps
I would recommend to add a function with `onlyOwner` access to withdraw the share of accounts who are unapproved but still owns the token.

## Assessed type
Other

# Lines of code
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L216

# Vulnerability details
## Impact
Funds will be stuck in the contract as there are no recovery methods to take out shares of `holders` which owns the token but was disapproved at a later stage.

## Proof of Concept
Main concept of `LiquidInfrastructureERC20` is to distribute the revenue accured from NFTs to it's holders.

Owner can use `disapproveHolder` to remove already approved holder from allowlist. It can be called anytime which is a normal behaviour.

```solidity
    function disapproveHolder(address holder) public onlyOwner {
        require(isApprovedHolder(holder), "holder not approved");
Upgraded Q -> 2 from #51 [1709971279347]
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

Judge has assessed an item in Issue #51 as 2 risk. The relevant finding follows:

 ### Updating distributableERC20s array during distribution phase may break the entitlement accounting
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L205
When `distribute` function is called, the contract precalculates entitlement values for tokens stored in `distributableERC20s` array
```solidity
        // 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);
        }
```
Note that the entitlements are pushed into the array in the same order that tokens are stored in `distributableERC20s`.  If the contract owner attempts to change distributable tokens with `setDistributableERC20s` during the distribution phase
```solidity
    function setDistributableERC20s(
        address[] memory _distributableERC20s
    ) public onlyOwner {
        distributableERC20s = _distributableERC20s;
    }
```
, and new tokens are stored in a different order, wrong amounts will be distributed.
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L220
```solidity
                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;
                    }
                }
```
Consider adding this check to `setDistributableERC20s`
```solidity
require(!LockedForDistribution,"distribution is in process");
```
Upgraded Q -> 2 from #670 [1709900942686]
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: MEDIUM

Judge has assessed an item in Issue #670 as 2 risk. The relevant finding follows:

 ## [L-02] Precision loss in Distribution Calculation

**LoC:**

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L222C21-L223C51

```
uint256 entitlement = erc20EntitlementPerUnit[j] * this.balanceOf(recipient);
```

**Mitigation:**

Swap the order to avoid precision loss

```
uint256 entitlement = (this.balanceOf(recipient) * erc20EntitlementPerUnit[j]);
Anyone can call withdrawFromManagedNFT() and deposit all tokens to the liquidInfrastructureERC20.sol
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#L359


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
The function `withdrawFromManagedNFT`, is used to deposit all tokens in all the managed `NFT's` accounts to the liquidInfrastructureERC20.sol to control. i believe that this functionality is an important one and therefore shouldn't be made `public`, as anyone can call it.
## 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
Make function to be called by `onlyOwner.


## Assessed type

Access Control
Use safeMint instead of mint for ERC721
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#L310


# Vulnerability details

## Impact
 ERC721Fee can not set fee when the NFT token ID value is more than `type(uint128).max`
 RC721Fee can not set fee when the NFT token ID value is more than type(uint128).max
 
## Tools Used
manual

## Recommended Mitigation Steps
Use safeMint instead of mint to check received address support for ERC721 implementation.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L262


## Assessed type

ERC721
Precision loss in calculation of entitlement
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-L276
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L222-L223


# Vulnerability details

## Impact
Calculation of erc20EntitlementPerUnit doesn't consider adding scaling factor before division and similarly while calculating entitlement scaling factor not included in multiplication which will result in precision loss.

## Proof of Concept
Here In [erc20EntitlementPerUnit](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L275-L276) while calculating entitlement ```uint256 entitlement = balance / supply;``` suppose distributableERC20s[i] is USDC having 6 decimal precision and total supply having 18 decimal presion then on calculating uint256 entitlement = 1e6/(1000*1e18) (say) 
entitlement = 0
and similarly here [entitlement](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L222-L223) on calculation of ```uint256 entitlement = erc20EntitlementPerUnit[j] * this.balanceOf(recipient); ``` no division with scaling factor will result in inconsistency.

## Tools Used
Manual

## Recommended Mitigation Steps
consider including scaling factor
 ```uint256 entitlement = (balance * scaling_factor) / supply;```

```uint256 entitlement = (erc20EntitlementPerUnit[j] * this.balanceOf(recipient))/ scaling_factor;```


## Assessed type

Math
`_withdrawBalancesTo()` - Either the owner but more likely approved `msg.sender` who didnt know any better, OR knows but is rogue, calls this internal function via the calling functions (`withdrawBalances` or `withdrawBalancesTo`) DURING an active distribution, which is not allowed, but possible here.
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/LiquidInfrastructureNFT.sol#L171-L186


# Vulnerability details

Because there's no checks here to check if `LockedForDistribution`. This check is ONLY in the ERC20 contract, and should be added here too. This likely means can transfer tokens to ERC20 contract during partial distributions and cause inconsistent/unexpected state changes to balances of ERC20 contract, which could affect share of distributions to holders, unfair distributions in other words.

So a check needs to be implemented in `_withdrawBalancesTo` function, not the two calling functions, to check the value of `LiquidInfrastructureERC20.LockedForDistribution()`. If true, should revert and throw error.



## Assessed type

Invalid Validation