xanoutas/solidity-security-findings huggingface dataset:
`LendingLedger._checkpoint_market()` may cause DoS due to the market with low liquidity
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/main/src/LendingLedger.sol#L85
https://github.com/code-423n4/2023-08-verwa/blob/main/src/LendingLedger.sol#L92


# Vulnerability details

## Impact

`LendingLedger._checkpoint_market()` may cause DoS due to the market with low liquidity.


## Proof of Concept

As the function implementation shows, the `lendingMarketTotalBalanceEpoch[_market] = currEpoch` when the market is created.If the market is illiquid, probably no one will call `checkpoint_market()`.This means that the `lastMarketUpdateEpoch ` has not been updated for a long time and remains relatively low. After many years, when the attacker calls this function again, it enters the for loop, which may lead to a waste of gas due to an overly long array and eventually lead to DoS
```solidity
function _checkpoint_market(address _market, uint256 _forwardTimestampLimit) private {
        uint256 currEpoch = (block.timestamp / WEEK) * WEEK;
        uint256 lastMarketUpdateEpoch = lendingMarketTotalBalanceEpoch[_market]; //@audit
        uint256 updateUntilEpoch = Math.min(currEpoch, _forwardTimestampLimit);
        if (lastMarketUpdateEpoch == 0) {
            lendingMarketTotalBalanceEpoch[_market] = currEpoch;
        } else if (lastMarketUpdateEpoch < currEpoch) {
            // Fill in potential gaps in the market total balances history
            uint256 lastMarketBalance = lendingMarketTotalBalance[_market][lastMarketUpdateEpoch];
            for (uint256 i = lastMarketUpdateEpoch; i <= updateUntilEpoch; i += WEEK) { //@audit
                lendingMarketTotalBalance[_market][i] = lastMarketBalance;
            }
            if (updateUntilEpoch > lastMarketUpdateEpoch) {
                // Only update epoch when we actually checkpointed to avoid decreases
                lendingMarketTotalBalanceEpoch[_market] = updateUntilEpoch;
            }
        }
    }
```
Let's imagine a scenario, suppose Alice is a malicious user
1. Alice participated in creating a market, so `lendingMarketTotalBalanceEpoch[_market]` is present and is equal current time.
2. However, the liquidity of this market is very low and few people participate, the `lastMarketUpdateEpoch` does not update
3. Many years later
4. Alice launches a DoS attack by calling function `checkpoint_market()`


## Tools Used

manual

## Recommended Mitigation Steps

Set a `Max_epoch` parameter and check it to prevent the loop from being too long


## Assessed type

DoS
The reward of Epoch 0 can't be claimed and will be DOS
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/LendingLedger.sol#L157-L161


# Vulnerability details

## Impact

The root cause is that the epoch starts at 0, not 1, so you should not check that the epoch is not zero to determine whether the user and the market are valid.
In the code, `claim`, `checkpoint_lender` and `checkpoint_market` all judge the validity by checking the epoch is non-zero, which results in the epoch 0 reward cannot be claimed and will be DOS.
And because `checkpoint_lender` and `checkpoint_market` are DOS, users can't update manually and have to wait for the next `sync_ledger` call.

## Proof of Concept

```solidity
    function testDOSClaimEpoch0Rewards() public {
        whiteListMarket();

        vm.prank(goverance);
        ledger.setRewards(0, WEEK * 10, 1 ether);
        payable(ledger).transfer(1000 ether);

        int256 delta = 1 ether;
        vm.prank(lendingMarket);
        ledger.sync_ledger(lender, delta);

        vm.warp(WEEK);

        // claim revert
        vm.expectRevert("No deposits for this user");
        vm.prank(lender);
        ledger.claim(lendingMarket, 0, WEEK);

        // Manually update also revert
        vm.expectRevert("No deposits for this lender in this market");
        ledger.checkpoint_lender(lendingMarket, lender, WEEK);

        // Wait lendingMarket sync_ledger
        vm.prank(lendingMarket);
        ledger.sync_ledger(lender, delta);

        // Epoch 0: 0
        uint256 balance_before = address(lender).balance;
        vm.prank(lender);
        ledger.claim(lendingMarket, 0, WEEK);
        assertEq(address(lender).balance - balance_before, 0);

        // Epoch 1: 1 ether
        vm.warp(WEEK * 2);
        vm.prank(lender);
        ledger.claim(lendingMarket, 0, WEEK * 2);
        assertEq(address(lender).balance - balance_before, 1 ether);
    }
```

The POC shows that Epoch 0 rewards cannot be claimed, will DOS revert, and users cannot manually update to claim rewards, having to wait for the next sync_ledger call.

