Summary
The CommunityVault::claimRewards
function is intended to claim the yielded rewards from the Chainlink staking program, but instead it transfers the full balance of token
held by CommunityVault
.
Vulnerability Details
* @notice Claims rewards from the Chainlink staking contract
* @param _minRewards min amount of rewards required to claim
* @param _rewardsReceiver address to receive rewards
**/
function claimRewards(
uint256 _minRewards,
address _rewardsReceiver
) external onlyVaultController {
uint256 rewards = getRewards();
if (rewards != 0 && rewards >= _minRewards) {
rewardsController.claimReward();
token.safeTransfer(_rewardsReceiver, token.balanceOf(address(this)));
}
}
https://github.com/Cyfrin/2024-09-stakelink/blob/ea5574ebce3a86d10adc2e1a5f6d5512750f7a72/contracts/linkStaking/CommunityVault.sol#L34C1-L48C6
Impact
As you can see, it is not the getRewards
rewards
amount that is transferred to the _rewardsReceiver
, but instead the whole token
balance held in the contract.
The getRewards
function is inherited from the parent Vault
contract, and it determines how many rewards have accumulated thanks to the Chainlink restaking program:
* @notice Returns the claimable rewards balance of this contract in the Chainlink staking rewards contract
* @return rewards balance
*/
function getRewards() public view returns (uint256) {
return rewardsController.getReward(address(this));
}
https://github.com/Cyfrin/2024-09-stakelink/blob/ea5574ebce3a86d10adc2e1a5f6d5512750f7a72/contracts/linkStaking/base/Vault.sol#L105C1-L111C6
That means that when the vault controller calls claimRewards
, he will withdraw not only the rewards
amount that the vault is eligible for, but the full token
amount held by the CommunityVault
contract, therefore withdrawing in excess.
Tools Used
Manual review.
Recommendations
Consider only transferring the rewards
amount that is intended to represent the real claimable amount of token
, instead of transferring the full balance of the address(this)
contract, by changing the code of the claimRewards
function accordingly:
/**
* @notice Claims rewards from the Chainlink staking contract
* @param _minRewards min amount of rewards required to claim
* @param _rewardsReceiver address to receive rewards
**/
function claimRewards(
uint256 _minRewards,
address _rewardsReceiver
) external onlyVaultController {
uint256 rewards = getRewards();
if (rewards != 0 && rewards >= _minRewards) {
rewardsController.claimReward();
- token.safeTransfer(_rewardsReceiver, token.balanceOf(address(this)));
+ token.safeTransfer(_rewardsReceiver, rewards);
}
}