sherlock-audit / 2024-07-kwenta-staking-contracts-judging

1 stars 0 forks source link

patronasxd - Decimal Precision Issue in USDC rewards Calculation #82

Closed sherlock-admin2 closed 1 month ago

sherlock-admin2 commented 1 month ago

patronasxd

High

Decimal Precision Issue in USDC rewards Calculation

Summary

The current implementation of the earnedUSDC and rewardPerTokenUSDC function incorrectly handles decimal precision for USDC, which uses 6 decimals. This results in inaccurate reward calculations and potential overpayments.

Root Cause

in the View function earnedUSDC of StakingRewardsV2.sol, and rewardPerTokenUSDC of StakingRewardsV2.sol the reward calculation uses a precision factor of 1e18, which is suitable for ERC20 tokens with 18 decimal places. However, since USDC uses 6 decimal places, the precision factor should be 1e16 to ensure accurate reward amounts.

Internal pre-conditions

No response

External pre-conditions

  1. USDC Token Implementation: The USDC token adheres to a 6-decimal format, which is different from the 18-decimal format assumed in the contract's calculations.

Attack Path

  1. User calls any method with the updateReward modifier to set the incorrect USDC reward amount.

Impact

PoC

let's walk through calculations showing how using 1e18 versus 1e16 affects the reward distribution for USDC.

Scenario 1: Incorrect Precision (1e18)

Reward Per Token Calculation (rewardPerTokenUSDC):

rewardPerTokenUSDC() = rewardPerTokenStoredUSDC
    + (((lastTimeRewardApplicable() - lastUpdateTime) * rewardRateUSDC * 1e18) / allTokensStaked)

Substituting the values:

rewardPerTokenUSDC() = 0
    + (((604800 * 1000 * 1e18) / 1,000,000))
    = ((604800 * 1000 * 1e18) / 1,000,000)
    = 604800000000000000000

Earned USDC Calculation (earnedUSDC):

earnedUSDC() = ((totalBalance * (rewardPerTokenUSDC() - userRewardPerTokenPaidUSDC[_account])) / 1e18)
    + rewardsUSDC[_account]

Substituting the values:

earnedUSDC() = ((1000 * (604800000000000000000 - 0)) / 1e18)
    = ((1000 * 604800000000000000000) / 1e18)
    = 604800000

Result: 604,800 USDC, which is high and unrealistic.

Scenario 2: Correct Precision (1e16)

Reward Per Token Calculation (rewardPerTokenUSDC):

rewardPerTokenUSDC() = rewardPerTokenStoredUSDC
    + (((lastTimeRewardApplicable() - lastUpdateTime) * rewardRateUSDC * 1e16) / allTokensStaked)

Substituting the values:

rewardPerTokenUSDC() = 0
    + (((604800 * 1000 * 1e18) / 1,000,000) / 1e2)
    = ((604800 * 1000 * 1e18) / (1,000,000 * 1e2))
    = 604800000000000000

Earned USDC Calculation (earnedUSDC):

earnedUSDC() = ((totalBalance * (rewardPerTokenUSDC() - userRewardPerTokenPaidUSDC[_account])) / 1e16)
    + rewardsUSDC[_account]

Substituting the values:

earnedUSDC() = ((1000 * (604800000000000000 - 0)) / 1e16)
    = ((1000 * 604800000000000000) / 1e16)
    = 60480

Result: 60,480 USDC, which is a more realistic value.

Summary

Using 1e18: The reward calculations are high (e.g., 604,800 USDC), indicating an error due to incorrect precision scaling.

Using 1e16: The reward calculations are reasonable and align with expected values (e.g., 60,480 USDC)

Mitigation

use 1e16 instead of 1e18 for rewardPerTokenUSDC and earnedUSDC

patronasxdxd commented 1 month ago

Apologies for the miscalculation in the report. The correct value should obviously be 1e6 instead of 1e16. Let's just say that being sleep-deprived makes my brain mix things up. 😄