Permanent loss of rewards on temporary underfunding of RewardsManager
contract
#114
Labels
3 (High Risk)
Assets can be stolen/lost/compromised directly
bug
Something isn't working
duplicate-251
satisfactory
satisfies C4 submission criteria; eligible for awards
Lines of code
https://github.com/code-423n4/2023-05-ajna/blob/276942bc2f97488d07b887c8edceaaab7a5c3964/ajna-core/src/RewardsManager.sol#L811-L821
Vulnerability details
Impact
L815 in the _transferAjnaRewards() method of the
RewardsManager
contract silently trims every reward, that is about to be transferred, down to the actual balance of the contract instead of reverting the transaction due to lack of funds. It's clear that this was added with good intentions and is only a problem when theRewardsManager
contract is underfunded, but the consequences are severe nevertheless._transferAjnaRewards() is called at 4 instances in the
RewardsManager
contract but the consequences become clearest through the claimRewards() and further _claimRewards() methods. Both of these methods take measures that the same rewards can only be claimed once, see L122 and L594.Therefore calling claimRewards() during a period where the
RewardsManager
contract is underfunded leads to the following outcomes:1: Rewards are silently trimmed before transfer -> receive less than expected without revert
2: Event ClaimRewards() is emitted as if full rewards were paid -> misleading the front-end
3: Transaction succeeds, internal accounting "thinks" rewards were claimed -> re-try to claim rewards reverts with
AlreadyClaimed()
errorThe result is a permanent loss of rewards (up to the specified epoch) for the user while pretending the rewards were successfully claimed via event (log).
Proof of Concept
The following PoC, based on an existing test case, demonstrates that a user, who tries to claim rewards during temporary underfunding, gets less than expected rewards while being misled by the
ClaimRewards()
event. When re-attempting to claim the full rewards, theAlreadyClaimed()
error arises, i.e. permanent loss up to the specified epoch.Just apply the diffs below and run the modified test case with
forge test --match testMultiPeriodRewardsSingleClaimLoss
:Tools Used
VS Code, Foundry
Recommended Mitigation Steps
Since the internal accounting is not suited to handle partial claims, I recommend to let the transaction fail on lack of funds, see the diff below. This way, the user does not lose the eligibility to claim rewards and can try again once the
RewardsManager
is properly funded.Assessed type
Token-Transfer
The text was updated successfully, but these errors were encountered: