Platform: Code4rena
Start Date: 03/05/2023
Pot Size: $60,500 USDC
Total HM: 25
Participants: 114
Period: 8 days
Judge: Picodes
Total Solo HM: 6
Id: 234
League: ETH
Rank: 9/114
Findings: 2
Award: $1,135.45
π Selected for report: 1
π Solo Findings: 0
π Selected for report: Jorgect
Also found by: ABAIKUNANBAEV, shealtielanz
1099.2149 USDC - $1,099.21
User can claim rewards even when is already claimed
The _claimRewards function is using to calculate and send the reward to the caller but this function is no validating if isEpochClaimed mapping is true due that in claimRewards function is validated, see the stament in the following lines:
file: ajna-core/src/RewardsManager.sol function claimRewards( uint256 tokenId_, uint256 epochToClaim_ ) external override { StakeInfo storage stakeInfo = stakes[tokenId_]; if (msg.sender != stakeInfo.owner) revert NotOwnerOfDeposit(); if (isEpochClaimed[tokenId_][epochToClaim_]) revert AlreadyClaimed(); // checking if the epoch was claimed; _claimRewards( stakeInfo, tokenId_, epochToClaim_, true, stakeInfo.ajnaPool ); }
Now the moveStakedLiquidity is calling _claimRewards too without validate isEpochClaimed mapping:
file: ajna-core/src/RewardsManager.sol function moveStakedLiquidity( uint256 tokenId_, uint256[] memory fromBuckets_, uint256[] memory toBuckets_, uint256 expiry_ ) external override nonReentrant { StakeInfo storage stakeInfo = stakes[tokenId_]; if (msg.sender != stakeInfo.owner) revert NotOwnerOfDeposit(); uint256 fromBucketLength = fromBuckets_.length; if (fromBucketLength != toBuckets_.length) revert MoveStakedLiquidityInvalid(); address ajnaPool = stakeInfo.ajnaPool; uint256 curBurnEpoch = IPool(ajnaPool).currentBurnEpoch(); // claim rewards before moving liquidity, if any _claimRewards(stakeInfo, tokenId_, curBurnEpoch, false, ajnaPool); // no checking is isEpochClaimed is true and revert
Also we can see in the _claimRewards function there is no validation is isEpochClaimed is true, this allow a malicius user claimReward first and then move his liquidity to other bucket or the same bucket claiming the reward each time that he want.
function _claimRewards( StakeInfo storage stakeInfo_, uint256 tokenId_, uint256 epochToClaim_, bool validateEpoch_, address ajnaPool_ ) internal { // revert if higher epoch to claim than current burn epoch if ( validateEpoch_ && epochToClaim_ > IPool(ajnaPool_).currentBurnEpoch() ) revert EpochNotAvailable(); // update bucket exchange rates and claim associated rewards uint256 rewardsEarned = _updateBucketExchangeRates( ajnaPool_, positionManager.getPositionIndexes(tokenId_) ); rewardsEarned += _calculateAndClaimRewards(tokenId_, epochToClaim_); uint256[] memory burnEpochsClaimed = _getBurnEpochsClaimed( stakeInfo_.lastClaimedEpoch, epochToClaim_ ); emit ClaimRewards( msg.sender, ajnaPool_, tokenId_, burnEpochsClaimed, rewardsEarned ); // update last interaction burn event stakeInfo_.lastClaimedEpoch = uint96(epochToClaim_); // transfer rewards to sender _transferAjnaRewards(rewardsEarned); }
manual
check if the isEpochClaime is true and revert in the _claimReward function
if (isEpochClaimed[tokenId_][epochToClaim_]) revert AlreadyClaimed();
Invalid Validation
#0 - c4-judge
2023-05-18T16:13:34Z
Picodes marked the issue as duplicate of #316
#1 - c4-judge
2023-05-30T22:02:59Z
Picodes marked the issue as selected for report
#2 - c4-judge
2023-05-31T14:06:44Z
Picodes marked the issue as satisfactory
#3 - c4-sponsor
2023-06-26T23:46:37Z
ith-harvey marked the issue as sponsor confirmed
#4 - ith-harvey
2023-06-28T14:55:07Z
The series of calls they are suggesting are possible: stake claimRewards() -> get rewards moveStakedLiquidity() -> get rewards
They should not be able to get these rewards because _calculateAndClaimRewards()
iterates from last claimed epoch.
#5 - c4-sponsor
2023-06-28T14:56:47Z
ith-harvey marked the issue as sponsor disputed
π Selected for report: rbserver
Also found by: 0xnev, ABAIKUNANBAEV, Audit_Avengers, Aymen0909, BGSecurity, BRONZEDISC, Bason, DadeKuma, GG_Security, Jerry0x, Jorgect, MohammedRizwan, REACH, Sathish9098, Shogoki, T1MOH, UniversalCrypto, aviggiano, ayden, berlin-101, bytes032, codeslide, descharre, fatherOfBlocks, hals, kaveyjoe, kodyvim, lfzkoala, lukris02, nadin, naman1778, patitonar, pontifex, sakshamguruji, squeaky_cactus, teawaterwire, wonjun, yjrwkk
36.2377 USDC - $36.24
SafeERC20 its imported and declared in the code(using SafeERC20 for ERC20) but itΒ΄s no already used in the code.
#0 - c4-judge
2023-05-18T19:20:10Z
Picodes marked the issue as grade-c
#1 - c4-judge
2023-05-18T19:20:49Z
Picodes marked the issue as grade-b