Part 2

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

`wethRewardPerVaultShare` is incorrectly upadated during distribution of the weth reward between the protocol and market

Summary

As provided, the wethRewardPerVaultShare represents the amount of weth reward accumulated by the market per vault delegated credit (share). However, during distribution of the weth reward between the protocol and market, the whole receivedVaultsWethRewardX18 is added to self.wethRewardPerVaultShare instead of first dividing this value by the total vault shares. This results in incorrect accounting.

Vulnerability Details

The total vault shares is given by the totalDelegatedCreditUsd field in the Data struct. This is utilized globally when performing per-share distributions of various quantities e.g:
Consider distributeDebtToVaults():

function distributeDebtToVaults( //@audit-ok
Data storage self,
SD59x18 newUnrealizedDebtUsdX18,
SD59x18 newRealizedDebtUsdX18
)
internal
{
// @audit-info cache the total vault's shares as SD59x18
>> SD59x18 totalVaultSharesX18 = ud60x18(self.totalDelegatedCreditUsd).intoSD59x18();
---snip---
// @audit-info update storage values: Since we are updating per-share storage fields, we devide the sum
// by total shares
self.realizedDebtUsdPerVaultShare = newRealizedDebtUsdX18.div(totalVaultSharesX18).intoInt256().toInt128();
self.unrealizedDebtUsdPerVaultShare = newUnrealizedDebtUsdX18.div(totalVaultSharesX18).intoInt256().toInt128();
}

The same is done in settleCreditDeposit().

// calculate the usdc that has been accumulated per usd of credit delegated to the market
>> UD60x18 addedUsdcPerCreditShareX18 = netUsdcReceivedX18.div(ud60x18(self.totalDelegatedCreditUsd));
// add the usdc acquired to the accumulated usdc credit variable
>> self.usdcCreditPerVaultShare =
ud60x18(self.usdcCreditPerVaultShare).add(addedUsdcPerCreditShareX18).intoUint128();

Now, when converting accumilated fees to weth, the _handleWethRewardDistribution() calls market.receiveWethReward() which adds the received weth rewards to the stored values of pending protocol weth rewards and vaults' total weth reward.

function receiveWethReward(
Data storage self,
address asset,
UD60x18 receivedProtocolWethRewardX18,
UD60x18 receivedVaultsWethRewardX18
)
internal
{
---SNIP---
// increment the amount of pending weth reward to be distributed to fee recipients
self.availableProtocolWethReward =
ud60x18(self.availableProtocolWethReward).add(receivedProtocolWethRewardX18).intoUint128();
// @audit-info Incorrect update done here
>> self.wethRewardPerVaultShare =
ud60x18(self.wethRewardPerVaultShare).add(receivedVaultsWethRewardX18).intoUint128();
}

However, as seen, the receivedVaultsWethRewardX18 is directly added to self.wethRewardPerVaultShare without prior dividing by total vault shares.

Impact

When in reality the receivedVaultsWethRewardX18 should indicate the per-share weth reward, the calculation above does not enforce this.

Tools Used

Manual Review

Recommendations

function receiveWethReward(
Data storage self,
address asset,
UD60x18 receivedProtocolWethRewardX18,
UD60x18 receivedVaultsWethRewardX18
)
internal
{
// ...
+ // Get total shares
+ SD59x18 totalVaultSharesX18 = ud60x18(self.totalDelegatedCreditUsd).intoSD59x18();
+
+ // Calculate per-share reward
+ UD60x18 rewardPerShareX18 = receivedVaultsWethRewardX18.div(totalVaultSharesX18.intoUD60x18());
self.wethRewardPerVaultShare =
- ud60x18(self.wethRewardPerVaultShare).add(receivedVaultsWethRewardX18).intoUint128();
+ ud60x18(self.wethRewardPerVaultShare).add(rewardPerShareX18).intoUint128();
}
Updates

Lead Judging Commences

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