xanoutas/solidity-security-findings huggingface dataset:
Incorrect implementation of _beforeTokenTransfer() will lead to DOS while distribution of ERC20s tokens.
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#L127-L146
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L184-L189
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L198-L237


# Vulnerability details

## Impact
Due to incorrect implementation of _beforeTokenTransfer(), each time when contract tokens are burned address(0) will be added to holders[] array which will result in DOS while distributing ERC20s tokens to all holders.

## Proof of Concept
Whenever tokens are burned in [_beforeTokenTransfer()](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L127-L146)
1. address to = address(0).
2. ```bool exists = (this.balanceOf(to) != 0)``` => (exists = false).
3. In next line statement inside ```if(!exists)``` execute (as !exists = true) and address(0) will be pushed in holders[] array.

1st case - When [distributeToAllHolders()](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L184-L189) is called holder.length is passed to [distribute()](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L198-L237) function. Inside [distribute()](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L198-L237) function ```for (i = nextDistributionRecipient; i < limit; i++)``` will try to distribute ERC20s tokens to all holders which may consists of lots of address(0) also which results out of gas errors.

Although as address(0) is not in HolderAllowlist[] so no token will be transfered to address(0) (again there is no zero address check in approveHolder() but it has onlyOwner modifier so considering address(0) will not be approved holder).

2nd case - when [distribute()](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L198-L237) function is called with (say numDistributions = 3) then while distributing inside ```for (i = nextDistributionRecipient; i < limit; i++)``` loop if index i = nextDistributionRecipient to next 2 indices consist of address(0) then no distributions will be made to approved holders. 


## Tools Used
Manual

## Recommended Mitigation Steps
Apply check to ensure ```to != address(0)``` before pushing inside holders[].


## Assessed type

DoS
ERC20 tokens with no return value will fail to transfer 
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
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L223


# Vulnerability details

## Impact
Although the ERC20 standard suggests that a transfer should return true on success, many tokens are non-compliant in this regard (including high profile, like USDT) . In that case, the .transfer() call here will revert even if the transfer is successful, because solidity will check that the RETURNDATASIZE matches the ERC20 interface.


## Tools Used
manual

## Recommended Mitigation Steps
Consider using OpenZeppelin’s SafeERC20




## Assessed type

ERC20
newErc20s are allowed even when they do not have newAmounts
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/LiquidInfrastructureNFT.sol#L120-L123


# Vulnerability details

## Impact
• Threshold amount mismatch, leading to incorrect liquid account balance transfers
• Could prevent intended excess token transfers into the contract for that token
• Loss of accuracy in accounting for funds


## Proof of Concept
newErc20s are allowed even if they do not have  newAmounts

The setThresholds() function in the LiquidInfrastructureNFT contract does not properly validate the newErc20s and newAmounts arrays


## Tools Used
Manual
## Recommended Mitigation Steps

Create a Mapping to set up what newErc20s are allowed. If they have a newAmounts they are allowed. If they do not have newAmounts, they are not allowed 

       for (uint i = 0; i < newErc20s.length; i++) {
               +++ Mapping[newErc20s[i]] = newAmounts[i]
                   thresholdErc20s.push(newErc20s[i]);
                   thresholdAmounts.push(newAmounts[i]);
               }


## Assessed type

Other
Blacklisted recepient will not get any distribution 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#L220-L226


# Vulnerability details

## Impact

Some tokens have blacklisted users and of a recipient is in the blacklist he will not be able to get any rewards.

## Proof of Concept

## Tools Used

Manual review

## Recommended Mitigation Steps

Write a withdraw function that allows any recipient to withdraw its rewards


## Assessed type

Token-Transfer
LiquidInfrastructureERC20#burnFromAndDistribute can be called 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#L344-L349


# Vulnerability details

## Impact
burnFromAndDistribute() can be called by anyone, this will can other's token to reduce totalSupply() and can transfer more to holders.

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

## Tools Used
Manual review
## Recommended Mitigation Steps
limit function to access 


## Assessed type

Access Control
Using the standard transfer may not be able to retrieve some 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#L220-L227
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureNFT.sol#L178-L182


# Vulnerability details

## Impact

Using the standard IERC20.transfer()
If distribution token is like USDT, it will not be able to transfer out, because this kind of token does not return bool
This will cause it to always revert

## Proof of Concept

## Tools Used

Manual review 

## Recommended Mitigation Steps
Token.safeTransferOut(token,msg.sender,amount);



## Assessed type

Token-Transfer
Incorrect Token Distribution Due To Decimal Mismatch
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#L270-L277


# Vulnerability details

