Summary
effectiveBalanceOf
, do arithmetic without checking for overflows which could lead to unexpected behavior. This unchecked addition could be vulnerable to an integer overflow. For example, if a user has the max uint256
amount of base SDL balance, adding even 1 wei of bonus could overflow and wrap around to 0.
Vulnerability Details
The effectiveBalanceOf
function in SDLPool.sol
performs an unchecked addition when calculating a user's effective balance: SDLPool.sol#effectiveBalanceOf
function effectiveBalanceOf(address _account) external view returns (uint256) {
return effectiveBalances[_account];
}
Here, effectiveBalances
is a mapping that stores the sum of a user's base SDL balance + any applicable locking bonuses.
Impact
In distributeRewards
of SDLPoolCCIPControllerPrimary.sol
, rewards are divided proportionally based on each user's effective balance percentage of the total effective supply. SDLPoolCCIPControllerPrimary.sol#distributeRewards
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]);
}
}
By triggering overflow, exploit contract can manipulate its effective balance to incorrectly show 0, even though it owns practically the entire effective supply. This could allow the exploit contract to claim all protocol rewards.
Tools Used
Manual Review
Recommendations
By using OpenZeppelin's SafeMath libraries to perform checked arithmetic operations.
function effectiveBalanceOf(address _account) external view returns (uint256) {
- return effectiveBalances[_account];
+ return effectiveBalances[_account].add(locks[_account].bonusAmount);
}
Adopting overflow-safe math would mitigate this attack vector.