Tadle

Tadle
DeFi
30,000 USDC
View results
Submission Details
Severity: low
Invalid

Turbo Mode Partial Abort Locks User Collateral in PreMarkets and DeliveryPlace Contracts

Summary

A critical vulnerability has been identified in the PreMarkets and DeliveryPlace contracts, specifically in the abort process for Turbo Mode offers. This vulnerability can lead to permanent locking of funds in partial abort scenarios, with no user-accessible mechanism to retrieve the locked collateral.

Vulnerability Details

The vulnerability arises from the interaction between the abortAskOffer function in the PreMarkets contract and the settlement processes in the DeliveryPlace contract, specifically for Turbo Mode offers.

  1. In Turbo Mode, the original offer creator deposits collateral for the entire offer, while subsequent takers can trade without additional collateral.

  2. The abortAskOffer function in PreMarkets calculates a refund based on the remaining amount of the offer:

uint256 remainingAmount;
if (offerInfo.offerStatus == OfferStatus.Virgin) {
remainingAmount = offerInfo.amount;
} else {
remainingAmount = offerInfo.amount.mulDiv(
offerInfo.usedPoints,
offerInfo.points,
Math.Rounding.Floor
);
}
  1. After calculating the refund, it sets the offer status to Settled:

offerInfo.abortOfferStatus = AbortOfferStatus.Aborted;
offerInfo.offerStatus = OfferStatus.Settled;
  1. Any function in the contract suite that allows for the retrieval of the remaining collateral needs for the OfferStatus to be either Virgin or canceled.

if (
offerInfo.offerStatus != OfferStatus.Virgin &&
offerInfo.offerStatus != OfferStatus.Canceled
) {
revert InvalidOfferStatus();
}

Impact

Users can permanently lose access to a portion of their collateral in partial abort scenarios. This could potentially amount to significant sums depending on the offer size and the aborted portion.

  1. Alice creates a Turbo Mode Ask offer for 1,000,000 points at 1 USDC each, depositing 1,100,000 USDC as collateral (110% collateral rate).

  2. Bob takes 500,000 points without depositing additional collateral.

  3. Alice attempts to abort the offer.

  4. The abort function calculates the refund based on the remaining 500,000 points, potentially refunding Alice only half of her original collateral.

  5. The other half of the collateral remains locked in the contract, as there's no mechanism to return it to Alice or distribute it to Bob (who didn't deposit any collateral).
    the following poc shows that the refundedAmount is less than the collateralAmount put by Alice (user)

function testTurboModeAbortLockedFunds() public {
// Setup: Create a large Ask offer in Turbo mode
uint256 offerAmount = 1_000_000 * 10**18; // 1,000,000 USDC
uint256 collateralRate = 11000; // 110%
uint256 collateralAmount = (offerAmount * collateralRate) / 10000;
console.log("balance capital", mockUSDCToken.balanceOf(address(capitalPool)));
CreateOfferParams memory params = CreateOfferParams({
marketPlace: marketPlace,
offerType: OfferType.Ask,
tokenAddress: address(mockUSDCToken),
points: offerAmount,
amount: offerAmount,
collateralRate: collateralRate,
eachTradeTax: 100, // 1%
offerSettleType: OfferSettleType.Turbo
});
// Alice creates the offer
vm.startPrank(user);
preMarktes.createOffer(params);
vm.stopPrank();
address offerAddr = GenerateAddress.generateOfferAddress(0);
address stockAddr = GenerateAddress.generateStockAddress(0);
// Record initial balances
uint256 aliceInitialBalance = mockUSDCToken.balanceOf(user);
uint256 contractInitialBalance = mockUSDCToken.balanceOf(address(capitalPool));
// Bob takes half of the offer
vm.prank(user2);
preMarktes.createTaker(offerAddr, offerAmount / 2);
// Alice aborts the offer
vm.prank(user);
preMarktes.abortAskOffer(stockAddr, offerAddr);
// Alice withdraws her refund
capitalPool.approve(address(mockUSDCToken));
vm.prank(user);
tokenManager.withdraw(address(mockUSDCToken), TokenBalanceType.MakerRefund);
// // Record final balances
uint256 aliceFinalBalance = mockUSDCToken.balanceOf(user);
uint256 contractFinalBalance = mockUSDCToken.balanceOf(address(capitalPool));
// // Calculate refunded and locked amounts
uint256 refundedAmount = aliceFinalBalance - aliceInitialBalance;
uint256 lockedAmount = contractFinalBalance - (contractInitialBalance - refundedAmount);
console.log("Initial collateral:", collateralAmount);
console.log("Refunded to Alice:", refundedAmount);
// Assert the vulnerability
// "Alice should receive less than full collateral"
assert(refundedAmount < collateralAmount);
}

Tools Used

Manual Review, Foundry

Recommendations

Modify the abortAskOffer function to handle partial aborts in Turbo Mode correctly.

Updates

Lead Judging Commences

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

[invalid] finding-PreMarkets-abortAskOffer-remainingAmount-compute

Valid high, for cancelled offers, the unused collateral should be returned back to the maker. The `remainingAmount` is calculated wrongly with regards to usedPoints instead of unused points. Note: See comments under 826 and 907 for invalidation reasons

Appeal created

cryptomoon Auditor
10 months ago
0xnevi Lead Judge
10 months ago
0xnevi Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

[invalid] finding-PreMarkets-abortAskOffer-remainingAmount-compute

Valid high, for cancelled offers, the unused collateral should be returned back to the maker. The `remainingAmount` is calculated wrongly with regards to usedPoints instead of unused points. Note: See comments under 826 and 907 for invalidation reasons

Support

FAQs

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