Summary
A critical vulnerability has been identified in the `SettleAskMaker` function, where the maker's refund is incorrectly handled if the `settledPoints` are not equal to the `usedPoints`. This issue results in the maker losing a portion of their funds because the function does not update the user's account correctly in this scenario.
Vulnerability Details
The `SettleAskMaker` function is responsible for refunding the maker when they close an ASK offer. The refund amount is calculated based on the `settledPoints` and `usedPoints`. However, the current implementation only updates the maker's account if the `settledPoints` are equal to the `usedPoints`. If the `settledPoints` are less than the `usedPoints`, the function fails to refund the correct amount(refunds 0), leading to a loss of funds for the maker.
### Vulnerable Code
The issue arises in the following code segment:
* @notice Settle ask maker
* @dev caller must be offer authority
* @dev offer status must be Virgin or Canceled
* @dev market place status must be AskSettling
* @param _offer offer address
* @param _settledPoints settled points
*/
function settleAskMaker(address _offer, uint256 _settledPoints) external {
(
OfferInfo memory offerInfo,
MakerInfo memory makerInfo,
MarketPlaceInfo memory marketPlaceInfo,
MarketPlaceStatus status
) = getOfferInfo(_offer);
if (_settledPoints > offerInfo.usedPoints) {
revert InvalidPoints();
}
if (marketPlaceInfo.fixedratio) {
revert FixedRatioUnsupported();
}
if (offerInfo.offerType == OfferType.Bid) {
revert InvalidOfferType(OfferType.Ask, OfferType.Bid);
}
if (
offerInfo.offerStatus != OfferStatus.Virgin &&
offerInfo.offerStatus != OfferStatus.Canceled
) {
revert InvalidOfferStatus();
}
if (status == MarketPlaceStatus.AskSettling) {
if (_msgSender() != offerInfo.authority) {
revert Errors.Unauthorized();
}
} else {
if (_msgSender() != owner()) {
revert Errors.Unauthorized();
}
if (_settledPoints > 0) {
revert InvalidPoints();
}
}
uint256 settledPointTokenAmount = marketPlaceInfo.tokenPerPoint *
_settledPoints;
ITokenManager tokenManager = tadleFactory.getTokenManager();
if (settledPointTokenAmount > 0) {
tokenManager.tillIn(
_msgSender(),
marketPlaceInfo.tokenAddress,
settledPointTokenAmount,
true
);
}
uint256 makerRefundAmount;
if (_settledPoints == offerInfo.usedPoints) {
if (offerInfo.offerStatus == OfferStatus.Virgin) {
makerRefundAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType,
offerInfo.collateralRate,
offerInfo.amount,
true,
Math.Rounding.Floor
);
} else {
uint256 usedAmount = offerInfo.amount.mulDiv(
offerInfo.usedPoints,
offerInfo.points,
Math.Rounding.Floor
);
makerRefundAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType,
offerInfo.collateralRate,
usedAmount,
true,
Math.Rounding.Floor
);
}
tokenManager.addTokenBalance(
TokenBalanceType.SalesRevenue,
_msgSender(),
makerInfo.tokenAddress,
makerRefundAmount
);
}
@audit >>>
IPerMarkets perMarkets = tadleFactory.getPerMarkets();
perMarkets.settledAskOffer(
_offer,
_settledPoints,
settledPointTokenAmount
);
emit SettleAskMaker(
makerInfo.marketPlace,
offerInfo.maker,
_offer,
_msgSender(),
_settledPoints,
settledPointTokenAmount,
makerRefundAmount
);
}
In this implementation, when `_settledPoints != offerInfo.usedPoints`, the function does not update the maker’s balance - 0, leading to a loss of the maker’s funds.
The absence of logic to handle the scenario where `settledPoints < usedPoints` means that the remaining funds are not refunded, and the maker loses those funds.
Look at the implementation in SettleAsktaker function, both conditions there are well captured.
@audit>> if (_settledPoints == stockInfo.points) {
tokenManager.addTokenBalance(
TokenBalanceType.RemainingCash,
_msgSender(),
makerInfo.tokenAddress,
collateralFee
);
@audit>> } else {
tokenManager.addTokenBalance(
TokenBalanceType.MakerRefund,
offerInfo.authority,
makerInfo.tokenAddress,
collateralFee
);
}
Impact
This vulnerability results in a significant financial loss for the maker when the `settledPoints` are not equal to the `usedPoints`. Makers lose a portion of their funds due to the incorrect handling of the refund process
Tools Used
- Manual code review
Recommendations
To mitigate this issue, the code should include logic to handle cases where `settledPoints < usedPoints`. This can be done by nesting an `else` statement to manage the condition when an order with `settledPoints` less than `usedPoints` is being settled.
### Suggested Code Modification
Kindly review the added logic below, more im=ntegration might still be possible, but this is a possible fix.
* @notice Settle ask maker
* @dev caller must be offer authority
* @dev offer status must be Virgin or Canceled
* @dev market place status must be AskSettling
* @param _offer offer address
* @param _settledPoints settled points
*/
function settleAskMaker(address _offer, uint256 _settledPoints) external {
(
------------------------------------------------------
uint256 makerRefundAmount;
if (_settledPoints == offerInfo.usedPoints) {
if (offerInfo.offerStatus == OfferStatus.Virgin) {
makerRefundAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType,
offerInfo.collateralRate,
offerInfo.amount,
true,
Math.Rounding.Floor
);
} else {
uint256 usedAmount = offerInfo.amount.mulDiv(
offerInfo.usedPoints,
offerInfo.points,
Math.Rounding.Floor
);
makerRefundAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType,
offerInfo.collateralRate,
usedAmount,
true,
Math.Rounding.Floor
);
}
tokenManager.addTokenBalance(
TokenBalanceType.SalesRevenue,
_msgSender(),
makerInfo.tokenAddress,
makerRefundAmount
);
}
else {
++ uint256 usedAmount = offerInfo.amount.mulDiv(
++ _settledPoints,
++ offerInfo.points,
++ Math.Rounding.Floor
++ );
++ makerRefundAmount = OfferLibraries.getDepositAmount(
++ offerInfo.offerType,
++ offerInfo.collateralRate,
++ usedAmount,
++ true,
++ Math.Rounding.Floor
++ );
++ }
++ tokenManager.addTokenBalance(
++ TokenBalanceType.SalesRevenue,
++ _msgSender(),
++ makerInfo.tokenAddress,
++ makerRefundAmount
++ );
++ }
IPerMarkets perMarkets = tadleFactory.getPerMarkets();
perMarkets.settledAskOffer(
_offer,
_settledPoints,
settledPointTokenAmount
);
This update ensures that the maker's balance is correctly adjusted and that all funds are refunded when the order is settled, regardless of the relationship between `settledPoints` and `usedPoints`.