xanoutas/solidity-security-findings huggingface dataset:
Inaccurate total shares due to overflow or underflow, affecting reward distribution.
Audit: 2024-03-acala-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

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

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

## Impact
Overallocation or underallocation of rewards to users based on inflated or deflated share amounts.
 - Inconsistencies in the total shares value, making it difficult to accurately calculate and distribute rewards.

## Function Description
The `add_share` and `remove_share` functions in the Rewards pallet are responsible for updating the total shares of a pool when adding or removing shares for a specific user.

The `add_share` function increases the total shares of the pool by the `add_amount` provided:

```rust
// In add_share
pool_info.total_shares = pool_info.total_shares.saturating_add(add_amount);
```

Similarly, the `remove_share` function decreases the total shares of the pool by the `remove_amount`:

```rust
// In remove_share
pool_info.total_shares = pool_info.total_shares.saturating_sub(remove_amount);
```
Both functions use the `saturating_add` and `saturating_sub` methods respectively to perform the share calculations while avoiding integer overflow.

## Vulnerability Details
Saturating math ensures that the result of an arithmetic operation is within the valid range of the data type, preventing integer overflow. However, it may lead to unexpected behavior when dealing with very large share amounts.

> For example, consider a scenario where the total shares of a pool are close to the maximum value representable by the underlying data type. _If a user adds a significant amount of shares that would cause an overflow, the `saturating_add` method will cap the result at the maximum value instead of wrapping around._ This can lead to share inflation, as the total shares may not accurately reflect the actual sum of individual shares.

`Similarly, in the case of `remove_share`, if the `remove_amount` is greater than the current total shares, the `saturating_sub` method will clamp the result to zero. This can lead to share deflation, as the total shares may become zero even if there are still individual shares in the pool`.

> Due to the use of saturating math, the actual behavior of the `add_share` and `remove_share` functions may deviate from the expected behavior in extreme edge cases:

- In the case of `add_share`, if the `add_amount` is very large and would cause an overflow, the total shares will be capped at the maximum representable value, leading to share inflation.
- In the case of `remove_share`, if the `remove_amount` is greater than the current total shares, the total shares will
Unvalidated input allows an attacker to gain disproportionate rewards unfairly.
Audit: 2024-03-acala-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L515
https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L512-L523


# Vulnerability details

Due to insufficient validation of the `amount` parameter in the `deposit_dex_share` function.
   - The function directly uses the `amount` provided by the user to calculate and update the user's shares via the `add_share` function.
>  - However, there is no validation to ensure that the `amount` is within a reasonable range or matches the user's actual token balance.
   - An attacker can potentially exploit this vulnerability by providing a large `amount` value, even if they don't possess the corresponding number of tokens.
   - [Line 515](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L515)
     ```rust
      <orml_rewards::Pallet<T>>::add_share(who, &PoolId::Dex(lp_currency_id), amount.unique_saturated_into());
     ```
   - This line directly passes the user-provided `amount` to the `add_share` function without proper validation.

## Issue Description
The purpose of this is to allow users to stake their tokens and receive shares in the DEX pool.
   - The function takes the following inputs:
     - `who`: The account ID of the user staking the tokens.
     - `lp_currency_id`: The ID of the LP token being staked.
     - `amount`: The amount of tokens being staked.
   - The function performs the following key steps:
     1. Transfers the staked tokens from the user's account to the Incentives module's account.
     2. Calls the `add_share` function from the Rewards module to calculate and update the user's shares based on the staked amount.
     3. Emits a `DepositDexShare` event to record the staking activity.

   - [Code snippet](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L512-L523):
```rust
ensure!(lp_currency_id.is_dex_share_currency_id(), Error::<T>::InvalidCurrencyId);


T::Currency::transfer(lp_currency_id, who, &Self::account_id(), amount)?;
<orml_rewards::Pallet<T>>::add_share(who, &PoolId::Dex(lp_currency_id), amount.unique_saturated_into());


Self::deposit_event(Event::DepositDexShare {
    who: who.clone(),
    dex_share_type: lp_currency_id,
    deposit: amount,
});
Ok(())
}
```

   - Should only accept valid `amount` values that correspond to the user's actual token balance.
   - It should enforce proper validation and checks to ensure that the staked `amount` is within a reasonable range and does not exceed the user's available tokens.


   - An attacker can potentially provide a large `amount` value to the `deposit_dex_share` function, even if they don't have sufficient tokens.
   - The function will proceed to calculate and update the user's shares based on the manipulated
