Failing to update rewards before executing the handleIncomingUpdate function in SDLPoolPrimary, while adjusting the effectiveBalance of the ccipController, results in miscalculated rewards. This oversight can obstruct the distribution of rewards for the secondary chain.
Actions taken in the secondary pool are queued and then communicated to the primary pool. The primary pool must acknowledge these changes before they are executed. The message sent to the primary pool includes the number of new queued locks to be minted (numNewQueuedLocks) and the change in the reSDL supply (reSDLSupplyChange).
Upon receiving the message, the SDLPoolCCIPControllerPrimary contract updates the reSDLSupplyByChain and forwards the message to SDLPoolPrimary. The SDLPoolPrimary contract then processes the message, returning the mintStartIndex for the secondary chain to use when minting new locks. It also updates the effectiveBalances for the ccipController and the totalEffectiveBalance.
The issue arises because the handleIncomingUpdate function does not update the rewards before altering these values. Since these values directly affect reward accounting, failing to update them leads to incorrect calculations. This could result in a scenario where the total rewards accrued by all stakers exceed the available balance in the rewardsPool.
For example, consider Alice has staked 500 sdl tokens, and there is an outgoing 1000 reSdl. The state would be as follows:
effectiveBalance[alice] = 500
effectiveBalance[ccipController] = 1000
totalEffectiveBalance = 1500
Now, assume 1500 reward tokens are distributed this will update the rewardPerToken = 1 (rewards/totalStaked), and Alice will withdraw her rewards. The amount of rewards Alice receives is calculated using the withdrawableRewards function, which relies on her effectiveBalance (controller.staked()). With a rewardPerToken of 1 and Alice's userRewardPerTokenPaid at 0, Alice would receive 500 rewards.
now, someone stakes another 1000 sdl on the secondary chain, an incoming update with a supply change of 1000 is received on the primary chain. This update changes the effectiveBalance[ccipController] to 2000 without a prior reward update which will keep the userRewardPerTokenPaid for ccipController 0.
Consequently, when the RewardsInitiator contract calls the distributeRewards function in SDLPoolCCIPControllerPrimary, attempting to withdrawRewards from the rewardPool the call will perpetually fail. The rewards for the ccipController would be calculated as 2000 * (1 - 0) = 2000 rewards, while the actual balance of the rewardsPool is only 1000 rewards.
notice that the increase of 1000 will never be solved .
here a poc that shows , that not updating reward in incomingUpdates , will cause the distributeReward function to revert , cause of insufficient balance in the reward pool , i used the repo setup :
Incorrect reward calculations could prevent rightful stakers from receiving their due rewards or leave unclaimable rewards in the pool (in the case of negative supply change), thereby compromising the protocol's credibility.
manual review
Implement an updateReward(ccipController) call within the handleIncomingUpdate function to ensure rewards are recalculated whenever effectiveBalance changes. This will prevent miscalculations and maintain reward distribution accuracy.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.