Vulnerability Details
Whitelisted chains are added and removed using SDLPoolCCIPControllerPrimary::addWhitelistedChain()
and SDLPoolCCIPControllerPrimary::removeWhitelistedChain()
respectively.
SDLPoolCCIPControllerPrimary.sol#L144-L164
function addWhitelistedChain(
uint64 _chainSelector,
address _destination,
bytes calldata _updateExtraArgs,
bytes calldata _rewardsExtraArgs
) external onlyOwner {
if (whitelistedDestinations[_chainSelector] != address(0)) revert AlreadyAdded();
if (_destination == address(0)) revert InvalidDestination();
whitelistedChains.push(_chainSelector);
whitelistedDestinations[_chainSelector] = _destination;
updateExtraArgsByChain[_chainSelector] = _updateExtraArgs;
rewardsExtraArgsByChain[_chainSelector] = _rewardsExtraArgs;
emit ChainAdded(_chainSelector, _destination, _updateExtraArgs, _rewardsExtraArgs);
}
SDLPoolCCIPControllerPrimary.sol#L166-L184
function removeWhitelistedChain(uint64 _chainSelector) external onlyOwner {
if (whitelistedDestinations[_chainSelector] == address(0)) revert InvalidDestination();
emit ChainRemoved(_chainSelector, whitelistedDestinations[_chainSelector]);
for (uint256 i = 0; i < whitelistedChains.length; ++i) {
if (whitelistedChains[i] == _chainSelector) {
whitelistedChains[i] = whitelistedChains[whitelistedChains.length - 1];
whitelistedChains.pop();
}
}
delete whitelistedDestinations[_chainSelector];
delete updateExtraArgsByChain[_chainSelector];
delete rewardsExtraArgsByChain[_chainSelector];
}
The rewards are distributed using SDLPoolCCIPControllerPrimary::distributeRewards()
which calls SDLPoolCCIPControllerPrimary::_distributeRewards()
for each whitelisted chain.
SDLPoolCCIPControllerPrimary.sol#L91C9-L91C9
* @notice Claims and distributes rewards between all secondary chains
**/
function distributeRewards() external onlyRewardsInitiator {
uint256 totalRESDL = ISDLPoolPrimary(sdlPool).effectiveBalanceOf(address(this));
address[] memory tokens = ISDLPoolPrimary(sdlPool).supportedTokens();
uint256 numDestinations = whitelistedChains.length;
ISDLPoolPrimary(sdlPool).withdrawRewards(tokens);
uint256[][] memory distributionAmounts = new uint256[][](numDestinations);
for (uint256 i = 0; i < numDestinations; ++i) {
distributionAmounts[i] = new uint256[](tokens.length);
}
for (uint256 i = 0; i < tokens.length; ++i) {
address token = tokens[i];
uint256 tokenBalance = IERC20(token).balanceOf(address(this));
address wrappedToken = wrappedRewardTokens[token];
if (wrappedToken != address(0)) {
IERC677(token).transferAndCall(wrappedToken, tokenBalance, "");
tokens[i] = wrappedToken;
tokenBalance = IERC20(wrappedToken).balanceOf(address(this));
}
uint256 totalDistributed;
for (uint256 j = 0; j < numDestinations; ++j) {
uint64 chainSelector = whitelistedChains[j];
uint256 rewards = j == numDestinations - 1
? tokenBalance - totalDistributed
: (tokenBalance * reSDLSupplyByChain[chainSelector]) / totalRESDL;
distributionAmounts[j][i] = rewards;
totalDistributed += rewards;
}
}
for (uint256 i = 0; i < numDestinations; ++i) {
❌ _distributeRewards(whitelistedChains[i], tokens, distributionAmounts[i]);
}
}
Therefore, if a whitelisted chain is removed from the whitelist, the rewards of this chain will be stuck in the contract until this chain is added back to the whitelist by the owner.
Impact
User rewards will be stuck in the contract if a whitelisted chain is removed from the whitelist until it is added back by the owner.
Recommendations
Distribute all rewards before removing a whitelisted chain from the whitelist.