Tadle

Tadle
DeFiFoundry
27,750 USDC
View results
Submission Details
Severity: high
Valid

Lack of input validation in `PreMarkets.sol#listOffer()` function

Summary

When makerInfo.offerSettleType == OfferSettleType.Protected in the PreMarkets.sol#listOffer() function, severe fund loss in the protocol may occur due to lack of validation for _collateralRate.

Vulnerability Details

In the PreMarkets.sol#listOffer() function, if the offer settlement type is protected, it is transferred from the collateral caller to the CapitalPool contract.
The transferAmount is calculated according to collateralRate in stockInfo.preOffer.

SNIP...
--> OfferInfo storage offerInfo = offerInfoMap[stockInfo.preOffer];
SNIP...
/// @dev transfer collateral when offer settle type is protected
if (makerInfo.offerSettleType == OfferSettleType.Protected) {
uint256 transferAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType,
--> offerInfo.collateralRate,
_amount,
true,
Math.Rounding.Ceil
);
ITokenManager tokenManager = tadleFactory.getTokenManager();
--> tokenManager.tillIn{value: msg.value}(
_msgSender(),
makerInfo.tokenAddress,
transferAmount,
false
);
}

Next, set the entered _collateralRate to the collateralRate of the offer.

offerInfoMap[offerAddr] = OfferInfo({
id: stockInfo.id,
authority: _msgSender(),
maker: offerInfo.maker,
offerStatus: OfferStatus.Virgin,
offerType: offerInfo.offerType,
abortOfferStatus: AbortOfferStatus.Initialized,
points: stockInfo.points,
amount: _amount,
--> collateralRate: _collateralRate,
usedPoints: 0,
tradeTax: 0,
settledPoints: 0,
settledPointTokenAmount: 0,
settledCollateralAmount: 0
});

Meanwhile, the PreMarkets.sol#closeOffer() function uses the collateralRate of the offer in the OfferLibraries.sol#getRefundAmount() function to update refund token from capital pool to balance.

function closeOffer(address _stock, address _offer) external {
SNIP...
/**
* @dev update refund token from capital pool to balance
* @dev offer settle type is protected or original offer
*/
if (
makerInfo.offerSettleType == OfferSettleType.Protected ||
stockInfo.preOffer == address(0x0)
) {
--> uint256 refundAmount = OfferLibraries.getRefundAmount(
offerInfo.offerType,
offerInfo.amount,
offerInfo.points,
offerInfo.usedPoints,
offerInfo.collateralRate
);
ITokenManager tokenManager = tadleFactory.getTokenManager();
tokenManager.addTokenBalance(
TokenBalanceType.MakerRefund,
_msgSender(),
makerInfo.tokenAddress,
refundAmount
);
}
offerInfo.offerStatus = OfferStatus.Canceled;
emit CloseOffer(_offer, _msgSender());
}

As a result, users can steal funds from the protocol by intentionally entering a large _collateralRate.

Example:

  1. Alice calls listOffer with _collateralRate = 12000 (120%) and _amount = 100 ehter.

  2. At this time, assume that collateralRate = 10000 (100%) of stockInfo.preOffer.

  3. At this time, transferAmount is calculate as follows:

    transferAmount = _amount * offerInfoMap[stockInfo.preOffer].collateralRate / COLLATERAL_RATE_DECIMAL_SCALER =

    = 100 ether * 10000 / 10000 = 100 ether

    As a result, Alice has paid 100 ether.

  4. Next, the collateralRate of that offer is set to 12000.

  5. Alice immediately calls closeOffer for the offer.

    At this time, offerInfo.usedPoints = 0.

    Therefore,

    refundAmount = offerInfo.amount * offerInfo.collateralRate / COLLATERAL_RATE_DECIMAL_SCALER =

    = 100 ether * 12000 / 10000 = 120 ether

    As a result, Alice receives 20 ether more than the deposit amount.

Impact

Malicous users can steal funds from the protocol by intentionally entering a large _collateralRate.

Tools Used

Manual Review

Recommendations

In the PreMarkets.sol#listOffer() function, if makerInfo.offerSettleType == OfferSettleType.Protected, it is recommended to add the following line:

/// @dev transfer collateral when offer settle type is protected
if (makerInfo.offerSettleType == OfferSettleType.Protected) {
+++ if (_collateralRate != offerInfo.collateralRate) {
+++ revert InvalidCollateralRate();
+++ }
uint256 transferAmount = OfferLibraries.getDepositAmount(
offerInfo.offerType,
offerInfo.collateralRate,
_amount,
true,
Math.Rounding.Ceil
);
ITokenManager tokenManager = tadleFactory.getTokenManager();
tokenManager.tillIn{value: msg.value}(
_msgSender(),
makerInfo.tokenAddress,
transferAmount,
false
);
}
Updates

Lead Judging Commences

0xnevi Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-PreMarkets-listOffer-collateralRate-manipulate

Valid high severity, because the collateral rate utilized when creating an offer is stale and retrieved from a previously set collateral rate, it allows possible manipilation of refund amounts using an inflated collateral rate to drain funds from the CapitalPool contract

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.