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(
Data storage self,
SD59x18 newUnrealizedDebtUsdX18,
SD59x18 newRealizedDebtUsdX18
)
internal
{
>> SD59x18 totalVaultSharesX18 = ud60x18(self.totalDelegatedCreditUsd).intoSD59x18();
---snip---
self.realizedDebtUsdPerVaultShare = newRealizedDebtUsdX18.div(totalVaultSharesX18).intoInt256().toInt128();
self.unrealizedDebtUsdPerVaultShare = newUnrealizedDebtUsdX18.div(totalVaultSharesX18).intoInt256().toInt128();
}
The same is done in settleCreditDeposit()
.
>> UD60x18 addedUsdcPerCreditShareX18 = netUsdcReceivedX18.div(ud60x18(self.totalDelegatedCreditUsd));
>> 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---
self.availableProtocolWethReward =
ud60x18(self.availableProtocolWethReward).add(receivedProtocolWethRewardX18).intoUint128();
>> 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();
}