xanoutas/solidity-security-findings huggingface dataset:
Protocol doesn't account for token decimals in entitlement calculation 
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


# Vulnerability details

## Impact
Total loss of revenue for tokens with decimals less than 1e18 such as USDC/USDT which the protocol has identified that Liquid Infrastructure is expected to use ERC20 stablecoins like USDC, Dai, or USDT as revenue tokens.

## Proof of Concept
In `_beginDistribution()`
`entitlement` is calculated by the division of the `balance` the protocol owns of that revenue token by the total `supply` of LiquidErc20
```solidity
File: contracts/LiquidInfrastructureERC20.sol

275:             uint256 entitlement = balance / supply;
```
the issue is that the LiquidErc20 inherits from the standard openzeppelin ERC20 contract and therefore has a decimal of 1e18, meaning that `supply`, the divisor, is in `18` decimals places.
```solidity
File: contracts/LiquidInfrastructureERC20.sol

270:             uint256 supply = this.totalSupply();
```
However, the `balance` value is in the decimal place of the revenue token, and for USDC that would be `6`
```solidity
File: contracts/LiquidInfrastructureERC20.sol

272:             uint256 balance = IERC20(distributableERC20s[i]).balanceOf(
273:            address(this)
274:         );
```
That means the total entitlement calculated would be `0`

## Recommended Mitigation Steps
consider adding this change:
```diff
File: contracts/LiquidInfrastructureERC20.sol

+ 275:            uint256 entitlement = (balance * 1e18) / supply;
- 275:            uint256 entitlement = balance / supply;
```





## Assessed type

Decimal
An approved holder can proxy the rewards for a disapproved holder
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#L394-L403


# Vulnerability details

## Impact

`LiquidInfrastructureERC20.sol` allows the contract owner to block a token holder via `disapproveHolder`. Further, a disapproved holder cannot receive infrastructure tokens. This is enforced in [_beforeTokenTransfer](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L133-L138). However, this method does not disallow a blocked user to transfer their infrastructure tokens to another holder. The other holder will be collecting the rewards on the behalf of the blocked user. Since the contract owner does not have control over the rewards token (such as USDC), the unblocked holder can simply act as a rewards proxy, perhaps, collecting a fee for doing that.

As a result, `disapproveHolder` is a feature that is too easy to circumvent.

## Proof of Concept

The test `test/poc2.ts` demonstrates that:
 - a disapproved holder `holder2` may transfer tokens to `holder3`,
 - `holder3` receives the rewards on behalf of `holder2`,
 - `holder3` transfers the rewards back to `holder2.`

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;

const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";

