You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
eeyore - Loss of user-earned rewards due to lack of recovery mechanism and insufficient reward token balances in the VaultRewarderLib _claimRewardToken() function
#36
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelMediumA valid Medium severity issueRewardA payout will be made for this issue
Loss of user-earned rewards due to lack of recovery mechanism and insufficient reward token balances in the VaultRewarderLib _claimRewardToken() function
Summary
The protocol zeroes out user-earned rewards if there are no reward tokens to transfer within the vault contract.
Vulnerability Detail
In the VaultRewarderLib, users can earn extra APR from incentive tokens provided by booster protocols. There are three ways these incentive tokens can be handled:
Automatic Reinvest: Tokens are claimed by the vault (from booster protocols) and reinvested into the vault.
Account Claim: Tokens are claimed by the vault (from booster protocols) and distributed per vault share.
Emission Rate: Extra tokens are distributed per emissionRatePerYear per vault share. These tokens are not claimed but are transferred to the vault externally.
These methods can be used in combinations.
In the default flow, vaults operate in the Automatic Reinvest mode, ensuring there is no loss for the user as rewards are reinvested into the vault.
However, when Account Claim and Emission Rate are used together, users may unexpectedly lose rewards because their rewards are zeroed out during the claim process. This happens because rewards for the Emission Rate are transferred to the vault indirectly and are not reserved for the user. In the worst case, the emissionRatePerYear can be set, but rewards may never be transferred into the vault.
If the rewards for the Emission Rate are not transferred, users who claim rewards first will cannibalize the rewards from the Account Claim functionality, leaving later users without rewards from either source. Essentially, rewards are distributed on a first-come, first-served basis.
In the _claimRewardToken() function, where reward tokens should be transferred to the users, the user rewards are zeroed if there is an insufficient balance of reward tokens, and information about untransferred/pending rewards is not stored.
function _claimRewardToken(
addressrewardToken,
addressaccount,
uint256vaultSharesBefore,
uint256vaultSharesAfter,
uint256rewardsPerVaultShare
) internalreturns (uint256rewardToClaim) {
rewardToClaim =_getRewardsToClaim(
rewardToken, account, vaultSharesBefore, rewardsPerVaultShare
);
VaultStorage.getAccountRewardDebt()[rewardToken][account] = (
(vaultSharesAfter * rewardsPerVaultShare) /uint256(Constants.INTERNAL_TOKEN_PRECISION)
); // <-- This equates to zeroing the user rewards if the transfer fails in the next step.if (0< rewardToClaim) {
// Ignore transfer errors here so that any strange failures here do not// prevent normal vault operations from working. Failures may include a// lack of balances or some sort of blacklist that prevents an account// from receiving tokens.tryIEIP20NonStandard(rewardToken).transfer(account, rewardToClaim) {
bool success = TokenUtils.checkReturnCode();
if (success) {
emitVaultRewardTransfer(rewardToken, account, rewardToClaim);
} else {
emitVaultRewardTransfer(rewardToken, account, 0);
}
// Emits zero tokens transferred if the transfer fails.
} catch {
emitVaultRewardTransfer(rewardToken, account, 0);
}
}
}
Token transfers are most likely to fail due to a lack of balances rather than blacklisting. Valid, non-blacklisted users should not suffer losses because of this. This leads to a direct loss of funds/rewards for the users and unfair reward distribution.
Impact
User-earned rewards are lost both from Account Claim and Emission Rate.
In case of a transfer failure, which is most likely due to a lack of balances, the unclaimed value should be stored for future attempts if rewards for the Emission Rate are transferred later.
Introduce new storage values and update the functions as in this example:
if (0 < rewardToClaim) {
// Ignore transfer errors here so that any strange failures here do not
// prevent normal vault operations from working. Failures may include a
// lack of balances or some sort of blacklist that prevents an account
// from receiving tokens.
try IEIP20NonStandard(rewardToken).transfer(account, rewardToClaim) { // @audit-issue - M - most likely will revert due to insufficient balance
bool success = TokenUtils.checkReturnCode();
if (success) {
emit VaultRewardTransfer(rewardToken, account, rewardToClaim);
+ VaultStorage.getAccountRewards()[rewardToken][account] = 0;
} else {
emit VaultRewardTransfer(rewardToken, account, 0); // @audit-issue - M - missing recovery mechanism
+ VaultStorage.getAccountRewards()[rewardToken][account] = rewardToClaim;
}
// Emits zero tokens transferred if the transfer fails.
} catch {
emit VaultRewardTransfer(rewardToken, account, 0);
+ VaultStorage.getAccountRewards()[rewardToken][account] = rewardToClaim;
}
}
1 comment(s) were left on this issue during the judging contest.
Hash01011122 commented:
Invalid, No possible attack path mentioned nor justified how insufficient reward token balances scenario can take place
sherlock-admin4
changed the title
Salty Midnight Loris - Loss of user-earned rewards due to lack of recovery mechanism and insufficient reward token balances in the VaultRewarderLib _claimRewardToken() function
eeyore - Loss of user-earned rewards due to lack of recovery mechanism and insufficient reward token balances in the VaultRewarderLib _claimRewardToken() function
Jul 11, 2024
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelMediumA valid Medium severity issueRewardA payout will be made for this issue
eeyore
Medium
Loss of user-earned rewards due to lack of recovery mechanism and insufficient reward token balances in the VaultRewarderLib _claimRewardToken() function
Summary
The protocol zeroes out user-earned rewards if there are no reward tokens to transfer within the vault contract.
Vulnerability Detail
In the
VaultRewarderLib
, users can earn extra APR from incentive tokens provided by booster protocols. There are three ways these incentive tokens can be handled:emissionRatePerYear
per vault share. These tokens are not claimed but are transferred to the vault externally.These methods can be used in combinations.
In the default flow, vaults operate in the Automatic Reinvest mode, ensuring there is no loss for the user as rewards are reinvested into the vault.
However, when Account Claim and Emission Rate are used together, users may unexpectedly lose rewards because their rewards are zeroed out during the claim process. This happens because rewards for the Emission Rate are transferred to the vault indirectly and are not reserved for the user. In the worst case, the
emissionRatePerYear
can be set, but rewards may never be transferred into the vault.If the rewards for the Emission Rate are not transferred, users who claim rewards first will cannibalize the rewards from the Account Claim functionality, leaving later users without rewards from either source. Essentially, rewards are distributed on a first-come, first-served basis.
In the
_claimRewardToken()
function, where reward tokens should be transferred to the users, the user rewards are zeroed if there is an insufficient balance of reward tokens, and information about untransferred/pending rewards is not stored.Token transfers are most likely to fail due to a lack of balances rather than blacklisting. Valid, non-blacklisted users should not suffer losses because of this. This leads to a direct loss of funds/rewards for the users and unfair reward distribution.
Impact
User-earned rewards are lost both from Account Claim and Emission Rate.
Code Snippet
https://github.com/sherlock-audit/2024-06-leveraged-vaults/blob/main/leveraged-vaults-private/contracts/vaults/common/VaultRewarderLib.sol#L295-L328
Tool used
Manual Review
Recommendation
In case of a transfer failure, which is most likely due to a lack of balances, the unclaimed value should be stored for future attempts if rewards for the Emission Rate are transferred later.
Introduce new storage values and update the functions as in this example:
/** Reward Claim Methods **/ function _getRewardsToClaim( address rewardToken, address account, uint256 vaultSharesBefore, uint256 rewardsPerVaultShare ) internal view returns (uint256 rewardToClaim) { // Vault shares are always in 8 decimal precision rewardToClaim = ( (vaultSharesBefore * rewardsPerVaultShare) / uint256(Constants.INTERNAL_TOKEN_PRECISION) ) - VaultStorage.getAccountRewardDebt()[rewardToken][account] + + VaultStorage.getAccountRewards()[rewardToken][account]; }
Duplicate of #1
The text was updated successfully, but these errors were encountered: