Skip to content

ERC721FloorBidMatcher blockchain reorg vulnerability#

Low Risk

A blockchain reorganization occurs when accepted blocks of transactions are thrown out and replaced with other blocks of transactions when a longer chain of truth is found. More information blockchain reorgs here: https://medium.com/blockvigil/how-we-deal-with-chain-reorganization-at-ethvigil-5a8c06859c7

Blockchain reorgs can cause vulnerabilities in smart contracts (that don't account for it) when users or software see and act on information in a blockchain that is later thrown out because of a blockchain reorganization.

A blockchain reorg vulnerability exists in ERC721FloorBidMatcher due to the following circumstances:

  1. The createBuyOrder functions create order IDs sequentially.
  2. The matchBuyOrder function uses uint256 orderId to identify orders.

Here is what can happen:

  1. User A (or some bot or other software) sees a new order of NFTs from address X at the price of 10 USDC. The order ID is 112.
  2. User A submits a transaction to execute the matchBuyOrder function using order ID 112 to sell his NFTs at that price (10 USDC).
  3. While User A's transaction is pending a block reorganization occurs that throws out the transaction that created order 112 and replaces it with a different transaction that creates a different order that is also order 112 (because order ids are sequentially created). This different order has a different price, let's say the price is 1 USDC instead of 10 USDC.
  4. User A's transaction executes the new/different order 112 and so the user sells his NFTs at a different price than he/she meant to.

Recommendation#

A way to prevent this is to generate the order id from the important information of the order plus an incrementing value like block.number. For example order ids could be generated like this:

uint256 orderId = keccak256(abi.encodePacked(_msgSender(), erc721TokenAddress, paymentTokenAddress, tokenPrice, block.number))

Another way to prevent this is by using additional parameters in the matchBuyOrder function to identify the order. For example matchBuyOrder could have these parameters:

function matchBuyOrder(
    uint256 orderId, 
    uint256[] calldata tokenIds,
    address erc721TokenAddress,
    address paymentTokenAddress,
    uint256 tokenPrice
)
And those parameters can be compared to the order's data to ensure the right order is executed.

Note: While I have seen this problem actually occur on Polygon I have not seen it occur on Ethereum. So I don't know how likely this issue could happen. One thing that can be looked at is how often chain reorgs happen. Etherscan shows chain reorg info here: https://etherscan.io/blocks_forked