Summary
when withdrawing amount the protocolFee and Withdrawn amount wrongly calculated because of different decimals precision.
Vulnerability Details
As the protocol uses any token with decimals lower or equal to 18, but the fee is in 18 decimal precision.
Attack Scenario
sender deposit 5 USDC (5 * 1e6), the aggregate balance of the token increased by the 5e6, in 6 decimal precision, but when withdrawing amount, the amount is multiplied by 18 decimal precision which wrongly calculated protocol and withdrawan amount.
https://github.com/Cyfrin/2024-10-sablier/blob/main/src/SablierFlow.sol#L772-L880
function _withdraw(
uint256 streamId,
address to,
uint128 amount
)
internal
returns (uint128 withdrawnAmount, uint128 protocolFeeAmount)
{
...
if (amount > withdrawableAmount) {
revert Errors.SablierFlow_Overdraw(streamId, amount, withdrawableAmount);
}
uint256 amountScaled = Helpers.scaleAmount(amount, tokenDecimals);
...
if (protocolFee > ZERO) {
(protocolFeeAmount, amount) = Helpers.calculateAmountsFromFee({ totalAmount: amount, fee: protocolFee });
unchecked {
protocolRevenue[token] += protocolFeeAmount;
}
}
unchecked {
aggregateBalance[token] -= amount;
}
token.safeTransfer({ to: to, value: amount });
...
return (amount, protocolFeeAmount);
}
}
Impact
Medium
Tools Used
Foundry
Recommendations
When calculating protocolFee and
`withdraw` amount, use the Scaled amount with 18 decimal precision as protocolFee is in 18 decimal precision.
if (protocolFee > ZERO) {
// Calculate the protocol fee amount and the net withdraw amount.
- (protocolFeeAmount, amount) = Helpers.calculateAmountsFromFee({ totalAmount: amount, fee: protocolFee });
+ (protocolFeeAmount, amount) = Helpers.calculateAmountsFromFee({ totalAmount: amountScaled, fee: protocolFee });
// Safe to use unchecked because addition cannot overflow.
unchecked {
// Effect: update the protocol revenue.
protocolRevenue[token] += protocolFeeAmount;
}
}
unchecked {
// Effect: update the aggregate balance.
- aggregateBalance[token] -= amount;
+ aggregateBalance[token] -= Helpers.descaleAmount({ amount: amount, decimals: _streams[streamId].tokenDecimals });
}
// Interaction: perform the ERC-20 transfer.
token.safeTransfer({ to: to, value: amount });