stake.link

stake.link
DeFiHardhatBridge
27,500 USDC
View results
Submission Details
Severity: low
Invalid

User rewards of a whitelisted chain are stuck when this chain is removed from the whitelist

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.

Updates

Lead Judging Commences

0kage Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.