code-423n4 / 2024-03-dittoeth-findings

0 stars 0 forks source link

Market manipulation, front-running, distorted prices, potential financial losses for users. #211

Closed c4-bot-9 closed 4 months ago

c4-bot-9 commented 5 months ago

Lines of code

https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/facets/BidOrdersFacet.sol#L130-L204 https://github.com/code-423n4/2024-03-dittoeth/blob/91faf46078bb6fe8ce9f55bcb717e5d2d302d22e/contracts/libraries/LibOrders.sol#L556-L626

Vulnerability details

Description

bidMatchAlgo and sellMatchAlgo, which are responsible for matching incoming orders with existing orders in the orderbook based on price.

BidOrdersFacet.sol#bidMatchAlgo

function bidMatchAlgo(
    address asset,
    STypes.Order memory incomingBid,
    MTypes.OrderHint[] memory orderHintArray,
    MTypes.BidMatchAlgo memory b
) private returns (uint88 ethFilled, uint88 ercAmountLeft) {
    // ... code ...
    while (true) {
        // ... code ...
        STypes.Order memory lowestSell = _getLowestSell(asset, b);
        if (incomingBid.price >= lowestSell.price) {
            matchlowestSell(asset, lowestSell, incomingBid, matchTotal);
            // ... code ...
        } else {
            // ... code ...
            return matchIncomingBid(asset, incomingBid, matchTotal, b);
        }
    }
}

LibOrders.sol#sellMatchAlgo

function sellMatchAlgo(
    address asset,
    STypes.Order memory incomingAsk,
    MTypes.OrderHint[] memory orderHintArray,
    uint256 minAskEth
) internal {
    // ... code ...
    while (true) {
        STypes.Order memory highestBid = s.bids[asset][startingId];
        if (incomingAsk.price <= highestBid.price) {
            matchHighestBid(incomingAsk, highestBid, asset, matchTotal);
            // ... code ...
        } else {
            // ... code ...
            return;
        }
    }
}

These functions iterate through the orderbook, matching the incoming order with the lowest sell order or highest bid order, respectively, based on price.

The bidMatchAlgo and sellMatchAlgo functions are expected to match incoming orders with existing orders in the orderbook based on price, facilitating trades between buyers and sellers.

The expected inputs are the asset being traded, the incomingBid or incomingAsk order, and any necessary orderHintArray for efficient matching.

The intended outcome is to fill the incoming order as much as possible by matching it with existing orders, updating the orderbook state accordingly, and returning the filled amounts and any remaining amounts.

Vulnerability Details

The edge case is from the lack of checks or restrictions on the size of the orders being placed and matched. An attacker can exploit this by placing large orders using funds obtained through a flash loan, influencing the market prices significantly.

The steps an attacker can take are:

  1. Take out a large flash loan of the traded asset.
  2. Place a large buy order at a high price to artificially inflate the asset's price.
  3. Simultaneously place a sell order at a lower price, but still above the current market price.
  4. The attacker's manipulated orders get matched with existing orders, executing trades at the manipulated prices.
  5. Profit from the price difference between the buy and sell orders.
  6. Repay the flash loan and keep the profit within the same transaction.

The lines responsible for the vulnerability are:

An attacker can manipulate the market prices by placing large orders using flash loans. The bidMatchAlgo and sellMatchAlgo functions will match these manipulated orders with existing orders, executing trades at artificially inflated or deflated prices.

The attacker can profit from the price difference between their manipulated buy and sell orders, effectively buying low and selling high within the same transaction. This behavior deviates from the intended fair and orderly matching of orders based on genuine market demand and supply.

Impact

Proof of Concept

Scenario:

  1. The current market price of the traded token is $10 per token.
  2. The orderbook has the following existing orders:
    • Sell orders:
      • 500 tokens at $10.50
      • 1000 tokens at $11.00
    • Buy orders:
      • 200 tokens at $9.80
      • 800 tokens at $9.50
  3. User A wants to buy a large number of tokens, which will significantly impact the price.
  4. The attacker, User B, monitors the mempool and identifies User A's pending order.