// This test makes assertions about the LiquidInfrastructureERC20 contract by running it on hardhat
//
// Important test details:
// Contract interactions happen via hardhat-ethers: https://hardhat.org/hardhat-runner/plugins/nomiclabs-hardhat-ethers
// Chai is used to make assertions https://www.chaijs.com/api/bdd/
// Ethereum-waffle is used to extend chai and add ethereum matchers: https://ethereum-waffle.readthedocs.io/en/latest/matchers.html
async function liquidErc20Fixture() {
  const signers = await ethers.getSigners();
  const nftAccount1 = signers[0];
  const nftAccount2 = signers[1];
  const nftAccount3 = signers[2];
  const erc20Owner = signers[3];
  const holder1 = signers[4];
  const holder2 = signers[5];
  const holder3 = signers[6];
  const holder4 = signers[7];
  const badSigner = signers[8];

  // Deploy several ERC20 tokens to use as revenue currencies
  //////////////////
  const { testERC20A, testERC20B, testERC20C
MintingAndDistribution function can create monetary loss for new tokens holder
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#L303-L311


# Vulnerability details

The [mintAndDistribute function](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L303-L311) is not properly working, is first distributing the rewards to token holders and after that is minting new tokens not sending any rewards in the current ditributionPeriod to the new token holder. This may represent a monetary loss to him as the new tokens collection by LiquidInfrastructureERC20 might be completely different and rewards might be significantly smaller then what the new holder have initially expected.

## Impact
The fact that the new holder token is not taking any profit from the current contract state but only from the future contract state can create drastic monetary losses for him as it might be different from what he have initially thought.

## Proof of Concept
1. Alice is doing an OTC deal and the Admin wants to mint her shares.
2. The admin is using the [mintAndDistribute function](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L303-L311)  to first distribute all the rewards to the current holders and the is minting new shares for Alice.
3. Alice will start receiving rewards only starting from the next distribution period and the rewards can be much smaller than what she initially saw in the contract or they can be completely other tokens

## Tools Used
Manual Review

## Recommended Mitigation Steps
When using the mintAndDistribute function first mint new share and after that distribute the tokens.


## Assessed type

Rug-Pull
Precision loss error in distribution logic could lead to locked assets
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
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L222


# Vulnerability details

## Impact

The main impact is that assets will not be distributed as long as the total supply of the Liquid Infrastructure ERC20 is greater than the balance of the asset token, which will be the case most of the time. As such assets will remain in the protocol, and even if the total supply is less than the asset token, the lack of precision could cause assets to still remain locked in the protocol.


## Proof of Concept

Consider an excerpt from the [_beginDistribution()](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L275) which is called at the beginning of the [distribute()](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L205) function

```
for (uint i = 0; i < distributableERC20s.length; i++) {
            uint256 balance = IERC20(distributableERC20s[i]).balanceOf(
                address(this)
            );
            uint256 entitlement = balance / supply;
            erc20EntitlementPerUnit.push(entitlement);
        }
```

As we can see, the line

```
uint256 entitlement = balance / supply;
```

is the cause of the precision loss error, that is if supply is greater than the balance of the token then entitlement equates to zero. As such, consider the later loop in the distribution function.

```
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;
                    }
                }
```

Lets further analysis the line

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

Since the entitlement per unit is equal to zero the user will not get any rewards, and if they do most of the time an excess will be left locked in the protocol.

To prove further consider

supply = 8
balance = 12
balance of recipient(share) = 2

According to solidity protocol calculation user receives

12/8 = 1
1 * 2 = 2

According to no loss precision Arithmetic 

12/8 = 1.5
1.5 * 2 = 3

User looses a portion of their rewards

## Tools Used

Manual Review

## Recommended Mitigation Steps

Due to the lack of floating point arithmetic in solidity
Division before multiplication can reduce the awards that are sent to the LiquidInfrastructureERC20 token holders.
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
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/bd6ee47162368e1999a0a5b8b17b701347cf9a7d/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L222-L225


# Vulnerability details

## Impact
Division before multiplication can reduce the awards that are sent to the LiquidInfrastructureERC20 token holders. This happens while calculating the erc20EntitlementPerUnit values. 

## Proof of Concept
erc20EntitlementPerUnit is calculated as:
```
uint256 entitlement = balance / supply;
erc20EntitlementPerUnit.push(entitlement);
```

This calculated entitlement is then multiplied by the balance of the token holder:

```
uint256 entitlement = erc20EntitlementPerUnit[j] * this.balanceOf(recipient);
if (toDistribute.transfer(recipient, entitlement)) {
   receipts[j] = entitlement;
}
```

Division before multiplication in this manner (as Solidity rounds the value of the division to the nearest smaller integer) is bound to decrease the number of tokens to which the holder is entitled.

## Tools Used
Manual review

## Recommended Mitigation Steps
Perform the multiplication before division.

```
balance * this.balanceOf(recipient) / supply;
```



## Assessed type

Other
OwnableUpgradeable` uses single-step ownership transfer
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#L5


# Vulnerability details

## Impact
Single-step ownership transfer means that if a wrong address was passed when transferring ownership or admin rights it can mean that role is lost forever. The ownership pattern implementation for the protocol is in Ownable.sol where a single-step transfer is implemented.This can be a problem for all methods marked in onlyOwner throughout the protocol, some of which are core protocol functionality.


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


## Tools Used

## Recommended Mitigation Steps

It is a best practice to use two-step ownership transfer pattern, meaning ownership transfer gets to a "pending" state and the new owner should claim his new rights, otherwise the old owner still has control of the contract. Consider using OpenZeppelin's Ownable2Step contract




## Assessed type

Library
A portion of rewards might get allocated to disapproved wallets which renders reward distribution unfair
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#L216
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L116-L119
https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L275


# Vulnerability details

## Vulnerability details
When calculating rewards, [balance of each `distributableERC20` is divided by `supply` to calculate `entitlement`](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L272-L275) (amount of `distributableERC20` per `infraERC20` share), however, [`totalSupply()` is used in this calculation](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L270) which also includes amount of tokens that are owned by `disapproved` wallets (assuming that their balance > 0 which means that they were approved before).
The issue arises from the fact that we are [excluding disapproved wallets from receiving rewards](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L216) which means that the tokens owned by this wallets should not be involved in [calculating rewards ratio at the first place](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L275-L276).

## Impact
- Less rewards will be distributed to `approved` holders, a portion of rewards will be allocated to `disapproved` holders which means that a portion of `distributableERC20` tokens will be locked in the contract forever.
- The impact of this issue could be anywhere from `low` to `high` depending on total number of tokens that are allocated to disapproved holders

## Proof of Concept
- 1- Owner [approves `Alice` and `Bob`](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L106-L109)
- 2- Owner mints 100 tokens to `Alice` and 100 tokens to `BOB`
- 3- After a while owner finds out that `Alice` KYC is fake, [hence owner will disapprove Alice account](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-infrastructure/contracts/LiquidInfrastructureERC20.sol#L116-L119), but `Alice` still holds 100 tokens, the point is that she is not able to use her tokens anymore (expect transferring them to another approved address), so they are of no use to her and she forgets about those tokens.
- 4- Owner withdraws `USDC` tokens from `ManagedNFTs` by calling [withdrawFromManagedNFTs](https://github.com/code-423n4/2024-02-althea-liquid-infrastructure/blob/main/liquid-inf
Some distributableERC20 tokens could be locked in the LiquidInfrastructureERC20 token contract.
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


# Vulnerability details

## Impact
Per a distributableERC20 token, the entitlement is calculated by dividing the distributableERC20 token balance of the LiquidInfrastructureERC20 token address dividing by the LiquidInfrastructureERC20 token supply.
In this case, there can be loss of precision because the entitlement can't be float.
And in the `distribute()` function, the entitlement amount to be transferred is calculated by multiplying the erc20EntitlementPerUnit by the recipient's balance.
Therefore, there will be some distributableERC20 tokens left in the LiquidInfrastructureERC20 token address and they will be locked.


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

## Tools Used
Manual Review

## Recommended Mitigation Steps
Recommend adding a function to withdraw the locked tokens.


## Assessed type

Other
Block Number Dependency:
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#L243


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
Block Number Dependency: The contract uses block numbers to calculate periods, which can be imprecise due to fluctuating block times.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
 function _isPastMinDistributionPeriod() internal view returns (bool) {
        // Do not force a distribution with no holders or supply
        if (totalSupply() == 0 || holders.length == 0) {
            return false;
        }

       @> return (block.number - LastDistribution) >= MinDistributionPeriod;
    }

## Tools Used
Manual analysis
## Recommended Mitigation Steps


## Assessed type

Context
Reward inflation is incorrectly calculated 
Audit: 2024-03-acala-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-acala/blob/main/src/orml/rewards/src/lib.rs#L158


# Vulnerability details

## Impact

Inside of the rewards pallet, there is a mistake in the reward inflation calculation that makes

## Proof of Concept

Reward inflation is calculated in the rewards pallet every time there is a deposit and somebody adds shares (and if the total shares are not equal to 0).

First, the owner of the owner of the `incentives` pallet has to call `update_incentive_rewards()` and assign some tokens and reward amounts that will be distributed periodically when `on_initialize()` is called.

Let's take some pool as an example:

1. The pool will be `DOT` pool and the rewards will be `NATIVE_COIN`. So initially `total_reward` is set to 0 and `total_withdrawn_reward` is set to 0.

2. The owner will call `update_incentive_rewards()` and set the rewards to 500 so that 500 tokens will be accumulated as incentives every now and then.

3. User1 deposits 100 shares to the pool so now the pool has the following state:

```
initial_total_shares = 0
total_shares = 100
withdrawn_inflation = [NATIVE_COIN, (0,0)]
reward_inflation = 0
total_reward = 0
share = 100
withdrawn_reward = 0 (as the reward_inflation = 0)
```

4. So now `on_initialize()` can happen and the `incentives` pallet can ``accumulate_incentives` as the total shares no more equal to 0. `Rewards` pallet, in its turn, increase the `total_reward` value by this number.

5. Now user2 deposits 200 shares. And the pool now has the following state:

```
initial_total_shares = 100
total_shares = 300
withdrawn_inflation = [NATIVE_COIN, (0,0)]
reward_inflation = 200 * 500 (total reward) / 100 = 1000
total_reward = 500 + 
share = 200
withdrawn_reward =  1000
```

So user2 has the `withdraw_reward` equal to 1000 when the `incentives` contract has only 500 tokens to claim. Therefore, the calculation inflates the `withdrawn_reward` variable making the rewards for the user too huge so that the `incentives` pallet has no tokens to cover these amounts.

## Tools Used

Manual review.

## Recommended Mitigation Steps

Change the way `reward_inflation` is calculated.


## Assessed type

Other
Invariant break of transfer share and rewards because of rounding down
Audit: 2024-03-acala-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/orml/rewards/src/lib.rs#L326-L361


# Vulnerability details

## Impact
Accounts can use the function [transfer_share_and_rewards](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/orml/rewards/src/lib.rs#L326), to transfer share and the underlying rewards for that amount of shares from one account to another, the formula used inside it can be reduce to [ (TOTAL_REWARDS * shares_to_transfer) / TOTAL_SHARES )](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/orml/rewards/src/lib.rs#L341-L343) however if TOTAL_SHARES is bigger then (TOTAL_REWARDS * shares_to_transfer ) OR vice-versa then the result is rounded down to 0 and the transfer of shares will happen but there will be no transfer of underlying rewards so the amount of shares that the sender have will change but the amount of rewards will not and in the balance of the receiver he will receive shares but no underlying rewards.

## Proof of Concept
We wrote a unit test, to run the unit test use the following command
```console
cargo test --release  --package orml-rewards --lib -- tests::test_transfer_share_and_rewards_finding_rounding_down_in_rewards_formula_shares_gets_transfered_and_rewards_not
```
The unit test:
```rust
fn test_transfer_share_and_rewards_finding_rounding_down_in_rewards_formula_shares_gets_transfered_and_rewards_not(){
	ExtBuilder::default().build().execute_with(|| {
		RewardsModule::add_share(&ALICE, &DOT_POOL, 100);
		assert_eq!(
			RewardsModule::pool_infos(DOT_POOL),
			PoolInfo {
				total_shares: 100,
				rewards: Default::default()
			}
		);

		assert_ok!(RewardsModule::accumulate_reward(&DOT_POOL, NATIVE_COIN, 100));
		assert_eq!(
			RewardsModule::pool_infos(DOT_POOL),
			PoolInfo {
				total_shares: 100,
				rewards: vec![(NATIVE_COIN, (100, 0))].into_iter().collect()
			}
		);

		RewardsModule::add_share(&BOB, &DOT_POOL, 100);

		assert_eq!(
			RewardsModule::pool_infos(DOT_POOL),
			PoolInfo {
				total_shares: 200,
				rewards: vec![(NATIVE_COIN, (200, 100))].into_iter().collect()
			}
		);

		assert_ok!(RewardsModule::transfer_share_and_rewards(&ALICE, &DOT_POOL, 33, &BOB));

		assert_eq!(
			RewardsModule::shares_and_withdrawn_rewards(DOT_POOL, ALICE),
			(67, Default::default())
		);

		assert_eq!(
			RewardsModule::shares_and_withdrawn_rewards(DOT_POOL, BOB),
			(133, vec![(NATIVE_COIN, 100)].into_iter().collect())
		);

		assert_ok!(RewardsModule::transfer_share_and_rewards(&BOB, &DOT_POOL, 1, &CAROL));
		assert_eq!(
			RewardsModule::shares_and_withdrawn_rewards(DOT_POOL, CAROL),
			(1, vec![(NATIVE_COIN, 100 * 1 / 133)].into_iter().collect())
		);

		assert_eq!(
			RewardsModule::shares_and_withdrawn_rewards(DOT_POOL, BOB),
			(132, vec![(NATIVE_COIN, 100)].into_iter().collect())
		);

		assert_eq!(
			RewardsModule::shares_and_withdrawn_rewards(DOT_POOL, CARO