Lack of detailed event data hinders auditing and transparency efforts.
Audit: 2024-03-acala-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L416
https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L320-L348
https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L355-L369


# Vulnerability details

When a user claims their rewards using the claim_rewards function, the ClaimRewards event is emitted, but it does not include information about the specific reward amounts claimed for each currency.
   - Similarly, when the deduction rates are updated using the `update_claim_reward_deduction_rates` function, the `ClaimRewardDeductionRateUpdated` event is emitted, but it does not include the actual deduction rate values.
   - The absence of detailed event data can make it difficult for users and auditors to track and verify the claimed reward amounts and the updated deduction rates.
   - This lack of transparency can hinder the ability to audit the pallet's operations effectively and may lead to confusion or mistrust among users.

## Issue Description
The Incentives pallet emits events in several functions, including:
     - claim_rewards: Emits the ClaimRewards event when a user claims their rewards.
     - update_claim_reward_deduction_rates: Emits the ClaimRewardDeductionRateUpdated event when the deduction rates are updated.
     - update_claim_reward_deduction_currency: Emits the ClaimRewardDeductionCurrencyUpdated event when the deduction currency is updated.
   - These functions are responsible for performing specific actions and emitting corresponding events to inform users and observers about the occurrence of these actions.

   - Code snippets: [Here](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L416), [Here](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L320-L348), and [here](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L355-L369)
     ```rust
     // In the claim_rewards function
     fn do_claim_rewards(who: T::AccountId, pool_id: PoolId) -> DispatchResult {
         // ... (claim rewards logic)
         Self::deposit_event(Event::ClaimRewards {
             who: who.clone(),
             pool: pool_id,
         });
         Ok(())
     }

     // In the update_claim_reward_deduction_rates function
     pub fn update_claim_reward_deduction_rates(origin: OriginFor<T>, updates: Vec<(PoolId, Rate)>) -> DispatchResult {
         // ... (update logic)
         Self::deposit_event(Event::ClaimRewardDeductionRateUpdated {
             pool: pool_id,
             deduction_rate,
         });
         // ... (remaining logic)
         Ok(())
     }

     // In the update_claim_re
Users tokens set indefinitely in the `UnbondingPeriod` could potentially disrupt the protocol's and introduce vulnerabilities
Audit: 2024-03-acala-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/earning/src/lib.rs#L158


# Vulnerability details

## Impact
Allowing users to indefinitely hold their tokens in the unstaking status without appropriate mechanisms or restrictions can have detrimental effects on the protocol's security, economic stability, and overall functionality.

**Economic Disturbance:** Tokens held in the unstaking status do not participate in staking and therefore do not contribute to the network's security or governance. This could disrupt the economic balance of the protocol by reducing the effective staked amount.

**Vulnerability to Attacks:** Large token holders could abuse this feature to strategically withdraw their tokens from staking, reducing the security of the network and potentially enabling attacks such as 51% attacks or governance manipulation.

**Inefficiency in Resource Allocation:** Tokens stuck in the unstaking status tie up resources that could otherwise be utilized for staking, liquidity provision, or other productive activities within the ecosystem, leading to inefficiencies in resource allocation.

**Market Manipulation:** Bad actors could exploit the ability to hold tokens in the unstaking status to manipulate token prices or create artificial scarcity, potentially harming other users and disrupting the stability of the token's market.


## Affected code
https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/earning/src/lib.rs#L158  
https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/primitives/src/bonding/controller.rs#L122


## Proof of Concept
From the doc:
https://guide.acalaapps.wiki/staking/aca-staking#unstaking

"Restaking - should you want to put the ACA back to staking status, you can use restake anytime to restake any amount that is currently unstaking, the ACA will be in staked status and start earning yield immediately."

The `unbond` function allows users to unstake their ACA tokens for free, in a process that takes 28 days.
Once this process starts, users won't earn any yield and the tokens will follow the `UnbondingPeriod` blocks.

```rust
		/// Start unbonding tokens up to `amount`.
		/// If bonded amount is less than `amount`, then all the remaining bonded tokens will start
		/// unbonding. Token will finish unbonding after `UnbondingPeriod` blocks.
		#[pallet::call_index(1)]
		#[pallet::weight(T::WeightInfo::unbond())]
		pub fn unbond(origin: OriginFor<T>, #[pallet::compact] amount: Balance) -> DispatchResult {
			let who = ensure_signed(origin)?;

			let unbond_at = frame_system::Pallet::<T>::block_number().saturating_add(T::UnbondingPeriod::get());
			let change = <Self as BondingController>::unbond(&who, amount, unbond_at)?;

			if let Some(change) = change {
				T::OnUnbonded::happened(&(who.clone(), change.change));
				Self::deposit_event(Event::Unbonded {
					who,
UpdateOrigin can set extreme deduction rates, potentially eliminating user rewards.
Audit: 2024-03-acala-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L332
https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L318-L348


# Vulnerability details

The function accepts any Rate value for the deduction_rate without checking if it falls within a reasonable range.
   - This allows `UpdateOrigin` to set extremely high or low deduction rates, potentially leading to excessive deductions or even complete elimination of rewards.
   - [#Line 332](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L332)
     ```rust
     v.try_set(deduction_rate).map_err(|_| Error::<T>::InvalidRate)?;
     ```
   - The try_set function attempts to set the new deduction rate without any range validation.


## Issue Description
The purpose of this function is to allow the UpdateOrigin to update the claim reward deduction rates for each pool.
   - The function takes a vector of tuples as input, where each tuple contains the pool ID and the new deduction rate for that pool.
   - It updates the `ClaimRewardDeductionRates` storage item with the new deduction rates for the specified pools.

   - [Code snippet](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/modules/incentives/src/lib.rs#L318-L348):
```rust
     #[pallet::call]
     impl<T: Config> Pallet<T> {
#[pallet::call_index(4)]
#[pallet::weight(<T as Config>::WeightInfo::update_claim_reward_deduction_rates(updates.len() as u32))]
pub fn update_claim_reward_deduction_rates(
    origin: OriginFor<T>,
    updates: Vec<(PoolId, Rate)>,
) -> DispatchResult {
    T::UpdateOrigin::ensure_origin(origin)?;
    for (pool_id, deduction_rate) in updates {
        if let PoolId::Dex(currency_id) = pool_id {
            ensure!(currency_id.is_dex_share_currency_id(), Error::<T>::InvalidPoolId);
        }
        ClaimRewardDeductionRates::<T>::mutate_exists(pool_id, |maybe_rate| -> DispatchResult {
            let mut v = maybe_rate.unwrap_or_default();
            if deduction_rate != *v.inner() {
        @>      v.try_set(deduction_rate).map_err(|_| Error::<T>::InvalidRate)?;
                Self::deposit_event(Event::ClaimRewardDeductionRateUpdated {
                    pool: pool_id,
                    deduction_rate,
                });
            }


            if v.inner().is_zero() {
                *maybe_rate = None;
            } else {
                *maybe_rate = Some(v);
            }
            Ok(())
        })?;
    }
    Ok(())
}
```
   - The deduction rates should be within a reasonable range, such as between 0 and 1, to ensure fair and proportional deductions. The function should validate the input deduction rates and reject any values that fall outside the acceptable range.

>  - Due to the lack of proper validation, the update_claim_rewa
Inaccurate reward distribution due to potential overflow in reward inflation calculation.
Audit: 2024-03-acala-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/orml/rewards/src/lib.rs#L161-L166
https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/orml/rewards/src/lib.rs#L143-L189


# Vulnerability details

Calculation of `reward_inflation` within the `add_share()` function involves multiplying the user's added shares (`add_amount`) with the pool's total reward (`total_reward`) and then dividing by the initial total shares in the pool (`initial_total_shares`).
   - When the multiplication result exceeds the maximum representable value of the underlying data type (e.g., `u128`), leading to an overflow.
   - The overflow can occur if the `add_amount` or `total_reward` values are extremely large, and the `initial_total_shares` value is relatively small.
   - [lib.rs#L161-L166](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/orml/rewards/src/lib.rs#L161-L166)
     ```rust
     U256::from(add_amount.to_owned().saturated_into::<u128>())
         .saturating_mul(total_reward.to_owned().saturated_into::<u128>().into())
         .checked_div(initial_total_shares.to_owned().saturated_into::<u128>().into())
         .unwrap_or_default()
         .as_u128()
         .saturated_into()
     ```

## Issue Description
The purpose of `add_share()` is to calculate the reward inflation based on the user's added shares and update the pool's total shares and rewards accordingly.
   - The function takes the following inputs:
     - `who`: The account ID of the user adding shares
     - `pool`: The ID of the pool to which shares are being added
     - `add_amount`: The amount of shares being added
   - _Key steps:_
     1. Calculate the initial total shares in the pool.
     2. Update the pool's total shares by adding the `add_amount`.
     3. Calculate the reward inflation for each reward currency based on the `add_amount` and the pool's total reward.
     4. Update the pool's total reward and total withdrawn reward for each currency.
     5. Update the user's shares and withdrawn rewards in the pool.

   - [lib.rs#add_share](https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/orml/rewards/src/lib.rs#L143-L189)
     ```rust
     fn add_share(who: &T::AccountId, pool: &T::PoolId, add_amount: T::Share) {
         PoolInfos::<T>::mutate(pool, |pool_info| {
             let initial_total_shares = pool_info.total_shares;
             pool_info.total_shares = pool_info.total_shares.saturating_add(add_amount);

             pool_info.rewards.iter_mut().for_each(|(reward_currency, (total_reward, total_withdrawn_reward))| {
                 let reward_inflation = if initial_total_shares.is_zero() {
                     Zero::zero()
                 } else {
                     U256::from(add_amount.to_owned().saturated_into::<u128>())
                         .saturating_mul(total_reward.to_owned()
Unnecessary iteration increases gas costs and decreases performance efficiency.
Audit: 2024-03-acala-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

There is a potential performance inefficiency when updating withdrawn rewards for reward currencies in which the user has no balance.
   - The function currently iterates through all reward currencies, even if the user has no withdrawn or pending rewards in some of those currencies.
   - This unnecessary iteration could lead to suboptimal performance, especially if there are many reward currencies and users with sparse reward balances.

## Impact
   - The performance inefficiency in the `remove_share()` function does not directly impact the functionality or security of the system.
   - However, it may lead to suboptimal performance and increased gas costs, especially in scenarios with many reward currencies and users with sparse reward balances.
   - This could potentially affect the overall efficiency and scalability of the system.

## Issue Description
   - The `remove_share()` function is called when a user wants to remove a portion of their staked shares from a pool.
   - It updates the user's share balance, the pool's total shares, and the user's withdrawn rewards for each reward currency.
   - The function takes the following inputs:
     - who: The account ID of the user removing shares
     - pool: The ID of the pool from which shares are being removed
     - remove_amount: The amount of shares to be removed
   - Key steps:
     1. _Claim any pending rewards for the user in the specified pool._
     2. _Update the user's share balance and the pool's total shares based on the `remove_amount`._
     3. Calculate the proportion of removed shares to the user's total shares.
     4. Iterate through all reward currencies and update the user's withdrawn rewards and the pool's total rewards based on the removed share proportion.

_It is expected to update the user's share balance, the pool's total shares, and the user's withdrawn rewards correctly based on the removed share amount._
   - It should calculate the proportion of removed shares to the user's total shares and update the withdrawn rewards and pool's total rewards accordingly.


> The `remove_share()` function behaves as expected, updating the share balances and withdrawn rewards correctly.
> However, it iterates through all reward currencies, even for currencies in which the user has no balance, leading to unnecessary computation. That's my point.

## Proof of Concept
https://github.com/code-423n4/2024-03-acala/blob/9c71c05cf2d9f0a2603984c50f76fc8a315d4d65/src/orml/rewards/src/lib.rs#L191-L251

```rust
fn remove_share(who: &T::AccountId, pool: &T::PoolId, remove_amount: T::Share) {
    // ... (existing code)
    
    // Update withdrawn rewards for each reward currency
    withdrawn_rewards
        .iter_mut()
        .for_each(|(reward_currency, withdrawn_reward)| {
            // ... (existing code)
        });