Part 2

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

Market::receiveWethReward does not update wethRewardPerVaultShare correctly

Summary

The Market::receiveWethReward function incorrectly updates wethRewardPerVaultShare, leading to an over-allocation of WETH rewards to vaults. Instead of distributing WETH rewards proportionally based on delegated credit, the function adds the entire reward amount to wethRewardPerVaultShare. The wethRewardPerVaultShare represents the amount of weth reward accumulated by the market per vault delegated credit (share). Instead of adding weth reward per credit, receiveWethReward adds the whole amount causing miscalculation. As a result, multiple vaults receive more rewards than they are entitled to, causing an imbalance in the reward distribution.

Vulnerability Details

  • The receiveWethReward function is invoked by FeeDistributionBranch::_handleWethRewardDistribution.

  • This function accepts receivedVaultsWethRewardX18 as an argument and directly updates wethRewardPerVaultShare without considering the total delegated credit.

  • Since the reward is not divided by the total delegated credit, it is stored in full, causing over-rewarding across vaults.

Example scenario:

Initial States:
Vault:
wethRewardDistribution:
totalShares: 2e18
valuePerShare: 0
Market:
wethRewardPerVaultShare: 0
CreditDelegation:
lastVaultDistributedWethRewardPerShare: 0
  • Consider receiveWethReward is called with receivedVaultsWethRewardX18 = 10e18

  • Market::wethRewardPerVaultShare will add 10e18

  • When Vault::recalculateVaultsCreditCapacity is called, it will calculated the change of wethRewardPerVaultShare. It will deduct lastVaultDistributedWethRewardPerShare from Market::wethRewardPerVaultShare and return the result. So the return will be 10e18

  • After that recalculateVaultsCreditCapacity will call self.wethRewardDistribution.distributeValue(vaultTotalWethRewardChangeSD59X18); to update value per share. Since vaultTotalWethRewardChangeSD59X18 is 10e18, the valuePerShare will be 10e18/2e18 = 5e18

  • The reward for the users of this vault becomes: totalShares * (valuePerShare - lastValuePerShare). This will be 10e18.

If there are more than one vault connected to the market, the vaults will be drained off WETH. If users from one vault claims reward, other users from other vault won't be able to claim anything as due to lack of WETH and vault will be in shortage of WETH

Impact

  • The system may drain WETH liquidity, as vaults will continuously withdraw more than what is available for rewards

  • Protocol funds will be at risk, as excessive WETH withdrawals may leave the system insolvent.

Tools Used

  • Manual code review

Recommendations

Modify Market::receiveWethReward, such that the receivedVaultsWethRewardX18 is divided by total delegated credit. This division will ensure vaults will get only their share.

function receiveWethReward(
Data storage self,
address asset,
UD60x18 receivedProtocolWethRewardX18,
UD60x18 receivedVaultsWethRewardX18
)
internal
{
// if a market credit deposit asset has been used to acquire the received weth, we need to reset its balance // @audit - notice
if (asset != address(0)) {
// removes the given asset from the received market fees enumerable map as we assume it's been fully // @audit - notice
// swapped to weth
self.receivedFees.remove(asset);
}
+ UD60x18 totalDelegatedCreditX18 = getTotalDelegatedCreditUsd(self);
+ UD60x18 wethRewardPerCredit = receivedVaultsWethRewardX18.div(totalDelegatedCreditX18);
// increment the amount of pending weth reward to be distributed to fee recipients
self.availableProtocolWethReward =
ud60x18(self.availableProtocolWethReward).add(receivedProtocolWethRewardX18).intoUint128();
// increment the all time weth reward storage
self.wethRewardPerVaultShare =
- ud60x18(self.wethRewardPerVaultShare).add(receivedVaultsWethRewardX18).intoUint128();
+ ud60x18(self.wethRewardPerVaultShare).add(wethRewardPerCredit).intoUint128();
}

When vault retieves the weth reward, it should be multiplied with vault shares to get the correct amount

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 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.

Give us feedback!