• YouTube Channel
  • System Status
  • VS Code Extension
  • Reentrancy Affecting Events Ordering

    Overview

    What is the Reentrancy Affecting Events Ordering vulnerability?

    The Ethereum Virtual Machine (EVM) has logging functionality associated with transactions. The log data can be deliberately generated by programmers using the emit keyword in Solidity, which will place an entry into the transaction log. These logs are not accessible to smart contracts, but can be read and utilized by external applications.

    If a function that emits an event makes an external call, it is possible that it can be re-entered (possibly indirectly) by the caller. If the function emits an event after making an external call, a reentrancy attack could cause events to be emitted out of order. If an external application depends on ordered events for correct operation, this may lead to exploits of the application that relies on Ethereum.

    Further reading: Solidity Documentation: Events

    Technical example of vulnerable code

      // SPDX-License-Identifier: Unlicense
      pragma solidity ^0.8.0;
    
      contract ReentrantMarketplace {
          // Represents an item in the marketplace
          struct Item {
              uint256 id;
              uint256 price;
              uint256 remainingQuantity;
          }
    
          // Mapping from item IDs to their details
          mapping(uint256 => Item) public items;
    
          // Event emitted when an item's price is updated
          event PriceUpdate(uint256 itemId, uint256 newPrice);
    
          // Event emitted when an item is purchased
          event ItemPurchased(uint256 itemId, address buyer, uint256 price);
    
          /**
           * Allows the contract owner to add an item to the marketplace.
           */
          function addItem(uint256 _itemId, uint256 _price, uint256 _startQuantity) external {
              items[_itemId] = Item(_itemId, _price, _startQuantity);
              emit PriceUpdate(_itemId, _price);
          }
    
          /**
           * Allows users to purchase an item. This function contains a re-entrancy vulnerability.
           */
          function purchaseItem(uint256 _itemId) external payable {
              Item storage item = items[_itemId];
              require(msg.value >= item.price, "Insufficient payment");
    
              item.remainingQuantity--;
              item.price += 10;
    
              // Vulnerable external call before emitting an event
              (bool sent, ) = msg.sender.call{value: msg.value - item.price}("");
              require(sent, "Failed to return excess Ether");
    
              emit ItemPurchased(_itemId, msg.sender, item.price);
    
              // Price change information is not logged until after the external call.
              emit PriceUpdate(_itemId, item.price);
          }
    
          /**
           * Returns the price of an item.
           */
          function getItemPrice(uint256 _itemId) external view returns (uint256) {
              return items[_itemId].price;
          }
      }
    
    

    In the example above, the smart contract ReentrantMarketplace represents a marketplace for purchasing abstract Items, which could be something like an NFT in practice. Each item has a limited supply, and as the supply goes down, the price goes up. Price changes are emitted as events, which external applications might monitor. In this contract, the function purchaseItem() does not emit the event related to the price change until after making an external call, so if this function were re-entered, the events could be emitted into the transaction log out of order. Any external application monitoring these to make a transaction in a particular price range might fail to do so and could potentially end up overpaying based on incorrectly-ordered events.

    Technical example of how to fix the vulnerability

      // SPDX-License-Identifier: Unlicense
      pragma solidity ^0.8.0;
    
      contract CorrectedMarketplace {
          // Represents an item in the marketplace
          struct Item {
              uint256 id;
              uint256 price;
              uint256 remainingQuantity;
          }
    
          // Mapping from item IDs to their details
          mapping(uint256 => Item) public items;
    
          // Event emitted when an item's price is updated
          event PriceUpdate(uint256 itemId, uint256 newPrice);
    
          // Event emitted when an item is purchased
          event ItemPurchased(uint256 itemId, address buyer, uint256 price);
    
          /**
           * Allows the contract owner to add an item to the marketplace.
           */
          function addItem(uint256 _itemId, uint256 _price, uint256 _startQuantity) external {
              items[_itemId] = Item(_itemId, _price, _startQuantity);
              emit PriceUpdate(_itemId, _price);
          }
    
          /**
           * Allows users to purchase an item. This function contains a re-entrancy vulnerability.
           */
          function purchaseItem(uint256 _itemId) external payable {
              Item storage item = items[_itemId];
              require(msg.value >= item.price, "Insufficient payment");
    
              item.remainingQuantity--;
              item.price += 10;
    
              emit ItemPurchased(_itemId, msg.sender, item.price);
              emit PriceUpdate(_itemId, item.price);
    
              // Vulnerable external call before emitting an event
              (bool sent, ) = msg.sender.call{value: msg.value - item.price}("");
              require(sent, "Failed to return excess Ether");
          }
    
          /**
           * Returns the price of an item.
           */
          function getItemPrice(uint256 _itemId) external view returns (uint256) {
              return items[_itemId].price;
          }
      }
    
    

    In the corrected example above, the function purchaseItem() emits the corresponding events before making the external call to return any change to the caller, avoiding the ordering issue due to the reentrancy in the prior example.