Platform: Code4rena
Start Date: 28/09/2023
Pot Size: $36,500 USDC
Total HM: 5
Participants: 115
Period: 6 days
Judge: 0xDjango
Total Solo HM: 1
Id: 290
League: ETH
Rank: 113/115
Findings: 1
Award: $4.37
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Bauchibred
Also found by: 0x3b, 0xDetermination, 0xMosh, 0xScourgedev, 0xTheC0der, 0xTiwa, 0xWaitress, 0xdice91, 0xfusion, 0xpiken, 0xprinc, 0xweb3boy, ArmedGoose, Aymen0909, Breeje, Brenzee, Daniel526, DavidGiladi, DeFiHackLabs, Flora, Fulum, HChang26, Hama, IceBear, J4X, Krace, KrisApostolov, Maroutis, Mirror, MohammedRizwan, Norah, PwnStars, SPYBOY, TangYuanShen, Testerbot, ThreeSigma, Tricko, al88nsk, alexweb3, ast3ros, berlin-101, bin2chen, blutorque, btk, d3e4, deth, e0d1n, ether_sky, ge6a, gkrastenov, glcanvas, hals, imare, inzinko, jkoppel, jnforja, joaovwfreire, josephdara, kutugu, lotux, lsaudit, mahdirostami, merlin, n1punp, nadin, neumo, nisedo, nobody2018, oakcobalt, orion, peanuts, pep7siup, pina, ptsanev, rokinot, rvierdiiev, said, santipu_, sashik_eth, seerether, squeaky_cactus, terrancrypt, tonisives, twicek, vagrant, xAriextz, y4y
4.3669 USDC - $4.37
releaseFunds
function does not check if the current accrued amount is zero and calls safeTransfer
in any case:
File: PrimeLiquidityProvider.sol 192: function releaseFunds(address token_) external { 193: if (msg.sender != prime) revert InvalidCaller(); ... 199: uint256 accruedAmount = tokenAmountAccrued[token_]; 200: tokenAmountAccrued[token_] = 0; ... 204: IERC20Upgradeable(token_).safeTransfer(prime, accruedAmount); 205: }
So, in case the reward token does not allow 0 zero transfers (for example, BNB token on mainnet) and the current accrued amount is zero, this function would revert causing DOS on external caller - Prime#_claimInterest
.
Recommendation: Consider adding a check that the transferred amount is > 0 and transfer tokens only in this case.
block.number
for reward calculation may pose security risks on Layer 2 (L2) chains.In the PrimeLiquidityProvider
contract, the accrueTokens
function utilizes the block.number
variable to calculate accrued token amounts. This is achieved by tracking the number of blocks elapsed since the last accrual:
File: PrimeLiquidityProvider.sol 249: function accrueTokens(address token_) public { ... 254: uint256 blockNumber = getBlockNumber(); 255: uint256 deltaBlocks = blockNumber - lastAccruedBlock[token_]; ...
Also, the Prime
contract includes a BLOCKS_PER_YEAR
immutable value that represents the expected number of blocks that would be produced on the current chain. Later this immutable variable was used in _incomeDistributionYearly
function:
File: Prime.sol 970: function _incomeDistributionYearly(address vToken) internal view returns (uint256 amount) { 971: uint256 totalIncomePerBlockFromMarket = _incomePerBlock(vToken); 972: uint256 incomePerBlockForDistributionFromMarket = (totalIncomePerBlockFromMarket * _distributionPercentage()) / 973: IProtocolShareReserve(protocolShareReserve).MAX_PERCENT(); 974: amount = BLOCKS_PER_YEAR * incomePerBlockForDistributionFromMarket; 975: 976: uint256 totalIncomePerBlockFromPLP = IPrimeLiquidityProvider(primeLiquidityProvider) 977: .getEffectiveDistributionSpeed(_getUnderlying(vToken)); 978: amount += BLOCKS_PER_YEAR * totalIncomePerBlockFromPLP; 979: }
While it is a common practice to use block.number
(or expected rate of blocks per period) for various accounting purposes, it's important to note that this approach may become problematic on Layer 2 chains. The behavior of block.number
on Layer 2 chains can differ significantly from the Ethereum mainnet, and changes in this behavior may occur in the future. For instance, zkSync recently experienced changes in block.number
production rates, as documented here: https://github.com/zkSync-Community-Hub/zkync-developers/discussions/87
Recommendation: Consider using block.timestamp
as a measure of time instead of block.number
for reward calculations. Unlike block.number
, block.timestamp
is less likely to exhibit significant variations, even in the event of changes on the underlying blockchain.
Prime#claimInterest
The Prime#claimInterest
function allows for the claiming of interest on behalf of an arbitrary user address:
File: Prime.sol 443: function claimInterest(address vToken, address user) external whenNotPaused returns (uint256) { 444: return _claimInterest(vToken, user); 445: }
While this function does not permit attackers to steal or lock a user's interest, it does allow them to influence the timing of when interest is claimed. This capability could potentially create issues in scenarios such as tax accounting, where the date of funds arrival affects the amount of taxes to be paid.
Recommendation: Consider to restrict the use of the Prime#claimInterest function to only the authorized owner or designated users.
Prime#accrueInterest
functionThe Prime#accrueInterest function is responsible for accounting accrued interest on each vToken. It increases the rewardIndex for each market by calculating a delta value based on distributionIncome and the sumOfMembersScore of the market. However, there is a potential issue where, if distributionIncome is sufficiently small while sumOfMembersScore is large, the calculation at line 585 could result in a delta value of 0. This means that the rewardIndex would not increase, even though unreleased amounts are properly accounted for. A malicious actor could exploit this by frequently calling the accrueInterest function to prevent rewardIndex from increasing, potentially interfering with the distribution of rewards to Prime holders:
File: Prime.sol 554: function accrueInterest(address vToken) public { ... 568: uint256 distributionIncome = totalIncomeUnreleased - unreleasedPSRIncome[underlying]; 569: 570: _primeLiquidityProvider.accrueTokens(underlying); 571: uint256 totalAccruedInPLP = _primeLiquidityProvider.tokenAmountAccrued(underlying); 572: uint256 unreleasedPLPAccruedInterest = totalAccruedInPLP - unreleasedPLPIncome[underlying]; 573: 574: distributionIncome += unreleasedPLPAccruedInterest; ... 580: unreleasedPSRIncome[underlying] = totalIncomeUnreleased; 581: unreleasedPLPIncome[underlying] = totalAccruedInPLP; 582: 583: uint256 delta; 584: if (markets[vToken].sumOfMembersScore > 0) { 585: delta = ((distributionIncome * EXP_SCALE) / markets[vToken].sumOfMembersScore); // @audit-issue medium? could be 0 ever? 586: } 587: 588: markets[vToken].rewardIndex = markets[vToken].rewardIndex + delta; 589: }
Recommendation: Consider updating unreleasedPSRIncome
and unreleasedPLPIncome
only in cases if delta > 0.
The Prime#_executeBoost
function is responsible for executing a boost for a user within a specific vToken market. It accrues interest for the user and updates their accrued interest based on rewardIndex
delta and the user's score. However, there is a potential issue where, if the delta of rewardIndex
change multiplied by the user's score is less than EXP_SCALE
, the attacker could frequently call the accrueInterestAndUpdateScore
function and avoid the user from accruing rewards.
File: Prime.sol 918: function _interestAccrued(address vToken, address user) internal view returns (uint256) { 919: uint256 index = markets[vToken].rewardIndex - interests[vToken][user].rewardIndex; 920: uint256 score = interests[vToken][user].score; 921: 922: return (index * score) / EXP_SCALE; 923: } 924: ... 779: function _executeBoost(address user, address vToken) internal { 780: if (!markets[vToken].exists || !tokens[user].exists) { 781: return; 782: } 783: 784: accrueInterest(vToken); 785: interests[vToken][user].accrued += _interestAccrued(vToken, user); 786: interests[vToken][user].rewardIndex = markets[vToken].rewardIndex; 787: }
In case if delta of rewardIndex change multiplied to user score is less than EXP_SCALE
Attacker could frequently call accrueInterestAndUpdateScore
function and avoid user from accruing rewards.
Recommendation: consider updating the _executeBoost
function to update the rewardIndex
value only if the accrued amount for the user is greater than zero.
#0 - 0xRobocop
2023-10-07T18:15:05Z
L-02 Dup of #132
#1 - c4-pre-sort
2023-10-07T18:15:10Z
0xRobocop marked the issue as low quality report
#2 - c4-judge
2023-11-03T02:12:10Z
fatherGoose1 marked the issue as grade-b