Open sherlock-admin2 opened 8 months ago
1 comment(s) were left on this issue during the judging contest.
takarez commented:
valid: users that stakes immediately will be accounted for during the reward distribution; medium as there is no loss of funds; medium(4)
rewardValidators()
given staking contract is deployed on mainnetIn the simple demonstration below, we show that an attacker _BOB may steal ~66% of Validator _ALICE's rewards by frontrunning an incoming call to rewardValidators(uint128,uint128[],uint128[]):
https://github.com/covalenthq/cqt-staking/pull/125/commits/a609cca0426cb22cbf5064212341c14c288efeda for 2.
Fix LGTM
The protocol team fixed this issue in PR/commit https://github.com/covalenthq/cqt-staking/commit/a609cca0426cb22cbf5064212341c14c288efeda.
The Lead Senior Watson signed off on the fix.
PUSH0
high
New staking between reward epochs will dilute rewards for existing stakers. Anyone can then front-run
OperationalStaking.rewardValidators()
to steal rewardsSummary
In the Covalent Network, validators perform work to earn rewards, which is distributed through
OperationalStaking
. The staking manager is expected to regularly invokerewardValidators()
to distribute staking rewards accordingly to the validator, and its delegators.However, the function takes into account all existing stakes, including new ones. This makes newer stakes being counted equally to existing stakes, despite newer stakes haven't existed for a working epoch yet.
An attacker can also then front-run
rewardValidators()
to steal a share of the rewards. The attacker gains a share of the reward, despite not having fully staked for the corresponding epoch.Vulnerability Detail
The function
rewardValidators()
is callable only by the staking manager to distribute rewards to validators. The rewards is then immediately distributed to the validator, and all their delegators, proportional to the amount staked.However, any new staking in-between reward epochs still counts as staking. They receive the full reward amount for the epoch, despite not having staked for the full epoch.
An attacker can abuse this by front-run the staking manager's distribution with a stake transaction. The attacker stakes a certain amount right before the staking manager distributes rewards, then the attacker is already considered to have a share of the reward, despite not having staked during the epoch they were entitled to.
This also applies to re-stakings, i.e. unstaked tokens that are re-staked into the same validator: Any stake recovers made through
recoverUnstaking()
is considered a new stake. Therefore an attacker can use the same funds to repeatedly perform the attack.Proof of concept
recoverUnstaking()
.Impact
Unfair distribution of rewards. New stakers get full rewards for epochs they didn't fully stake into.
Code Snippet
https://github.com/sherlock-audit/2023-11-covalent/blob/main/cqt-staking/contracts/OperationalStaking.sol#L262
Tool used
Manual Review
Recommendation
Use a checkpoint-based shares computation. An idea can be as follow: