OrderBook

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

[M-01] USDC supports blacklisting feature, and Orderbook contract loses all functionality if it gets blacklisted

Root + Impact

Description

  • Users are able to place orders for their tokens on their selected price in USDC token only, and only USDC token can be used to fill orders.

  • USDC is an ERC-20 token contract which is controlled by Circle. Custodial wallets owned by Circle have the ability to restrict certain addresses of their choosing to be blacklisted from all USDC transactions.

  • If the OrderBook contract is blacklisted from USDC transactions, then no orders will be filled due to buyOrder() function getting reverted on USDC transactions for protocol fees.

function buyOrder(uint256 _orderId) public {
Order storage order = orders[_orderId];
// Validation checks
if (order.seller == address(0)) revert OrderNotFound();
if (!order.isActive) revert OrderNotActive();
if (block.timestamp >= order.deadlineTimestamp) revert OrderExpired();
order.isActive = false;
uint256 protocolFee = (order.priceInUSDC * FEE) / PRECISION;
uint256 sellerReceives = order.priceInUSDC - protocolFee;
@> iUSDC.safeTransferFrom(msg.sender, address(this), protocolFee);
iUSDC.safeTransferFrom(msg.sender, order.seller, sellerReceives);
IERC20(order.tokenToSell).safeTransfer(msg.sender, order.amountToSell);
totalFees += protocolFee;
emit OrderFilled(_orderId, msg.sender, order.seller);
}

Risk

Likelihood:

  • Only when OrderBook contract gets blacklisted from USDC transactions

Impact:

  • No order will be filled due to failing buyOrder() function, hence rendering the contract unusable.

Proof of Concept

Add the following ERC-20 contract code for a mock USDC with blacklisting feature in test/mocks/BlacklistingUSDC.sol:

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.26;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract BlacklistingUSDC is ERC20, Ownable {
uint8 constant tokenDecimals = 6;
mapping(address => bool) public isBlacklisted;
modifier notBlacklisted(address _from, address _to) {
require(!isBlacklisted[_from], "Sender is blacklisted");
require(!isBlacklisted[_to], "Recipient is blacklisted");
_;
}
constructor() ERC20("BlacklistingUSDC", "bUSDC") Ownable(msg.sender) {}
function decimals() public pure override returns (uint8) {
return tokenDecimals;
}
function mint(address to, uint256 value) public {
uint256 updateDecimals = uint256(tokenDecimals);
_mint(to, (value * 10 ** updateDecimals));
}
function blacklist(address _account) external onlyOwner {
isBlacklisted[_account] = true;
}
function unBlacklist(address _account) external onlyOwner {
isBlacklisted[_account] = false;
}
function transfer(address to, uint256 value) public override notBlacklisted(msg.sender, to) returns (bool) {
super.transfer(to, value);
return true;
}
function transferFrom(address from, address to, uint256 value) public override notBlacklisted(from, to) returns (bool) {
super.transferFrom(from, to, value);
return true;
}
}

Then place the following function in test/TestOrderBook.t.sol and run with forge test --mt test_USDCBlacklistedContract:

import {BlacklistingUSDC} from "./mocks/BlacklistingUSDC.sol";
.
.
.
function test_USDCBlacklistedContract() public {
// Setup contract and mock USDC with blacklisting feature, and blacklist the OrderBook contract
vm.startPrank(owner);
BlacklistingUSDC busdc = new BlacklistingUSDC();
book = new OrderBook(address(weth), address(wbtc), address(wsol), address(busdc), owner);
busdc.blacklist(address(book));
vm.stopPrank();
// Create a Sell order from Alice
vm.startPrank(alice);
wbtc.approve(address(book), 2e8);
uint256 aliceId = book.createSellOrder(address(wbtc), 2e8, 180_000e6, 2 days);
vm.stopPrank();
// Setup Dan to but Alice's order
vm.startPrank(dan);
busdc.approve(address(book), 200_000e6);
busdc.mint(dan, 180_000e6);
// buyOrder is expected to fail due to contract being blacklisted
vm.expectRevert("Recipient is blacklisted");
book.buyOrder(aliceId);
}

This test case passes when the buyOrder() function reverts due to contract being blacklisted.

Recommended Mitigation

  • Use a non-blacklistable wrapper like wUSDC token for purchases

-constructor(address _weth, address _wbtc, address _wsol, address _usdc, address _owner) Ownable(_owner) {
+constructor(address _weth, address _wbtc, address _wsol, address _wusdc, address _owner) Ownable(_owner) {
  • Use a decentralised token like DAI for purchases

Updates

Lead Judging Commences

yeahchibyke Lead Judge about 2 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.