Tadle

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

Resellers in turbo mode can steal collateral tokens by faking points settlement because the settleAskMaker function always assumes the caller to be the original seller(collateral supplier).

Summary

In turbo mode the collateral of the first seller(originOffer) is used and buyers who resell does not put in any collateral.

But since the settleAskMaker function does not check for origin offer and always sends back collateral to the given offer(seller), a malicious seller can use this to steal collateral tokens from the pool.

Vulnerability Details

As we can also see from the docs, incase of turbo mode the settlement is done by the original seller(Alice) and the subsequent sellers(Bob and Dany) did not have to do the settlement.

However, the settlement functions does not restrict the settlement process to only Alice but Bob and Dany can also call it, even though they did not have to settle any points.

To get the collateral tokens the caller(seller) needs to settle the amount of points he sold:

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) {

But lets see an eample where a malicious user can exlpoit this to get collateral tokens without having to settle any points;

Example:

  • Alice sells 1000 points in turbo mode, Alice is the original seller who supplied the collateral

  • Bob buys 1000 points from Alice by calling createTaker, Bob now owns 1000 points in his stockInfo:

stockInfoMap[stockAddr] = StockInfo({
id: offerId,
stockStatus: StockStatus.Initialized,
stockType: offerInfo.offerType == OfferType.Ask
? StockType.Bid
: StockType.Ask,
authority: _msgSender(),
maker: offerInfo.maker,
preOffer: _offer,
points: _points,
amount: depositAmount,
offer: address(0x0)
});
  • Bob resells(listOffer) his 1000 points for unrealistically large amount say 10000000 USDC, to increase the attack and to make sure no one buys his sell offer, he can also achieve this by selling few seconds before the settlement period starts.

  • Since the original offer is in turbo mode Bob does not add any collateral:

if (makerInfo.offerSettleType == OfferSettleType.Turbo) {
address originOffer = makerInfo.originOffer;
OfferInfo memory originOfferInfo = offerInfoMap[originOffer];
/// @dev transfer collateral when offer settle type is protected
if (makerInfo.offerSettleType == OfferSettleType.Protected) { // if turbo no collateral sent by the reseller
  • Bob now has an offerInfo(sell/ask offer), with points = 1000, amount = 10000000 USDC and usedPoints = 0.

  • Now during the settlement period, Bob calls settleAskMaker with his offer and 0 as _setteledPoints.

  • Now Bob doesn't need to send any point tokens because settledPointTokenAmount = 0

uint256 settledPointTokenAmount = marketPlaceInfo.tokenPerPoint *
_settledPoints;
ITokenManager tokenManager = tadleFactory.getTokenManager();
if (settledPointTokenAmount > 0) {
tokenManager.tillIn(
  • After this the function checks if (_settledPoints == offerInfo.usedPoints) which means it will send back the collateral to the seller if he settles the points he sold.

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
);
}
  • In Bob's case since _settledPoints = 0 and offerInfo.usedPoints = 0, the contract will add the collateral tokens to Bob's balance.

  • Bob now has more than 10000000 collateral token in his balance, which he never supplied and can withdraw anytime.

  • Bob will also get his point Tokens from Alice. See closeBidTaker function:

} else {
offerInfo = perMarkets.getOfferInfo(makerInfo.originOffer);
if (stockInfo.offer == address(0x0)) {
userRemainingPoints = stockInfo.points;
} else {
OfferInfo memory listOfferInfo = perMarkets.getOfferInfo(
stockInfo.offer
);
userRemainingPoints =
listOfferInfo.points -
listOfferInfo.usedPoints;
}
  • Bob sucessfully bought 1000 points from Alice and stolen more than 1000000 collateral tokens and still get his points tokens from Alice's settlement.

  • Bob can also adjust the size of the attack by adjusting the amount given when he resells the points.

This is because the settleAskMaker function does not check for the type of settlement mode and does not check whether the given offer is the original seller who puts in the collateral or not.

Impact

loss of funds(collateral tokens).

Tools Used

manual

Recommendations

Re-design the settleAskMaker function such that in turbo mode the collateral is sent back only to the original(first) seller who supplied the collateral and not to every subsequent sellers.

Updates

Lead Judging Commences

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

finding-Premarkets-listOffer-turbo-settleAskMaker-exploit-settlement

Valid high severity, this allows resellers listing offers via `listOffer/relistOffer` to game the system. Based on the inherent design of Turbo mode not requiring takers making ask offers for the original maker offer to deposit collateral, the wrong refund of collateral to takers even when they did not deposit collateral due to turbo mode during settleAskMaker allows possible draining of pools.

Support

FAQs

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

Give us feedback!