Open code423n4 opened 1 year ago
thereksfour marked the issue as primary issue
This issue is mentioned in #41 as MorphoTokenisedDeposit::rewardTokenBalance
behavior can be affected by reentrancy though RewardableERC20::_claimAssetRewards
thereksfour changed the severity to QA (Quality Assurance)
The attack is not profitable, consider QA.
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. AndbalanceAfterClaimingRewards
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.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 and it 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