## Tools Used

Foundry

## Recommended Mitigation Steps

The Epoch should start at 1



## Assessed type

Context
Potential Overflow Vulnerability in `balanceOfAt` Function.
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/9a2e7be003bc1a77b3b87db31f3d5a1bcb48ed32/src/VotingEscrow.sol#L523


# Vulnerability details

## Impact

The [balanceOfAt function#L523](https://github.com/code-423n4/2023-08-verwa/blob/9a2e7be003bc1a77b3b87db31f3d5a1bcb48ed32/src/VotingEscrow.sol#L523)**
```solidity
return uint256(uint128(upoint.bias));
```
Here, `upoint.bias` is downcasted from `int128` to `uint128`, which could lead to an overflow if `upoint.bias` is a large negative value.

## Proof of Concept

Let's say that in the `balanceOfAt` function, `upoint.bias` is a large negative value, such as -1000000000000000000000000. This value is well below the minimum value that an `int128` can represent, which is -2^127.

When you attempt to downcast this large negative value to `uint128`, it will first be converted to an `int256` to accommodate the new type, and then it will be truncated to fit within the range of `uint128`. However, since the value is already below the minimum value that `int128` can represent, the conversion will not be able to preserve its original value. The resulting `uint128` value will be different from the original `int128` value, and it may not accurately represent the intended behavior.

In code:

```solidity
int128 upointBias = -1000000000000000000000000;
uint256 uintValue = uint256(uint128(upointBias));
```

This scenario, `uintValue` will not accurately represent the original value of `upointBias` due to the overflow during the downcasting process. This kind of issue can lead to unexpected behavior and potentially affect the functionality of your smart contract.

## Tools Used
Vscode

## Recommended Mitigation Steps

The code could be modified to to avoid the issue.

```solidity
// Check if upoint.bias is negative
    if (upoint.bias < 0) {
        // Return 0
        return 0;
    } else {
        // Return upoint.bias as a uint128
        return uint256(uint128(upoint.bias));
    }
```


## Assessed type

Math
Incorrect Slope Update in `increaseAmount()` Function.
Audit: 2023-08-verwa-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-08-verwa/blob/9a2e7be003bc1a77b3b87db31f3d5a1bcb48ed32/src/VotingEscrow.sol#L251-L257


# Vulnerability details

## Impact

**In the function [increaseAmount()#L251-L257](https://github.com/code-423n4/2023-08-verwa/blob/9a2e7be003bc1a77b3b87db31f3d5a1bcb48ed32/src/VotingEscrow.sol#L251-L257)**
If `_newLocked.end` is less than `_oldLocked.end`, the slope change for `_newLocked.end` is updated with an incorrect value. This might lead to incorrect slope changes and assert violations.

```solidity
if (_newLocked.end > block.timestamp) {
    if (_newLocked.end > _oldLocked.end) {
        newSlopeDelta = newSlopeDelta - userNewPoint.slope; // old slope disappeared at this point
        slopeChanges[_newLocked.end] = newSlopeDelta;
    }
    // else: we recorded it already in oldSlopeDelta
}
```
## Proof of Concept

To better understand the potential issue in the `increaseAmount()` function:

1. User Alice currently has a locked balance with an end time `_oldLocked.end` in the future.
2. Alice decides to increase her locked amount by calling `increaseAmount()` and provides more tokens. She also specifies a new end time `_newLocked.end` that is earlier than the previous end time `_oldLocked.end`.

Here's the relevant part of the code from the `increaseAmount()` function:

```solidity
if (_newLocked.end > block.timestamp) {
    if (_newLocked.end > _oldLocked.end) {
        newSlopeDelta = newSlopeDelta - userNewPoint.slope; // old slope disappeared at this point
        slopeChanges[_newLocked.end] = newSlopeDelta;
    }
    // else: we recorded it already in oldSlopeDelta
}
```
In this scenario, the following steps occur:

1. Alice's initial locked balance `_oldLocked` has a non-zero end time `_oldLocked.end` in the future.
2. Alice wants to increase her locked balance and sets a new end time `_newLocked.end` that is earlier than `_oldLocked.end`.
3. The code checks whether `_newLocked.end` is greater than the current timestamp (`block.timestamp`), which means the new end time is in the future.
4. Since `_newLocked.end` is in the future, the code proceeds to check whether `_newLocked.end` is also greater than `_oldLocked.end`. However, in this scenario, `_newLocked.end` is actually earlier than `_oldLocked.end`.

Issue:
Since `_newLocked.end` is earlier than `_oldLocked.end`, the code incorrectly enters the `if` block and tries to update the slope change for `_newLocked.end`. However, since `_newLocked.end` is earlier, this leads to an incorrect slope change being recorded, which could result in unexpected behavior in the contract's calculations and assert violations.

Outcome:
The incorrect slope change recorded for `_newLocked.end` could lead to calculations based on incorrect voting power and locked balances, potentially causing discrepancies, unintended behavior, and assert violations when the contract later uses these slope changes for various calculations.

## Tools Used
Vscode

## Recommended Mi
Upgraded Q -> 3 from #308 [1709906778639]
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

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

 - [L03] Addresses can be pushed into holders even if balanceOf(addr) is zero. For example, Alice sends 0 LiquidInfrastructureERC20 Token to Bob. Although Bob owns 0 Token, Bob addr can still be added in **holders**.

```solidity
    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);
        }
    }
```
Upgraded Q -> 3 from #303 [1709905914484]
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

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

 ### [L-01] The burn address will be part of the holders array

When burning tokens, the `to` address will be address(0). This `to` address will be appended to the `holders` array during `burn()` as `_beforeTokenTransfer()` is invoked, which is

```
        if (from == address(0) || to == address(0)) {
            _beforeMintOrBurn();
        }
        bool exists = (this.balanceOf(to) != 0);
        if (!exists) {
>           holders.push(to);
        }
```

Have a clause that disallows the burn address to be part of the holders array.

https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L138-L145
Upgraded Q -> 3 from #575 [1709558704768]
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

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

 Summary:
everytime the approved users burn their tokens, the zero address will be pushed to the holders array.

Detail:
inside the `_beforeTokenTransfer` function:

```js
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();
        }
        // @note: Alice and users that Alice approved can burn Alice's tokens to push address(0) to `holders`
        bool exists = (this.balanceOf(to) != 0);
        if (!exists) {
            holders.push(to);
        }
    }
```

as you see if approved users burn their tokens, the zero address will be pushed to the `holders` array cause the zero address's balance is always 0.
this is because, in the ERC20.sol in the `_burn` function, the function reduces the balance of the `from` but doesn't increase the balance of the zero address.
so It will cause to `exists` variable to always be true if `to` is a zero address.


Impact:
Holders should be actual holders not a bunch of zero addresses. this is just breaking the functionality of the protocol.

Recommendation:

add another condition in the if statement to don't push zero addresses:
```diff

//existing codes...

+    if(!exists && to != address(0))

//existing codes...
```
`LiquidInfrastructureERC20.sol::burnFromAndDistribute()` - L306: Tether (USDT)'s `approve()` function will revert if the current approval is not zero, to protect against front-running changes of approvals.
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

I checked and there doesn't seem to be any implementation that currently handles this issue with `USDT`, and this is one of the 3 main tokens being used in the protocol besides `DAI` and `USDC`.
Therefore any attempts to burn USDT tokens will be reverted.

Affected method:
```solidity
burnFrom(account, amount);
```
Can be seen from the `ERC20Burnable.sol` & `ERC20.sol` that `USDT`'s special case is not handled at all:
```solidity
    function burnFrom(address account, uint256 amount) public virtual {
        uint256 currentAllowance = allowance(account, _msgSender());
        require(currentAllowance >= amount, "ERC20: burn amount exceeds allowance");
        unchecked {
            _approve(account, _msgSender(), currentAllowance - amount);
        }
        _burn(account, amount);
    }
```
and:
```solidity
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }
```

Recommendation:

- Set the allowance to zero before increasing the allowance and use safeApprove/safeIncreaseAllowance.
- use the latest OZ libraries.



## Assessed type

ERC20
Malicious NFT account actors can still withdraw the contracts ERC20 balances after transferring ownership of the NFT to the LiquidInfrastructureERC20
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/LiquidInfrastructureNFT.sol#L158


# Vulnerability details

## Impact
Malicious NFT account actors can still withdraw the contracts ERC20 balances after transferring ownership of the NFT to the LiquidInfrastructureERC20

## Proof of Concept
The protocol enforces that contracts `must own` the `nftContract` to withdraw the contracts ERC20 balances  

> *this contract must already be the owner of the `nftContract`*

This invariant is also checked in the `addManagedNFT()`:
```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"
        ); @note
        ManagedNFTs.push(nftContract);
        emit AddManagedNFT(nftContract);
    }
```
Notice it doesn't accept through approvals but only ownership. However, in the LiquidInfrastructureNFT.sol contract, the withdrawBalancesTo() function would still execute if the caller doesn't own the NFT but still approved:
```solidity
function withdrawBalancesTo(
        address[] calldata erc20s,
        address destination
    ) public virtual {
        require(
@>          _isApprovedOrOwner(_msgSender(), AccountId),
            "caller is not the owner of the Account token and is not approved either"
        );
        _withdrawBalancesTo(erc20s, destination);
    }
```
Now, let's consider this scenario
- Bob which is a network provider wants to join Alice network, so he transfers his LiquidInfrastructureNFT to Alice
- But right before he transfers it, he approves himself
- Now bob's network provide nft is part of the network and owned by Alice but bob is still able to withdraw every revenue token balance in the nft contract
## Tools Used

## Recommended Mitigation Steps
Consider changing `withdrawBalancesTo` to only allow the owner of the NFT contract to withdraw the contracts ERC20 balances


## Assessed type

Access Control
Potential DoS in minting
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#L164
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L142
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L144


# Vulnerability details

## Impact
There is a potential DoS attack in the `_afterTokenTransfer` function which will make the protocol break and the owner can't mint tokens again.
As we know
   "*When a function call gets a gas error The entire transaction, including any changes made by previous function calls within the transaction, is reverted. This means that all state changes made up to that point are undone*"


## Proof of Concept
in this snippet code in the `_beforeTokenTransfer` function:
``` js
//existing codes...

// @note: Alice and users that Alice approved can burn Alice's tokens to push address(0) to `holders`
 @>       bool exists = (this.balanceOf(to) != 0);
        // @audit-High : add this condition to prevent DOS attack in _afterTokenTransfer : if(!exists && to != address(0))
        if (!exists) {
            holders.push(to);
        }
```
every time a user wants to burn its tokens, the zero address will be pushed to holders because the `exists` becomes false because the balance of zero address for the token is 0 thanks to the `_burn` function which decreased the balance of the `from` but not increased the balance of zero address.

***Here is the step-by-step scenario of how a malicious user can make a DoS attack***:

1. after the deployment, the owner mints tokens for some users (for example 10,000).

2. a malicious one burns its tokens one by one, unit by unit to push more zero addresses to the holders array. **He can also do that with an approved malicious contract by the user to keep calling the `burnFrom(_maliciousUser, 1)` function**. this is possible because the balance of zero address is always will be 0 and it will enter the if statement:
```js
// `exists` is equal to false
if(!exists){
    holders.push(to);
}
```

3. we might see 10,000 zero addresses in holders that aren't the actual holders. however, lots of users might burn their tokens, and for everyone who burns tokens, a zero address will always be pushed so 10,000 is just a small number.

4. in the `_afterTokenTransfer` function :
```js
function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual override {
@>        bool stillHolding = (this.balanceOf(from) != 0);
        if (!stillHolding) {
            for (uint i = 0; i < holders.length; i++) {
@>                if (holders[i] == from) {
                    // Remove the element at i by copying the last
Anyone can call burnandDistribute
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#L331


# Vulnerability details

## Impact
Anyone is able to call burnAndDistribute and burn any amount without validation

## Proof of Concept
Reference Links to affected code

## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement Access Controls and use onlyOwner or the appropriate modifier


## Assessed type

Access Control
Calculation of entitlement is incorrect
Audit: 2024-02-althea-liquid-infrastructure-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The manner in which entitlement is calculated and then sent over to the holders is incorrect.

## Proof of Concept
entitlement is calculated as `balance / supply`, where balance is `IERC20(distributableERC20s[i]).balanceOf(address(this));` and supply is `this.totalSupply();`. This entitlement is multiplied by the LiquidInfrastructureERC20 balance of the holder and then distributed the respective distributableERC20s to the holder.

Calculating entitlement in this manner is incorrect as more distributableERC20s tokens will be sent to a holder than intended when `IERC20(distributableERC20s[i]).balanceOf(address(this))` is much higher than `this.totalSupply()`. If it's much higher, then the calculated entitlement will no longer represent the fraction of the pool that it is meant to represent. This might not leave enough tokens in the contract for the other holders and cause the transfers of distributableERC20s to revert due to insufficient balance in the contract when it tries to distribute tokens to other holders.

Rewards/entitlement for a holder should be calculated based on the fraction of their balance of the totalSupply. The ratio of `balance / supply` to calculate the `entitlement` does not represent the accurate share of the user. Instead, the share or the value to be transferred to the holder should be calculated as follows:

```
this.balanceOf(recipient) * IERC20(distributableERC20s[i]).balanceOf(address(this)) / this.totalSupply()
```

This accurately represents a user's share of the pool and how many tokens they need to be distributed. 

## Tools Used
Manual review

## Recommended Mitigation Steps
Perform the calculation as shown in the code snippet above.



## Assessed type

Other