Summary
The distributeProtocolAssetReward
function distributes protocol rewards among multiple fee recipients based on their share percentages. The function iterates over the list of recipients, calculates their respective rewards, and transfers the asset (USDC
in this case) to each recipient.
However, if any recipient is blacklisted (e.g., by USDC’s centralized contract), the transfer will fail, causing the function to revert and preventing all recipients from receiving their rewards.
Vulnerability Details
function distributeProtocolAssetReward(Data storage self, address asset, uint256 amount) internal {
uint256 feeRecipientsLength = self.protocolFeeRecipients.length();
UD60x18 totalFeeRecipientsSharesX18 = ud60x18(self.totalFeeRecipientsShares);
UD60x18 amountX18 = ud60x18(amount);
uint256 totalDistributed = 0;
for (uint256 i; i < feeRecipientsLength; i++) {
(address feeRecipient, uint256 shares) = self.protocolFeeRecipients.at(i);
uint256 feeRecipientReward = amountX18.mul(ud60x18(shares)).div(totalFeeRecipientsSharesX18).intoUint256();
totalDistributed += feeRecipientReward;
if (i == feeRecipientsLength - 1) {
feeRecipientReward += amountX18.sub(ud60x18(totalDistributed)).intoUint256();
}
IERC20(asset).safeTransfer(feeRecipient, feeRecipientReward);
}
}
One of the functions that use distributeProtocolAssetReward
function _convertUsdcToAssets(
uint128 dexSwapStrategyId,
address asset,
uint256 usdcAmount,
bytes memory path,
address recipient,
address usdc
)
internal
returns (uint256 assetOut)
{
if (usdcAmount == 0) revert Errors.AssetAmountIsZero(usdc);
if (asset == usdc) {
assetOut = usdcAmount;
} else {
MarketMakingEngineConfiguration.Data storage marketMakingEngineConfiguration =
MarketMakingEngineConfiguration.load();
uint256 settlementBaseFeeUsd = Collateral.load(usdc).convertUd60x18ToTokenAmount(
ud60x18(marketMakingEngineConfiguration.settlementBaseFeeUsdX18)
);
if (settlementBaseFeeUsd > 0) {
if (usdcAmount < settlementBaseFeeUsd) {
revert Errors.FailedToPaySettlementBaseFee();
}
usdcAmount -= settlementBaseFeeUsd;
@> marketMakingEngineConfiguration.distributeProtocolAssetReward(usdc, settlementBaseFeeUsd);
}
DexSwapStrategy.Data storage dexSwapStrategy = DexSwapStrategy.loadExisting(dexSwapStrategyId);
IERC20(usdc).approve(dexSwapStrategy.dexAdapter, usdcAmount);
if (path.length == 0) {
SwapExactInputSinglePayload memory swapCallData = SwapExactInputSinglePayload({
tokenIn: usdc,
tokenOut: asset,
amountIn: usdcAmount,
recipient: recipient
});
assetOut = dexSwapStrategy.executeSwapExactInputSingle(swapCallData);
} else {
SwapExactInputPayload memory swapCallData = SwapExactInputPayload({
path: path,
tokenIn: usdc,
tokenOut: asset,
amountIn: usdcAmount,
recipient: recipient
});
assetOut = dexSwapStrategy.executeSwapExactInput(swapCallData);
}
}
}
function convertMarketsCreditDepositsToUsdc(
uint128 marketId,
address[] calldata assets,
uint128[] calldata dexSwapStrategyIds,
bytes[] calldata paths
)
external
onlyRegisteredSystemKeepers
{
if (assets.length != dexSwapStrategyIds.length || assets.length != paths.length) {
revert Errors.ArrayLengthMismatch(0, 0);
}
Market.Data storage market = Market.loadExisting(marketId);
ConvertMarketsCreditDepositsToUsdcContext memory ctx;
for (uint256 i; i < assets.length; i++) {
(bool exists, uint256 creditDeposits) = market.creditDeposits.tryGet(assets[i]);
if (!exists) revert Errors.MarketDoesNotContainTheAsset(assets[i]);
if (creditDeposits == 0) revert Errors.AssetAmountIsZero(assets[i]);
address usdc = MarketMakingEngineConfiguration.load().usdc;
ctx.creditDepositsNativeDecimals =
Collateral.load(assets[i]).convertUd60x18ToTokenAmount(ud60x18(creditDeposits));
@> uint256 usdcOut = _convertAssetsToUsdc(
@> dexSwapStrategyIds[i], assets[i], ctx.creditDepositsNativeDecimals, paths[i], address(this), usdc
@> );
if (usdcOut == 0) revert Errors.ZeroOutputTokens();
market.settleCreditDeposit(assets[i], Collateral.load(usdc).convertTokenAmountToUd60x18(usdcOut));
emit LogConvertMarketCreditDepositsToUsdc(marketId, assets[i], creditDeposits, usdcOut);
}
}
If any of the protocolFeeRecipients
is blacklisted, the safeTransfer
call will fail, reverting the entire function and blocking rewards for all recipients. USDC and other centralized assets have blacklisting mechanisms (e.g., USDC
uses Circle’s blacklist feature). If any fee recipient is blacklisted, the call to safeTransfer
fails, preventing further execution of the function.
Impact
If one recipient is blacklisted, none of the recipients receive their rewards because the function reverts entirely.
Tools Used
Manual Review
Recommendations
Modify the function to Handle Blacklisted Recipients Gracefully