OrderBook

First Flight #43
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: high
Likelihood: low
Invalid

Token Blacklisting Causes Permanent Fund Locking in Order Operations

Token Blacklisting Griefing Attack

Description

  • The OrderBook contract integrates with tokens like USDC that implement blacklisting functionality for regulatory compliance

  • When a seller becomes blacklisted after creating an order, both buying and cancelling the order become impossible, permanently locking seller funds in the contract

function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// ... validation checks ...
order.isActive = false;
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
uint256 sellerReceives = order.priceInUSDC - protocolFee;
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
// @> Reverts if seller is blacklisted by USDC
iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
totalFees += protocolFee;
emit OrderFilled(_orderId, msg.sender, order.seller);
}
function cancelSellOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// ... validation checks ...
order.isActive = false;
// @> Reverts if seller is blacklisted by their own token
IERC20(order.tokenToSell).safeTransfer(order.seller, order.amountToSell);
emit OrderCancelled(_orderId, order.seller);
}

Risk

Likelihood: Low

  • Requires seller to be blacklisted by USDC or their token after order creation

  • Blacklisting is typically reserved for serious compliance violations

  • Most users are not at risk of being blacklisted under normal circumstances

  • However, blacklisting can occur due to false positives or regulatory changes

Impact: High

  • Seller funds become permanently locked in the contract

  • No mechanism exists to recover funds from blacklisted addresses

  • Order cannot be filled (buyer transaction reverts)

  • Order cannot be cancelled (seller transaction reverts)

  • Complete loss of access to deposited tokens

Recommended Mitigation

+ // Add emergency withdrawal mechanism for blacklisted addresses
+ mapping(address => bool) public emergencyWithdrawalEnabled;
+
+ function enableEmergencyWithdrawal(address _user) external onlyOwner {
+ emergencyWithdrawalEnabled[_user] = true;
+ }
+ function emergencyWithdrawOrder(uint256 _orderId, address _to) external {
+ Order storage order = orders[_orderId];
+
+ require(order.seller != address(0), "Order not found");
+ require(order.isActive, "Order not active");
+ require(emergencyWithdrawalEnabled[order.seller], "Emergency withdrawal not enabled");
+ require(_to != address(0), "Invalid recipient");
+
+ order.isActive = false;
+
+ // Transfer to specified address instead of seller
+ IERC20(order.tokenToSell).safeTransfer(_to, order.amountToSell);
+
+ emit OrderCancelled(_orderId, order.seller);
+ emit EmergencyWithdrawal(order.tokenToSell, order.amountToSell, _to);
+ }
+ // Add alternative payment mechanism
+ function buyOrderWithAlternativePayment(uint256 _orderId, address _paymentRecipient) external {
+ Order storage order = orders[_orderId];
+
+ require(order.seller != address(0), "Order not found");
+ require(order.isActive, "Order not active");
+ require(block.timestamp < order.deadlineTimestamp, "Order expired");
+ require(_paymentRecipient != address(0), "Invalid payment recipient");
+
+ order.isActive = false;
+ uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
+ uint256 sellerReceives = order.priceInUSDC - protocolFee;
+
+ // Transfer fee to protocol
+ iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
+
+ // Transfer payment to alternative recipient (not seller)
+ iUSDC.safeTransferFrom(msg.sender, _paymentRecipient, sellerReceives);
+
+ // Transfer tokens to buyer
+ IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
+
+ totalFees += protocolFee;
+ emit OrderFilled(_orderId, msg.sender, order.seller);
+ }
+ // Add try-catch for safer transfers
function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// ... validation checks ...
order.isActive = false;
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
uint256 sellerReceives = order.priceInUSDC - protocolFee;
iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
+ // Try to transfer to seller, if fails, hold in contract
+ try iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives) {
+ // Success - normal flow
+ } catch {
+ // Failed - hold payment in contract for manual resolution
+ iUSDC.safeTransferFrom(msg.sender, address(this), sellerReceives);
+ emit PaymentHeld(_orderId, order.seller, sellerReceives);
+ }
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
totalFees += protocolFee;
emit OrderFilled(_orderId, msg.sender, order.seller);
}

Benefits of Mitigation:

  1. Prevents permanent fund loss - Emergency mechanisms for blacklisted users

  2. Maintains functionality - Orders can still be processed with alternative payment

  3. Owner intervention - Trusted entity can resolve blacklisting issues

  4. Graceful degradation - System continues operating despite blacklisting events

  5. Audit trail - Events track emergency actions and held payments

Updates

Lead Judging Commences

yeahchibyke Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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