Tadle

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

Incorrect `_collateralRate` parameter validation when listing offers for `Protected` market leads to loss of funds.

Summary

The _collateralRate is not checked when calling PreMarkets.listOffer() for the Protected market, where the collateralRate from the preOffer is used for the collateral transfer amount.

This can lead to direct loss of funds for the user or the protocol in situations where _collateralRate does not equal preOffer.collateralRate.

Vulnerability Details

In the PreMarkets.listOffer() function, the check to validate the _collateralRate is only performed for Turbo markets:

File: PreMarkets.sol
335: if (makerInfo.offerSettleType == OfferSettleType.Turbo) {
336: address originOffer = makerInfo.originOffer;
337: OfferInfo memory originOfferInfo = offerInfoMap[originOffer];
338:
339: if (_collateralRate != originOfferInfo.collateralRate) { // <== this should be global for this function
340: revert InvalidCollateralRate();
341: }

Unfortunately, this check is limited to Turbo markets but should also be applied to Protected markets. In Protected markets, the collateral deposit amount is calculated based on the preOffer.collateralRate rather than the _collateralRate:

File: PreMarkets.sol
346: if (makerInfo.offerSettleType == OfferSettleType.Protected) {
347: uint256 transferAmount = OfferLibraries.getDepositAmount(
348: offerInfo.offerType,
349: offerInfo.collateralRate, // <== preOffer.collateralRate
350: _amount,
351: true,
352: Math.Rounding.Ceil
353: );

The _collateralRate is then used to create a new offer:

File: PreMarkets.sol
369: /// @dev update offer info
370: offerInfoMap[offerAddr] = OfferInfo({
371: id: stockInfo.id,
...
379: collateralRate: _collateralRate, // <== arbitrary, unchecked _collateralRate used
...
385: });

This can be exploited in the following scenario:

  1. A Maker creates a Protected market and an Ask offer with a collateral factor of 100%.

  2. An attacker, acting as a Taker, takes the Ask for 1 USDC.

  3. The attacker then relists the stock via the PreMarkets.listOffer() function but manipulates the _collateralRate to 1,000,000%, while only providing 1 USDC collateral.

  4. The attacker instantly closes the offer via the PreMarkets.closeOffer() function, where they are refunded based on the manipulated _collateralRate, and receives 10,000 USDC or any other amount held in the CapitalPool contract.

File: PreMarkets.sol
437: if (
438: makerInfo.offerSettleType == OfferSettleType.Protected ||
439: stockInfo.preOffer == address(0x0)
440: ) {
441: uint256 refundAmount = OfferLibraries.getRefundAmount( // <== attacker gets more than deposited
442: offerInfo.offerType,
443: offerInfo.amount,
444: offerInfo.points,
445: offerInfo.usedPoints,
446: offerInfo.collateralRate
447: );

Impact

All funds in the system can be stolen.

Tools Used

Manual review.

Recommendations

Validate that the _collateralRate value is equal to preOffer.collateralRate for Protected markets.

OR

Use _collateralRate in the transferAmount calculations when determining collateral.

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.