Soaring Lipstick Kitten - Inability To Remove Reward Token Due To Rounding Issues In ConfigureRewardToken

by ADMIN 106 views

Soaring Lipstick Kitten - Inability to Remove Reward Token Due to Rounding Issues in configureRewardToken

Introduction

The Soaring Lipstick Kitten is a vulnerability discovered in the SymmStaking contract, which is part of the Symm-io protocol. The issue lies in the configureRewardToken function, where the condition pendingRewards[token] > 10 is ineffective due to rounding issues in reward distribution calculations. This can lead to reward tokens becoming permanently non-removable, causing governance and protocol inefficiencies.

Root Cause

The root cause of this issue lies in the way rewards are calculated and distributed. When rewards are notified, pendingRewards[token] is incremented by the deposited amount. However, the reward rate is calculated using integer division (state.rate = amount / state.duration), which causes rounding down. When users claim rewards, the claimable amount is also rounded down, leading to discrepancies between distributed and pending rewards.

	function configureRewardToken(address token, bool status) external onlyRole(REWARD_MANAGER_ROLE) {
		_updateRewardsStates(address(0));

		if (token == address(0)) revert ZeroAddress();
		if (isRewardToken[token] == status) revert TokenWhitelistStatusUnchanged(token, status);

		isRewardToken[token] = status;
		if (!status) {
			if (pendingRewards[token] > 10) revert OngoingRewardPeriodForToken(token, pendingRewards[token]);

Internal Pre-conditions

The internal pre-conditions for this issue are:

  • notifyRewardAmount increases pendingRewards[token]
  • state.rate = amount / state.duration leads to rounding down, causing a mismatch between expected and actual reward distribution
  • Users claiming rewards further reduce pendingRewards[token] in a rounded-down manner, making it nearly impossible to reach exactly 10 or below.

External Pre-conditions

There are no external pre-conditions for this issue.

Attack Path

The attack path for this issue is as follows:

  1. An attacker deposits a large amount of tokens into the contract.
  2. The contract calculates the reward rate using integer division, causing rounding down.
  3. The attacker claims their rewards, which are also rounded down.
  4. The pendingRewards[token] value remains high, making it impossible to remove the reward token.

Impact

The impact of this issue is significant, as it prevents proper reward token management by administrators and leads to the accumulation of unnecessary reward tokens.

Proof of Concept

The proof of concept for this issue is as follows:


	describe("Reward Calculation", function () {
		it("should still pending rewards left when all claimed", async function () {
			// Scenario: Single depositor — user1 deposits 604,800 SYMM, waits 200s, claims 200 tokens.

			const depositAmount = "604900"
			const rewardAmount = depositAmount
			await stakingToken.connect(admin).approve(await symmStaking.getAddress(), rewardAmount)
			await symmStaking.connect(admin).configureRewardToken(await stakingToken.getAddress(), true)
			await symmStaking.connect(admin).notifyRewardAmount([await stakingToken.getAddress()], [rewardAmount])

			await stakingToken.connect(user1).approve(await symmStaking.getAddress(), depositAmount)
			await symmStaking.connect(user1).deposit(depositAmount, user1.address)


			const pending = await symmStaking.pendingRewards(await stakingToken.getAddress());
			expect(pending).to.equal(depositAmount);

			await time.increase(100*24*60*60)


			const user1BalanceBefore = await stakingToken.balanceOf(user1.address)
			await symmStaking.connect(user1).claimRewards()
			const user1BalanceAfter = await stakingToken.balanceOf(user1.address)
			const claimed = user1BalanceAfter - user1BalanceBefore
			expect(claimed).to.equal("604797")
		})
	})

Mitigation

The mitigation for this issue is to replace the pendingRewards[token] > 10 condition with a more flexible threshold, such as a percentage of total rewards or a higher absolute limit. This will prevent the issue from occurring and ensure that reward tokens can be properly removed.

Conclusion

In conclusion, the Soaring Lipstick Kitten is a vulnerability discovered in the SymmStaking contract, which can lead to reward tokens becoming permanently non-removable. The issue is caused by rounding issues in reward distribution calculations and can be mitigated by replacing the pendingRewards[token] > 10 condition with a more flexible threshold.
Soaring Lipstick Kitten - Inability to Remove Reward Token Due to Rounding Issues in configureRewardToken

Q&A

Q: What is the Soaring Lipstick Kitten vulnerability?

A: The Soaring Lipstick Kitten is a vulnerability discovered in the SymmStaking contract, which is part of the Symm-io protocol. The issue lies in the configureRewardToken function, where the condition pendingRewards[token] > 10 is ineffective due to rounding issues in reward distribution calculations.

Q: What is the root cause of this issue?

A: The root cause of this issue lies in the way rewards are calculated and distributed. When rewards are notified, pendingRewards[token] is incremented by the deposited amount. However, the reward rate is calculated using integer division (state.rate = amount / state.duration), which causes rounding down. When users claim rewards, the claimable amount is also rounded down, leading to discrepancies between distributed and pending rewards.

Q: What are the internal pre-conditions for this issue?

A: The internal pre-conditions for this issue are:

  • notifyRewardAmount increases pendingRewards[token]
  • state.rate = amount / state.duration leads to rounding down, causing a mismatch between expected and actual reward distribution
  • Users claiming rewards further reduce pendingRewards[token] in a rounded-down manner, making it nearly impossible to reach exactly 10 or below.

Q: What are the external pre-conditions for this issue?

A: There are no external pre-conditions for this issue.

Q: What is the attack path for this issue?

A: The attack path for this issue is as follows:

  1. An attacker deposits a large amount of tokens into the contract.
  2. The contract calculates the reward rate using integer division, causing rounding down.
  3. The attacker claims their rewards, which are also rounded down.
  4. The pendingRewards[token] value remains high, making it impossible to remove the reward token.

Q: What is the impact of this issue?

A: The impact of this issue is significant, as it prevents proper reward token management by administrators and leads to the accumulation of unnecessary reward tokens.

Q: How can this issue be mitigated?

A: This issue can be mitigated by replacing the pendingRewards[token] > 10 condition with a more flexible threshold, such as a percentage of total rewards or a higher absolute limit.

Q: What is the proof of concept for this issue?

A: The proof of concept for this issue is as follows:


	describe("Reward Calculation", function () {
		it("should still pending rewards left when all claimed", async function () {
			// Scenario: Single depositor — user1 deposits 604,800 SYMM, waits 200s, claims 200 tokens.

			const depositAmount = "604900"
			const rewardAmount = depositAmount
			await stakingToken.connect(admin).approve(await symmStaking.getAddress(), rewardAmount)
			await symmStaking.connect(admin).configureRewardToken(await stakingToken.getAddress(), true)
			await symmStaking.connect(admin).notifyRewardAmount([await stakingToken.getAddress()], [rewardAmount])

			await stakingToken.connect(user1).approve(await symmStaking.getAddress(), depositAmount)
			await symmStaking.connect(user1).deposit(depositAmount, user1.address)


			const pending = await symmStaking.pendingRewards(await stakingToken.getAddress());
			expect(pending).to.equal(depositAmount);

			await time.increase(100*24*60*60)


			const user1BalanceBefore = await stakingToken.balanceOf(user1.address)
			await symmStaking.connect(user1).claimRewards()
			const user1BalanceAfter = await stakingToken.balanceOf(user1.address)
			const claimed = user1BalanceAfter - user1BalanceBefore
			expect(claimed).to.equal("604797")
		})
	})

Q: What is the conclusion of this issue?

A: In conclusion, the Soaring Lipstick Kitten is a vulnerability discovered in the SymmStaking contract, which can lead to reward tokens becoming permanently non-removable. The issue is caused by rounding issues in reward distribution calculations and can be mitigated by replacing the pendingRewards[token] > 10 condition with a more flexible threshold.