The rewards might be locked inside the contract due to the reentrancy attack. #28
Labels
bug
Something isn't working
downgraded by judge
Judge downgraded the risk level of this issue
grade-a
primary issue
Highest quality submission among a set of duplicates
QA (Quality Assurance)
Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax
Lines of code
https://github.com/reserve-protocol/protocol/blob/9ee60f142f9f5c1fe8bc50eef915cf33124a534f/contracts/plugins/assets/erc20/RewardableERC20.sol#L111
Vulnerability details
Impact
The stakers might lose some rewards and the rewards might be locked inside the contract forever.
Proof of Concept
In
RewardableERC20
, users can claim their accumulated rewards usingclaimRewards()
.It claims and sync rewards from the staking contract first and transfers the rewards to users. Also, it has a
nonReentrant
modifier to prevent possible reentrancy approaches.Due to this
nonReentrant
modifier, the reentrancy attack is impossible withclaimRewards()
only but users can call deposit() or withdraw inside the hook.As a result, the attackers might manipulate
lastRewardBalance
like the below.rewardToken
is an ERC777 token with the_afterTokenTransfer
hook.claimRewards()
. Then_claimAccountRewards()
is called after syncing rewards from the staking contract._claimAccountRewards()
, let's considerlastRewardBalance = 100, claimableRewards = 10
.deposit()
and_claimAndSyncRewards()
will be called within _beforeTokenTransfer()._claimAndSyncRewards()
,lastRewardBalance
will be 100 as it isn't updated in_claimAccountRewards()
yet.And
balanceAfterClaimingRewards
will be100 - 10 + newlyClaimedRewards
becauseclaimableRewards = 10
was transfered already in_claimAccountRewards()
.newlyClaimedRewards
is positive after the second call of _claimAssetRewards within the same transaction. It's becauseclaimRewards()
calls this function.This assumption might be possible to happen if the reward contract has a minimum threshold of deposit amount and it transfers the rewards during the second
_claimAssetRewards
after the attacker meets the threshold condition by depositing more funds.balanceAfterClaimingRewards = 90 + 10 = 100
and _rewardsPerShare won't be increased becausebalanceAfterClaimingRewards == _previousBalance
although it has claimed new rewards.As a result, 10 rewards will be locked inside the contract.
So there are 2 assumptions to make this attack possible.
rewardToken
is an ERC777 token. I think it's not so strong assumption becauseclaimRewards()
has anonReentrant
modifier already with this assumption._claimAssetRewards()
claims some rewards during the second call in the same transaction after new deposit andit might be possible during the implementation for existing virtual functions.
Tools Used
Manual Review
Recommended Mitigation Steps
_claimAndSyncRewards()
and_claimAccountRewards()
should have anonReentrant
modifier individually instead ofclaimRewards
.Assessed type
Reentrancy
The text was updated successfully, but these errors were encountered: