C-1: Incorrect interpretation of released rewards in `LiquidityGauge`
Summary:
The LiquidityGauge contract uses the value of the checkpointed reward rate integral to determine the updated amount of released reward tokens, leading to multiple accounting problems.
Description:
The implementation of _get_vested_rewards() takes the value of reward_rate_integral[token].v as the amount of released rewards up to the latest checkpointed time (used_rewards).
170: last_reward_time: uint256 = self.reward_rate_integral[token].t
171: used_rewards: uint256 = self.reward_rate_integral[token].v
172: finish_time: uint256 = self.rewards[token].finish_time
173: total: uint256 = self.rewards[token].total
174: if finish_time > last_reward_time:
175: new_used: uint256 = (total - used_rewards) * (block.timestamp - last_reward_time) //\
176: (finish_time - last_reward_time) + used_rewards
177: return min(new_used, total) - used_rewards
The used_rewards variable is then used to compute the remaining amount (total - used_rewards) and to calculate the new value of distributed rewards (new_used).
However, taking the integral of the reward rate is incorrect, as this value is already scaled by the inverse of the total supply as part of the checkpoint process.
148: r.reward_rate_integral = self.reward_rate_integral[reward]
149: if block.timestamp > r.reward_rate_integral.t:
150: r.reward_rate_integral.v += (r.integral_inv_supply.v - self.integral_inv_supply_4_token[reward]) * d_reward //\
151: (block.timestamp - r.reward_rate_integral.t)
152: r.reward_rate_integral.t = block.timestamp
Impact:
Critical. The issue leads to multiple accounting problems and side effects, potentially bricking the state of the vault.
Recommendation:
Have a dedicated accumulator to measure the amount of distributed rewards per token. This counter should be incremented by the return value of _vest_rewards() whenever the state is persisted to storage (_checkpoint_user(), claim(), deposit_reward()).
Developer Response:
Fixed in 657e8c679cb575f35681fab810ff6615ed113924.