Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The lender won't be able to claim rewards in some cases and most of RewardsManager's methods (e.g. staking, unstaking ..etc) will revert. #354

Closed
code423n4 opened this issue May 11, 2023 · 4 comments
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-440 satisfactory satisfies C4 submission criteria; eligible for awards sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") upgraded by judge Original issue severity upgraded from QA/Gas by judge

Comments

@code423n4
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L719-L726

Vulnerability details

Impact

In RewardsManager, when claiming the rewards, _updateBucketExchangeRates method is called. This method does the following:

  1. If curBurnEpoch == 0 then just update the rates without calculating any reward.
  2. Otherwise, retrieve accumulator values (e.g. totalBurned)
	// retrieve accumulator values used to calculate rewards accrued
	(
		uint256 curBurnTime,
		uint256 totalBurned,
		uint256 totalInterestEarned
	) = _getPoolAccumulators(pool_, curBurnEpoch, curBurnEpoch - 1);
  1. Loop through the bucket index (positions recorded for the lender)
    • For each index, update bucket exchange rate and calculate the rewards (done via _updateBucketExchangeRateAndCalculateRewards)
    • Add the rewards to updatedRewards_ to track the total rewards.
	for (uint256 i = 0; i < indexes_.length; ) {

		// calculate rewards earned for updating bucket exchange rate
		updatedRewards_ += _updateBucketExchangeRateAndCalculateRewards(
			pool_,
			indexes_[i],
			curBurnEpoch,
			totalBurned,
			totalInterestEarned
		);

		// iterations are bounded by array length (which is itself bounded), preventing overflow / underflow
		unchecked { ++i; }
	}
  1. Calculate the rewards cap based on totalBurned
	uint256 rewardsCap            = Maths.wmul(UPDATE_CAP, totalBurned);
  1. Make sure old updateRewardsClaimed in epoch + updatedRewards_ doesn't exceed the rewards cap.
	// update total tokens claimed for updating bucket exchange rates tracker
	if (rewardsClaimedInEpoch + updatedRewards_ >= rewardsCap) {
		// if update reward is greater than cap, set to remaining difference
		updatedRewards_ = rewardsCap - rewardsClaimedInEpoch;
	}
  1. Add updatedRewards_ to updateRewardsClaimed in epoch
	// accumulate the full amount of additional rewards
	updateRewardsClaimed[curBurnEpoch] += updatedRewards_;

Since rewardsClaimedInEpoch is always supposed to be not be greater than rewardsCap we have no revert issue (note: the method reverts if rewardsCap is lower than rewardsClaimedInEpoch). However, to guarantee this, totalBurned should never decrease. In other words, if totalBurned went from a high to low value at any point, then rewardsCap will follow. If we check how totalBurned is calculated, we notice that there is a possibilty that this happens.

	uint256 totalBurned   = totalBurnedLatest   != 0 ? totalBurnedLatest   - totalBurnedAtBlock   : totalBurnedAtBlock;

totalBurnedLatest => total burned of current epoch
totalBurnedAtBlock => total burned of previous epoch

If totalBurnedLatest equals zero, it takes the difference between totalBurnedLatest and totalBurnedAtBlock. otherwise, it takes totalBurnedAtBlock. because of this logic, it is possible that we have the following:

  • Assume totalBurnedLatest = 1500 and totalBurnedAtBlock = 1000
    • totalBurned = 500
  • totalBurnedAtBlock increased with 100
    • totalBurned = 400

Now. since totalBurned went lower, rewardsCap will go lower as well.
The issue is that, in case the above happens, updateRewardsClaimed in epoch will be greater than rewardsCap and this condition will be true regardless of how much updatedRewards_ the user should receive

rewardsClaimedInEpoch + updatedRewards_ >= rewardsCap

This results in a revert in the following line since rewardsCap is lower than rewardsClaimedInEpoch:

	// if update reward is greater than cap, set to remaining difference
	updatedRewards_ = rewardsCap - rewardsClaimedInEpoch;

Therefore, the user won't be able to claim rewards in such situations. And since claim rewards is also done upon staking, unstaking and moveStakedLiquidity, all of those will revert due to this issue.

Proof of Concept

Please check above for explanation.
https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L719-L726

Tools Used

Manual analysis

Recommended Mitigation Steps

Check if rewardsCap is lower than rewardsClaimedInEpoch, set updatedRewards_ to zero.

Assessed type

DoS

@code423n4 code423n4 added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels May 11, 2023
code423n4 added a commit that referenced this issue May 11, 2023
@c4-sponsor
Copy link

MikeHathaway marked the issue as sponsor confirmed

@c4-sponsor c4-sponsor added the sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") label May 19, 2023
@c4-judge
Copy link
Contributor

Picodes marked the issue as duplicate of #440

@c4-judge
Copy link
Contributor

Picodes marked the issue as satisfactory

@c4-judge c4-judge added satisfactory satisfies C4 submission criteria; eligible for awards 3 (High Risk) Assets can be stolen/lost/compromised directly upgraded by judge Original issue severity upgraded from QA/Gas by judge and removed 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value labels May 30, 2023
@c4-judge
Copy link
Contributor

c4-judge commented Jun 3, 2023

Picodes changed the severity to 3 (High Risk)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-440 satisfactory satisfies C4 submission criteria; eligible for awards sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") upgraded by judge Original issue severity upgraded from QA/Gas by judge
Projects
None yet
Development

No branches or pull requests

3 participants