Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Invalid

Users with unclaimed rewards can't unstake shares

Summary

The VaultRouterBranch.unstake should claim a fee for the actorId before shares unstaking. The problem is in the check, which reverts the function when users have unclaimed fees. This way users with claimable rewards can't unstake shares.

Vulnerability Details

The check is places before the function which should accumulate fees:

function unstake(uint128 vaultId, uint256 shares) external {
// 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);
// get vault staking fee distribution data
Distribution.Data storage wethRewardDistribution = vault.wethRewardDistribution;
// cast actor address to bytes32
bytes32 actorId = bytes32(uint256(uint160(msg.sender)));
// get the claimable amount of fees
>> UD60x18 amountToClaimX18 = vault.wethRewardDistribution.getActorValueChange(actorId).intoUD60x18();//@audit should be after 605. so can't be unstaked with pending fees
// reverts if the claimable amount is NOT 0
>> if (!amountToClaimX18.isZero()) revert Errors.UserHasPendingRewards(actorId, amountToClaimX18.intoUint256());
// accumulate the actor's pending reward before unstaking
>> wethRewardDistribution.accumulateActor(actorId);
}

Impact

Unintended behavior, DoS of the core functionality.

Tools used

Manual Review

Recommendations

Consider checking the claimable amount after the wethRewardDistribution.accumulateActor(actorId) or skipping it at all.

function unstake(uint128 vaultId, uint256 shares) external {
// 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);
// get vault staking fee distribution data
Distribution.Data storage wethRewardDistribution = vault.wethRewardDistribution;
// cast actor address to bytes32
bytes32 actorId = bytes32(uint256(uint160(msg.sender)));
-
- // get the claimable amount of fees
- UD60x18 amountToClaimX18 = vault.wethRewardDistribution.getActorValueChange(actorId).intoUD60x18();
-
- // reverts if the claimable amount is NOT 0
- if (!amountToClaimX18.isZero()) revert Errors.UserHasPendingRewards(actorId, amountToClaimX18.intoUint256());
// accumulate the actor's pending reward before unstaking
wethRewardDistribution.accumulateActor(actorId);
+
+ // get the claimable amount of fees
+ UD60x18 amountToClaimX18 = wethRewardDistribution.getActorValueChange(actorId).intoUD60x18();
+
+ // reverts if the claimable amount is NOT 0
+ if (!amountToClaimX18.isZero()) revert Errors.UserHasPendingRewards(actorId, amountToClaimX18.intoUint256());
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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