Part 2

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

User will loss earned fees if not claiming fees before staking

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.

// src/market-making/branches/VaultRouterBranch.sol
function stake(uint128 vaultId, uint128 shares) external {
// to prevent safe cast overflow errors
if (shares < Constants.MIN_OF_SHARES_TO_STAKE) {
revert Errors.QuantityOfSharesLessThanTheMinimumAllowed(Constants.MIN_OF_SHARES_TO_STAKE, uint256(shares));
}
// fetch storage slot for vault by id
Vault.Data storage vault = Vault.loadLive(vaultId);
// prepare the `Vault::recalculateVaultsCreditCapacity` call
uint256[] memory vaultsIds = new uint256[](1);
vaultsIds[0] = uint256(vaultId);
// updates the vault's credit capacity and perform all vault state
// transitions before updating `msg.sender` staked shares
Vault.recalculateVaultsCreditCapacity(vaultsIds);
// load distribution data
Distribution.Data storage wethRewardDistribution = vault.wethRewardDistribution;
// cast actor address to bytes32
bytes32 actorId = bytes32(uint256(uint160(msg.sender)));
// accumulate the actor's pending reward before staking
wethRewardDistribution.accumulateActor(actorId);
// load actor distribution data
Distribution.Actor storage actor = wethRewardDistribution.actor[actorId];
// calculate actor updated shares amount
UD60x18 updatedActorShares = ud60x18(actor.shares).add(ud60x18(shares));
// update actor staked shares
wethRewardDistribution.setActorShares(actorId, updatedActorShares);
// transfer shares from actor
IERC20(vault.indexToken).safeTransferFrom(msg.sender, address(this), shares);
// emit an event
emit LogStake(vaultId, msg.sender, shares);
}
// src/market-making/leaves/Distribution.sol
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.

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Inside VaultRouterBranch if you stake wait some time then stake again makes you lose the rewards.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!