## Description
The Solidity smart contract in question implements a distribution mechanism for ERC20 tokens to token holders based on their holdings. The `_beginDistribution` function is designed to calculate and prepare the distribution of multiple ERC20 tokens by setting the entitlement per token unit held. This entitlement is determined by dividing the balance of each distributable ERC20 token by the total supply of the contract's native tokens.

A critical vulnerability lies within the entitlement calculation process, particularly when dealing with ERC20 tokens of varying decimal places. The contract incorrectly assumes that all ERC20 tokens and the contract's native token share the same decimal precision, typically 18 decimals in Ethereum standards. This assumption leads to significant calculation errors, especially evident when distributing tokens like USDC, which operate with a different decimal precision (6 decimals for USDC versus 18 for many ERC20 tokens).

For example, if the contract has a total supply of 400 tokens and there are 400 USDT (assuming 18 decimals) in the balance, the entitlement calculation would correctly yield 1 USDT per native token. However, applying the same calculation for USDC (with 6 decimals) results in a division of `400e6 / 400e18`, simplifying to `0` due to integer division in Solidity, thus preventing any USDC distribution to holders.


## Impact
Incorrect Token Distribution due to mismatch decimal.


## Proof of Concept

1. Deploy the contract with a native token supply (e.g., 400 tokens).
2. Add a balance of 400 USDT (18 decimals) and 400 USDC (6 decimals) to the contract.
3. Execute `_beginDistribution` to calculate entitlements per token unit held.
4. Notice that the entitlement for USDT is correctly calculated as `1`.
5. Observe that the entitlement calculation for USDC results in `0`, failing to distribute USDC to token holders.


```python

>>> testERC6Decimals.mint(liquidierc, 400*10**testERC6Decimals.decimals(), {'from': deployer})
 
Transaction sent: 0xbc311d4a6634dceede6cd27f77793b06d92a93d289a67bbd2ea94d78b83cbc4f
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 10
  TestERC6Decimals.mint confirmed   Block: 11   Gas used: 50636 (0.42%)

<Transaction '0xbc311d4a6634dceede6cd27f77793b06d92a93d289a67bbd2ea94d78b83cbc4f'>
>>> testERC6Decimals.balanceOf(alice)
0
>>> testERC6Decimals.balanceOf(liquidierc)
400000000
>>> liquidierc.balanceOf(alice)
400000000000000000000
>>> liquidierc.distribute(1, {'from': deployer})
 
Transaction sent: 0xd8f8f396e489b127e01d2aec0207536084c6c004e45cbd7f2f4b342dbcd9666e
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 11
  LiquidInfrastructureERC20.distribute confirmed   Block: 12   Gas used: 102776 (0.86%)

<Transaction '0xd8f8f396e489b127e01d2aec0207536084c6c004e45cbd7f2f
recoverAccount() does not recover any token balances as described
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/LiquidInfrastructureNFT.sol#L187-L205


# Vulnerability details

## Impact
recoverAccount() does not recover any token balances as described

## Proof of Concept
recoveryAccount() only emits the TryRecover event, but does not actually recover account balances.

The comment says it should recover ERC20 tokens to the contract, but no token transfer occurs. It also states that SuccessfulRecovery should be emitted after transfer, but this does not happen

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

## Tools Used
Manual review

## Recommended Mitigation Steps
recoverAccount() needs to:

- Actually perform the token recovery logic
- Transfer recovered ERC20 balances to the contract
- Emit the SuccessfulRecovery event with details on recovered tokens


## Assessed type

Context
dos error during distribution of all 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#L187
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L209


# Vulnerability details

## Impact
This is almost similar to one bug issue that i reported, it is slightly indifferent  because `distributeToAllHolders` while executing a call to `distribute` function passes in the total length of holders array which will not pass the check in `distribuyte` i.e 
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L208

since here `nextDistributionRecipient` will be 0 because of `beforedistribute` call execution and since `numDistributions` = `holders.length` due to passed argument, so the check fails, ultimately making the function revert.

## Proof of Concept
```
 function distributeToAllHolders() public {
        uint256 num = holders.length;
        if (num > 0) {
            distribute(holders.length);
        }
    }
```
The check 
```
 uint256 limit = Math.min(
            nextDistributionRecipient + numDistributions,
            holders.length
        );
```



## Recommended Mitigation Steps
``` diff
if (num > 0) {
-            distribute(holders.length);
+            distribute(numDistributions);  
        }
```


## Assessed type

DoS
Any approved owner can burn token from other person
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#L344


# Vulnerability details

## Impact
The `burnFromAndDistribute` is used by any token holder to burn their tokens after distribution which is done by calling `distributeToAllHolders`[ here is also one vulnerability will write it in another report].
The problem arises with the argument `account` , so any malicious token holder can pass any account address if he has knowledge of it and burn the tokens from that account

## Proof of Concept
```
 function burnFromAndDistribute(address account, uint256 amount) public {
        if (_isPastMinDistributionPeriod()) {
            distributeToAllHolders();
        }
@>        burnFrom(account, amount);
    }
