Summary
User will loss earned fees if not claiming fees before staking.
Vulnerability Details
In VaultRouterBranch::stake, it wll call wethRewardDistribution.accumulateActor() and wethRewardDistribution.setActorShares(), which will update the actor's lastValuePerShare to valuePerShare by Distribution.sol::_updateLastValuePerShare.
The earned fees are calculated by the difference between valuePerShare and lastValuePerShare. Stake() will set them the same and the earned fees will be empty.
function stake(uint128 vaultId, uint128 shares) external {
if (shares < Constants.MIN_OF_SHARES_TO_STAKE) {
revert Errors.QuantityOfSharesLessThanTheMinimumAllowed(Constants.MIN_OF_SHARES_TO_STAKE, uint256(shares));
}
Vault.Data storage vault = Vault.loadLive(vaultId);
uint256[] memory vaultsIds = new uint256[](1);
vaultsIds[0] = uint256(vaultId);
Vault.recalculateVaultsCreditCapacity(vaultsIds);
Distribution.Data storage wethRewardDistribution = vault.wethRewardDistribution;
bytes32 actorId = bytes32(uint256(uint160(msg.sender)));
wethRewardDistribution.accumulateActor(actorId);
Distribution.Actor storage actor = wethRewardDistribution.actor[actorId];
UD60x18 updatedActorShares = ud60x18(actor.shares).add(ud60x18(shares));
wethRewardDistribution.setActorShares(actorId, updatedActorShares);
IERC20(vault.indexToken).safeTransferFrom(msg.sender, address(this), shares);
emit LogStake(vaultId, msg.sender, shares);
}
function _updateLastValuePerShare(
Data storage self,
Actor storage actor,
UD60x18 newActorShares
)
private
returns (SD59x18 valueChange)
{
valueChange = _getActorValueChange(self, actor);
actor.lastValuePerShare = newActorShares.eq(UD60x18_ZERO) ? int256(0) : self.valuePerShare;
}
...
function _getActorValueChange(
Data storage self,
Actor storage actor
)
private
view
returns (SD59x18 valueChange)
{
SD59x18 deltaValuePerShare = sd59x18(self.valuePerShare).sub(sd59x18(actor.lastValuePerShare));
valueChange = deltaValuePerShare.mul(ud60x18(actor.shares).intoSD59x18());
}
Impact
User will loss earned fees if not claiming fees before staking.
Tools Used
Manual
Recommendations
1.Same as unstake, check if there are claimable fees before call accumulateActor.
// cast actor address to bytes32
bytes32 actorId = bytes32(uint256(uint160(msg.sender)));
+ UD60x18 amountToClaimX18 = vault.wethRewardDistribution.getActorValueChange(actorId).intoUD60x18();
+ if (!amountToClaimX18.isZero()) revert Errors.UserHasPendingRewards(actorId, amountToClaimX18.intoUint256());
// accumulate the actor's pending reward before staking
wethRewardDistribution.accumulateActor(actorId);
2.Add a new field to record claimable fees.