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: 38/114
Findings: 1
Award: $285.37
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: vakzz
Also found by: 0xStalin, 0xWaitress, SpicyMeatball
285.3731 USDC - $285.37
https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L310-L318
updateBucketExchangeRatesAndClaim has no check on the address of the pool.
malicious caller can pass in an arbitrary address of the pool, leading to a call to pass and updating a non-existing pool for _updateBucketExchangeRates
and _updateBucketExchangeRate
, subsequently inflating the updateRewardsClaimed
of the curBurnEpoch.
function updateBucketExchangeRatesAndClaim( address pool_, uint256[] calldata indexes_ ) external override returns (uint256 updateReward) { updateReward = _updateBucketExchangeRates(pool_, indexes_); // transfer rewards to sender _transferAjnaRewards(updateReward); }
https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L310-L318
_updateBucketExchangeRates
, first an external view to the currentBurnEpoch()
is called, which the malicious caller can return anything.function _updateBucketExchangeRates( address pool_, uint256[] memory indexes_ ) internal returns (uint256 updatedRewards_) { // get the current burn epoch from the given pool uint256 curBurnEpoch = IPool(pool_).currentBurnEpoch();
https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L671-L735
updateRewardsClaimed
, he would return a non-zero value for curBurnEpoch. Then _getPoolAccumulators
and _updateBucketExchangeRateAndCalculateRewards
are called.else { // retrieve accumulator values used to calculate rewards accrued ( uint256 curBurnTime, uint256 totalBurned, uint256 totalInterestEarned ) = _getPoolAccumulators(pool_, curBurnEpoch, curBurnEpoch - 1); if (block.timestamp <= curBurnTime + UPDATE_PERIOD) { // update exchange rates and calculate rewards if tokens were burned and within allowed time period for (uint256 i = 0; i < indexes_.length; ) { // calculate rewards earned for updating bucket exchange rate updatedRewards_ += _updateBucketExchangeRateAndCalculateRewards( pool_, indexes_[i], curBurnEpoch, totalBurned, totalInterestEarned ); // iterations are bounded by array length (which is itself bounded), preventing overflow / underflow unchecked { ++i; } } uint256 rewardsCap = Maths.wmul(UPDATE_CAP, totalBurned); uint256 rewardsClaimedInEpoch = updateRewardsClaimed[curBurnEpoch]; // update total tokens claimed for updating bucket exchange rates tracker if (rewardsClaimedInEpoch + updatedRewards_ >= rewardsCap) { // if update reward is greater than cap, set to remaining difference updatedRewards_ = rewardsCap - rewardsClaimedInEpoch; } // accumulate the full amount of additional rewards updateRewardsClaimed[curBurnEpoch] += updatedRewards_; }
_getPoolAccumulators
has no check on _pool as an valid input
https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L636-L661
function _getPoolAccumulators( address pool_, uint256 currentBurnEventEpoch_, uint256 lastBurnEventEpoch_ ) internal view returns (uint256, uint256, uint256) { ( uint256 currentBurnTime, uint256 totalInterestLatest, uint256 totalBurnedLatest ) = IPool(pool_).burnInfo(currentBurnEventEpoch_); ( , uint256 totalInterestAtBlock, uint256 totalBurnedAtBlock ) = IPool(pool_).burnInfo(lastBurnEventEpoch_); uint256 totalBurned = totalBurnedLatest != 0 ? totalBurnedLatest - totalBurnedAtBlock : totalBurnedAtBlock; uint256 totalInterest = totalInterestLatest != 0 ? totalInterestLatest - totalInterestAtBlock : totalInterestAtBlock; return ( currentBurnTime, totalBurned, totalInterest ); }
_updateBucketExchangeRateAndCalculateRewards
has no check on _pool as an valid input
https://github.com/code-423n4/2023-05-ajna/blob/main/ajna-core/src/RewardsManager.sol#L768-L804
function _updateBucketExchangeRateAndCalculateRewards( address pool_, uint256 bucketIndex_, uint256 burnEpoch_, uint256 totalBurned_, uint256 interestEarned_ ) internal returns (uint256 rewards_) { uint256 burnExchangeRate = bucketExchangeRates[pool_][bucketIndex_][burnEpoch_]; ...
validate pool address is an valid input.
Rug-Pull
#0 - c4-judge
2023-05-18T10:26:41Z
Picodes marked the issue as primary issue
#1 - c4-sponsor
2023-05-19T19:03:44Z
MikeHathaway marked the issue as sponsor confirmed
#2 - c4-judge
2023-05-30T19:15:52Z
Picodes marked the issue as partial-50
#3 - Picodes
2023-05-30T19:16:19Z
Insufficient description of the impact and justification for the severity level
#4 - c4-judge
2023-05-30T19:17:31Z
Picodes marked issue #8 as primary and marked this issue as a duplicate of 8