Audit finding: Early user can break pool via inflation attack due to no minimum liquidity check in the incentive contract
Project: Acala
Auditor: Code4Arena
Location: src/modules/incentives/src/lib.rs:511-523
Impact: High
Vulnerability class (Scout): Business Logic
Status: Fixed by [this PR](https://github.com/open-web3-stack/open-runtime-module-library/pull/991).

[H-02] Early user can break pool via inflation attack due to no minimum liquidity check in the incentive contract
Submitted by carrotsmuggler, also found by zhaojie
The incentive contract does not enforce a minimum liquidity limit. This means users can have as little as 1 share in the pool. This can lead to inflation attacks as described below.
Let’s imagine the state of the pool is as follows:
There is a single depositor, with 1000 shares deposited. Rewards have been accumulated up to 500 tokens. The user can then withdraw 998 shares, leaving 2 shares. They will also claim the rewards, and leave 1 reward token in the pool. This is the setup for the inflation attack. The user can then deposit 1 share.
The inflation is calculated as shown below:
U256::from(add_amount.to_owned().saturated_into::<u128>()).saturating_mul(total_reward.to_owned().saturated_into).checked_div(initial_total_shares.to_owned().saturated_into.unwrap_or_default().as_u128().saturated_into()
Here total_reward=1, add_amount=1 and initial_total_shares=2. So the result is calculated to 0; so inflation is 0.
After this step, the initial_total_shares is updated to 3. Now the user can deposit 2 wei of shares without changing the inflation amount. Next iteration, they can deposit 4 shares. This way, the user can deposit 2**n shares each iteration, and inflate the initial_total_shares without affecting the reward inflation. This leads to the situation where the total_shares keeps growing according to the deposit, but the entire reward inflation mechanism is broken. This lets users steal reward tokens from other users, and is a high-severity issue.
In fact, whenever the total_reward value is less than the total_shares, this issue can be triggered. This is because in those conditions, users can create deposits and have the reward_inflation evaluate to 0. A reward_inflation of 0 basically means later users can steal rewards of earlier users, as is outlined in the docs. However, this donation attack is more effective the lower the total_shares in the system.
Proof of Concept
The situation can be created via the following steps:
1. Since there is no minimum deposit, we can create a situation where total_reward < total_shares and total_shares = 2. This also works for higher values of total_shares but is most potent at this stage.
2. User deposits 1 share, or any number of shares as long as deposit * total_reward / total_shares is less than 1. The reward_inflation will be 0, while the user gets their shares accounted for.
3. Since total_shares has now increased, the user can deposit more shares and still have the reward_inflation be 0. This way, the user can keep depositing shares and increasing the total_shares without affecting the reward inflation.
4. Since reward_inflation and thus total_reward has not increased, but total_shares has increased, users will lose rewards, since the rewards are calculated as total_reward * user_shares / total_shares. This means older users lose shares.
While this vector is generally applicable and can lead to small losses when there’s a lot of liquidity, this becomes more potent when there is very low liquidity in the pool. This was the method of attack for the Wise Lending hack and is a high-severity issue. More details can be found in the blog post outlining the attack scenario with more numbers and examples.
Tools Used
Substrate
Recommended Mitigation Steps
Add a minimum liquidity limit. This will ensure the pool never reaches a liquidity amount so low that rounding errors become significant.
xlc (Acala) confirmed and commented:
It is actually almost impossible to trigger this in production because anyone can deposit into the incentives pool at any time, i.e., before rewards start accumulating.
Fixed by this PR.

Summary: The incentive contract allows deposits with no minimum liquidity, enabling an inflation attack where users can inflate share counts and steal rewards from others.