Front-Running Attack:

  1. User A submits a large bid order to buy 2000 tokens at $10.20 per token.
  2. User B sees User A's pending order in the mempool.
  3. User B quickly inserts their own sell order to sell 1000 tokens at $10.19 per token, ahead of User A's order.
  4. User A's order gets matched:
    • 500 tokens are bought at $10.50 from the existing sell order.
    • 1000 tokens are bought at $10.19 from User B's front-running sell order.
    • The remaining 500 tokens are bought at $11.00 from the existing sell order.
  5. User B's sell order is filled at a higher price ($10.19) than the current market price ($10.00), benefiting from the price movement caused by User A's large order.

Impact of Front-Running:

Sandwich Attack:

  1. User A submits a large bid order to buy 2000 tokens at $10.20 per token.
  2. User B sees User A's pending order in the mempool.
  3. User B quickly inserts their own sell order to sell 1000 tokens at $10.19 per token, ahead of User A's order.
  4. User B also places a buy order to buy 1000 tokens at $10.21 per token, after User A's order.
  5. User A's order gets matched:
    • 500 tokens are bought at $10.50 from the existing sell order.
    • 1000 tokens are bought at $10.19 from User B's front-running sell order.
    • The remaining 500 tokens are bought at $11.00 from the existing sell order.
  6. User B's sell order is filled at a higher price ($10.19) than the current market price ($10.00).
  7. User B's buy order is then filled at $10.21, allowing them to acquire the tokens at a lower price than the market price after User A's order is executed.

Impact of Sandwich Attack:

Potential Consequences:

Recommended Mitigation Steps

  1. Implement price impact checks:
    • Check the price deviation of incoming orders from the current market price.
    • Limit the size of orders relative to the available liquidity to prevent significant price impact.
function validatePriceImpact(STypes.Order memory incomingOrder, uint256 currentPrice, uint256 availableLiquidity) internal view {
    uint256 priceDeviation = calculatePriceDeviation(incomingOrder.price, currentPrice);
    require(priceDeviation <= MAX_PRICE_DEVIATION, "Price deviation too high");

    uint256 orderSize = incomingOrder.ercAmount.mul(incomingOrder.price);
    require(orderSize <= availableLiquidity.mul(MAX_ORDER_SIZE_PERCENT).div(100), "Order size too large");
}
  1. Introduce slippage tolerance:
    • Allow users to specify a maximum slippage tolerance for their orders.
    • Ensure that the executed prices of orders are within the specified slippage tolerance.
function matchlowestSell(
    address asset,
    STypes.Order memory lowestSell,
    STypes.Order memory incomingBid,
    MTypes.Match memory matchTotal,
    uint256 slippageTolerance
) private {
    // ... code ...
    uint256 executionPrice = calculateExecutionPrice(incomingBid, lowestSell);
    require(executionPrice <= incomingBid.price.mul(slippageTolerance).div(100), "Slippage tolerance exceeded");
    // ... code ...
}
  1. Implement flash loan detection and circuit breakers:
    • Monitor transactions for large flash loans and suspicious trading patterns.
    • Implement circuit breakers that halt trading or limit order sizes when unusual price movements or volumes are detected.
function detectFlashLoan(address borrower, uint256 amount) internal {
    FlashLoan storage loan = flashLoans[borrower];
    if (loan.amount == 0) {
        loan.amount = amount;
        loan.timestamp = block.timestamp;
    } else {
        require(block.timestamp > loan.timestamp + FLASH_LOAN_INTERVAL, "Flash loan detected");
        loan.amount = amount;
        loan.timestamp = block.timestamp;
    }
}

function checkCircuitBreaker() internal {
    require(!circuitBreakerTriggered, "Circuit breaker triggered");
    if (isPriceMovementUnusual() || isVolumeUnusual()) {
        circuitBreakerTriggered = true;
        // Halt trading or limit order sizes
    }
}

Assessed type

MEV

c4-pre-sort commented 5 months ago

raymondfam marked the issue as insufficient quality report

c4-pre-sort commented 5 months ago

raymondfam marked the issue as primary issue

raymondfam commented 5 months ago

Readme: Issues related to front-running: can front-run someone's order, liquidation, the chainlink/uniswap oracle update.

c4-judge commented 4 months ago

hansfriese marked the issue as unsatisfactory: Out of scope