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.