stake.link

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

Missing Important Checks in Reward Distribution Logic: Potential Denial of Service (DoS) Vulnerability or corss-chains inconsistent behavior

Summary

The distributeRewards and _distributeRewards functions lack crucial checks, potentially exposing the contract to unexpected behavior across chains or a Denial of Service (DoS) vulnerability. Specifically, the Chainlink Cross-Chain Interoperability Protocol (CCIP) requires tokens transferred to be supported by the CCIP Token pool. Additionally, the implemented logic assumes that all supported tokens across all chains are the same, which is not the case.

The absence of this check allows for the possibility of distributing rewards in an uncontrolled manner, posing a risk of DoS attacks. Furthermore, the addToken function, responsible for adding new tokens, fails to verify if the newly added token is supported by the CCIP, introducing a potential avenue for inconsistent behavior.

Vulnerability Details

  1. Missing CCIP Token Support Check in Distribution Functions:
    The distributeRewards and _distributeRewards functions do not include checks to ensure that the tokens being distributed are supported by the CCIP Token pool. This deviation from CCIP requirements exposes the contract to potential DoS attacks if nonsupported reward token is added,

// EVM2EVMOffRamp.sol contract called by CCIP Router
/// @inheritdoc IEVM2AnyOnRamp
function getPoolBySourceToken(IERC20 sourceToken) public view returns (IPool) {
if (!s_poolsBySourceToken.contains(address(sourceToken))) revert UnsupportedToken(sourceToken);
return IPool(s_poolsBySourceToken.get(address(sourceToken)));
}
//Router.sol
/// @inheritdoc IRouterClient
function ccipSend(
uint64 destinationChainSelector,
Client.EVM2AnyMessage memory message
) external payable whenHealthy returns (bytes32) {
address onRamp = s_onRamps[destinationChainSelector];
if (onRamp == address(0)) revert UnsupportedDestinationChain(destinationChainSelector);
uint256 feeTokenAmount;
// address(0) signals payment in true native
if (message.feeToken == address(0)) {
// for fee calculation we check the wrapped native price as we wrap
// as part of the native fee coin payment.
message.feeToken = s_wrappedNative;
feeTokenAmount = IEVM2AnyOnRamp(onRamp).getFee(message);
// Ensure sufficient native.
if (msg.value < feeTokenAmount) revert InsufficientFeeTokenAmount();
// Wrap and send native payment.
// Note we take the whole msg.value regardless if its larger.
feeTokenAmount = msg.value;
IWrappedNative(message.feeToken).deposit{value: feeTokenAmount}();
IERC20(message.feeToken).safeTransfer(onRamp, feeTokenAmount);
} else {
if (msg.value > 0) revert InvalidMsgValue();
feeTokenAmount = IEVM2AnyOnRamp(onRamp).getFee(message);
IERC20(message.feeToken).safeTransferFrom(msg.sender, onRamp, feeTokenAmount);
}
// Transfer the tokens to the token pools.
for (uint256 i = 0; i < message.tokenAmounts.length; ++i) {
IERC20 token = IERC20(message.tokenAmounts[i].token);
token.safeTransferFrom(
msg.sender,
address(IEVM2AnyOnRamp(onRamp).getPoolBySourceToken(token)),
message.tokenAmounts[i].amount
);
}
return IEVM2AnyOnRamp(onRamp).forwardFromRouter(message, feeTokenAmount, msg.sender);
}
  1. Expecting the Same Tokens Across All Chains:
    The code assumes that all chains support the same tokens, but this might not be true. This oversight could result in unpredictable outcomes when distributing rewards to different chains, each with its own unique (number and type of) token support.

  2. Lack of CCIP Token Support Verification in addToken Function:
    The addToken function, responsible for adding new tokens, does not verify whether the newly added token is supported by the CCIP. This oversight allows unsupported tokens to be added, potentially leading to inconsistencies in reward distribution.

//RewardsPoolController.sol
/**
* @notice adds a new token
* @param _token token to add
* @param _rewardsPool token rewards pool to add
**/
function addToken(address _token, address _rewardsPool) public virtual onlyOwner {
if (isTokenSupported(_token)) revert InvalidToken();
tokenPools[_token] = IRewardsPool(_rewardsPool);
tokens.push(_token);
if (IERC20Upgradeable(_token).balanceOf(address(this)) > 0) {
distributeToken(_token);
}
emit AddToken(_token, _rewardsPool);
}

Impact

The absence of essential checks in the contract's reward distribution logic introduces the risk of unexpected behavior across chains. The impact varies based on the configurations of the destination chains. Relying solely on the owner to ensure that only supported tokens are added across all supported chains exposes the project and its users to potential risks. The lack of systematic verification increases the likelihood of inconsistencies, jeopardizing the contract's reliability and the overall integrity of the system.

Tools Used

Manual review

Recommendations

  1. Implement CCIP Token Support Check in Distribution Functions:
    Enhance the distributeRewards and _distributeRewards functions by incorporating checks to ensure that tokens being distributed are supported by the CCIP Token pool. This involves validating each token against the list returned from calling getSupportedTokens() in the EVM2EVMOnRamp.sol contract.

  2. Review and Adjust Logic for Varying Token Support Across Chains:
    Review the logic related to token support assumptions across chains and adjust it to accommodate variations in supported tokens on different chains.

  3. Enhance addToken Function with CCIP Token Support Verification:
    Update the addToken function to include a check ensuring that the newly added token is supported by the CCIP Token pool. This can be achieved by verifying the token against the list obtained from calling getSupportedTokens() in the CCIP contract.

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.