Pre-auditThree days of senior audit time for $1,500, credited in full toward your Zealynx audit.3 audit days, credited toward your auditSee how →
C-02·

PLP transfer or balance updates do not update reward index

Not reportedoptionsleverageblack-scholes
TL;DR

The reward calculation in _getPendingRewards depends on two key factors: the user's PLP token balance and their reward index (usrRwdIndexQ128).

Severity
CRITICAL
Method
MManual review
CAT.
02Section · Severity

Severity

Impact: High

Likelihood: High

03Section · Description

Description

Link

The reward calculation in _getPendingRewards depends on two key factors: the user's PLP token balance and their reward index (usrRwdIndexQ128).

solidity
function _getPendingRewards(address token, address user) internal view returns (uint256) {
RewardInfo storage rewards = PoolStorage.layout().ledger.state.rewardsByToken[token];
ILPToken plp_ = ILPToken(PoolStorage.layout().setUp.base.plp);
uint256 lpBalance = plp_.balanceOf(user);
uint8 plpDecimals = IERC20Metadata(PoolStorage.layout().setUp.base.plp).decimals();
uint8 tokenDecimals = IERC20Metadata(token).decimals();
uint256 rewardIndexQ128 = PoolStorage.layout().ledger.state.usrRwdIndexQ128[token][user];
rewardIndexQ128 = rewards.rewardsPerLPQ128 - rewardIndexQ128;
return lpBalance.mulDiv(rewardIndexQ128, Q128).toDecimals(plpDecimals, tokenDecimals);
}

While the PLP balance can change through various operations (addLiquidity, withdrawLiquidity, or direct token transfers), the reward index is only updated in specific scenarios. This could lead to incorrect accounting for the rewards during PLP Token Transfers Or Other Balance Updates(like partial liquidity withdraw).

solidity
// Current update points for usrRwdIndexQ128:
// 1. First deposit (_initRewardIndex)
// 2. Full exit (_initRewardIndex)
// 3. Manual claim (_claimRewards)
function _updateLpRewardIndex(address token, address user) internal {
PoolStorage.layout().ledger.state.usrRwdIndexQ128[token][user] =
PoolStorage.layout().ledger.state.rewardsByToken[token].rewardsPerLPQ128;
}

In such cases, users can manipulate their rewards by transferring PLP tokens without updating their usrRwdIndexQ128:

  • A malicious user could transfer a large amount of PLP tokens to themselves before claiming rewards.
  • The calculation lpBalance.mulDiv(rewardIndexQ128, Q128) would use the inflated balance.
04Section · Recommendations

Recommendations

Implement a hook/accounting system to update the reward index whenever PLP token balances change.

05Section · Report provenance

Report provenance

Published by Pashov Audit Group, with Carlos (Bloqarl) participating as a contractor. The source report states that its fixes review was not fully completed; no final remediation disposition is recorded for this finding. Read the full report.

C-02