Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: high
Valid

[M-09] WETH Rewards Misallocation: getVaultAccumulatedValues Allows Vaults to Overclaim Rewards

Summary

The function getVaultAccumulatedValues is responsible for computing the accumulated value changes for vaults, including realized debt, unrealized debt, USDC credit, and WETH rewards. However, while the function correctly applies vaultCreditShareX18 to USDC credit changes, it fails to apply this adjustment to WETH rewards.

Vulnerability Details

Incorrect Calculation of WETH Rewards:

wethRewardChangeX18 = ud60x18(self.wethRewardPerVaultShare).sub(lastVaultDistributedWethRewardPerShareX18);
  • This only calculates the change in WETH rewards per share but does not multiply by vaultCreditShareX18 to proportionally distribute rewards among vaults.

  • Comparison with USDC Reward Calculation:

    • The function correctly applies vaultCreditShareX18 for USDC rewards

      usdcCreditChangeX18 = !lastVaultDistributedUsdcCreditPerShareX18.isZero()
      ? ud60x18(self.usdcCreditPerVaultShare).sub(lastVaultDistributedUsdcCreditPerShareX18).mul(
      vaultCreditShareX18
      )
      : UD60x18_ZERO;
  • This ensures USDC rewards are distributed proportionally, but the same logic is missing for WETH rewards.

Impact

The **impact is MEDIUM **because:

  • Economic Fairness is Violated: Some vaults can unfairly accumulate more WETH rewards than others.

  • Financial Risk to Liquidity Providers: Misallocated rewards can discourage LP participation and destabilize Zaros' incentive structure.

  • Potential Drain on WETH Rewards: A vault with minimal credit can exploit this flaw to drain excessive WETH rewards over multiple reward cycles.


The likelihood is High because:

  • The function is called regularly by vaults to check their accumulated values.

  • No special permissions are required—any vault can execute this function and claim excessive rewards.

  • If vault rewards are distributed dynamically, a vault could repeatedly drain more rewards than intended over time.

function testIncorrectWETHRewardCalculation() public {
address vaultA = address(0xA);
address vaultB = address(0xB);
uint256 vaultACredit = 10 ether; // Vault A owns 10% of total credit
uint256 vaultBCredit = 90 ether; // Vault B owns 90% of total credit
uint256 totalCredit = vaultACredit + vaultBCredit;
// Assign total credit to vaults
perpsContract.setVaultCredit(vaultA, vaultACredit);
perpsContract.setVaultCredit(vaultB, vaultBCredit);
// Assume WETH reward per share increases by 100 WETH
perpsContract.setWETHRewardPerVaultShare(100 ether);
// Vault A calls getVaultAccumulatedValues()
uint256 rewardA = perpsContract.getVaultAccumulatedValues(vaultA);
// Vault B calls getVaultAccumulatedValues()
uint256 rewardB = perpsContract.getVaultAccumulatedValues(vaultB);
// Expected: Vault A gets 10 WETH (10% of 100), Vault B gets 90 WETH (90% of 100)
// Actual (flawed): Both Vault A and Vault B get 100 WETH, which is incorrect.
assert(rewardA == 10 ether); // Fails
assert(rewardB == 90 ether); // Fails
}

Recommendations

Modify the wethRewardChangeX18 calculation to include multiplication by vaultCreditShareX18, ensuring proportional distribution.

wethRewardChangeX18 = !lastVaultDistributedWethRewardPerShareX18.isZero()
? ud60x18(self.wethRewardPerVaultShare).sub(lastVaultDistributedWethRewardPerShareX18).mul(
vaultCreditShareX18
)
: UD60x18_ZERO;

This** **ensures vaults receive rewards proportional to their credit share.

  • Prevents unauthorized vaults from claiming excess WETH.

  • Maintains consistency with how USDC rewards are already calculated.

Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`wethRewardPerVaultShare` is incremented by `receivedVaultWethReward` amount which is not divided by number of shares.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.