```

## Recommended Mitigation Steps
Use `msg.sender` instead of account


## Assessed type

Access Control
Distribution never ends
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#L234


# Vulnerability details

## Impact
the `if` block which calls the `_endDistribute` function  in $distribute$ function is never executed till `numDistributions` remains less than total no. of holders
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L209
Since `nextDistributionRecipient` in here will alaways be 0 because $_beginDistribution$ alaways sets the `nextDistributionRecipient` to 0 at the end
So this `nextDistributionRecipient + numDistributions`  is just `numDistributions,. So if `numDistributions` < `holderslength` , then `nextDistributionRecipient` will never be equal to `holderslength`

## Proof of Concept
```
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 (toDistribute.transfer(recipient, entitlement)) {
                        receipts[j] = entitlement;
                    }
                }

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

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

## Tools Used
Vs Code

## Recommended Mitigation Steps
Now really sure how to mitigate the risk, the most possible way could thing of is  instead of setting the `nextDistributionRecipient` to `i` after the loop it could be set to  `holders.length` directly so the block get's executed after the total entitlement is done


## Assessed type

DoS
Double entry point tokens will create double spending or DOS over distribution
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#L224-L226


# Vulnerability details

There is a special categories of tokens called Double Entry Point tokens, one good example of a token was UST. In a nutshell these are 2 completely different contracts ( different addresses ) that are operating over the same storage space. If the LiquidInfraERC20 will receive rewards from a token like this and it will try to distribute them based on the both addresses it have, because it is operating in loop, (from ERC20 case to ERC20 case) it can either revert the whole distribution process (basically blocking it) or some users will receive the rewards that others should have.
## Impact
Based on the fact that it will create a double spend situation, it can either revert because is running out of fundgs, or just continue execution if the underlying ERC20 will not revert in the case of failed transactions, in that case the users that are in front of the holders array will receive the tokens that the ones from the end of the array should have received 

## Proof of Concept
1. Let's say LiquidInfrastructureERC20 is holding 2 nfts, these 2 nfts are a victim of double entry point tokens, they are basically holding the same tokens but with different contract addresses.
2. LiquidInfrastructureERC20 will withdraw the balances from the 2 nfts, let's say 50 from each, so it will receive 50 UST0x1 and 50 UST0x2 ( they are the same UST tokens but I added the 0x1 and 0x2 notation to understand the difference that the contracts you are interacting with have different addresses), in total 100 UST tokens. 
3. Now it will try to distribute the rewards to the holders of LiquidInfrastructureERC20, let's say we have 2 holders, Alice and Bob, when the entitlement per unit is calculated it will have the same value...
4. When the actual transfer of tokens is takign place, the first holder from the array, ( let's say Alice in our case ) [will be blassed with a double spend event as the LiquidInfrastructureERC20 it will send basically the same tokens twice, but from different ERC20 addresses as it is iterating through the distributableERC20s](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L220-L227) 

## Tools Used
Manual Review

## Recommended Mitigation Steps
Be sure you are not using double entry point tokens ( e.g UST ), and periodically check the tokens that are in usage as you do not have any guarantee that one that is already in use can not become a double entry point token


## Assessed type

Token-Transfer
No check for MinDistributionPeriod on deployment this could brick the protocol
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#L249


# Vulnerability details

## Impact
`MinDistributionPeriod` has no check on constructor, the result is, the protocol could be bricked since most of methods relying on `_isPastMinDistributionPeriod()`.

## Proof of Concept
**MinDistributionPeriod** can only be set on deployment but has no check [MinDistributionPeriod = _minDistributionPeriod](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L471), and can't be changed later. If the deployer pass any value that is not effective to be Minimum Distribution Period, it will cause the whole protocol process to be bricked and not working as expected.

For example `MinDistributionPeriod`  could be passed as zero or any small number on construcor and this will not achieve the concept of `_isPastMinDistributionPeriod()` on [L249](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L249) which is 
```
 return (block.number - LastDistribution) >= MinDistributionPeriod;
```

The possibility of passing a low value for `MinDistributionPeriod` on constructor through deployment, will cause the code above to be not effective at all as the minimum distribution period is always elapsing with no effect on protocols methods.

## Tools Used
Manual Review


## Recommended Mitigation Steps
Add a condition check for `MinDistributionPeriod` on constructor for a minimum distribution period.


## Assessed type

Other