Pieces Protocol

First Flight #32
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: medium
Invalid

[M-7] Front-Running in TokenDivider::buyOrder Allows Attackers to Outbid Legitimate Users

Summary

The buyOrder function is vulnerable to front-running attacks because it does not protect against malicious actors monitoring the mempool and outbidding legitimate users. An attacker can observe a legitimate user’s transaction in the mempool, copy their parameters, and submit a higher bid with a higher gas fee to ensure their transaction is mined first. This undermines the fairness of the marketplace and could discourage users from participating.

Vulnerability Details

The buyOrder function processes transactions in a way that allows attackers to front-run legitimate users. Since Ethereum transactions are publicly visible in the mempool before they are mined, an attacker can monitor pending transactions and submit a competing transaction with a higher gas fee to ensure it is processed first. This is particularly problematic in auctions or marketplaces where the first valid transaction wins.

Impact

Medium Impact: Legitimate users may lose out on purchasing NFT fractions because attackers can front-run their transactions.

Marketplace Fairness: The fairness of the marketplace is undermined, as attackers can exploit the system to their advantage.

User Dissatisfaction: Users may be discouraged from participating in the marketplace if they perceive it as unfair or exploitable.

Tools Used

Aderyn, Foundry

Recommendations

To prevent front-running, consider implementing one of the following mechanisms:

  1. Commit-Reveal Scheme
    A commit-reveal scheme ensures that bids are hidden until the reveal phase, making it impossible for attackers to front-run legitimate users.

Implementation Example:

mapping(address => bytes32) public commitments;
function commitToBuy(bytes32 commitment) external {
commitments[msg.sender] = commitment;
}
function revealBuy(uint256 orderIndex, address seller, uint256 amount, bytes32 secret) external payable {
bytes32 commitment = keccak256(abi.encodePacked(amount, secret));
require(commitments[msg.sender] == commitment, "TokenDivider: Invalid commitment");
SellOrder memory order = s_userToSellOrders[seller][orderIndex];
require(msg.value >= order.price, "TokenDivider: Incorrect Ether amount");
// Process the buy order
balances[msg.sender][order.erc20Address] += order.amount;
s_userToSellOrders[seller][orderIndex] = s_userToSellOrders[seller][s_userToSellOrders[seller].length - 1];
s_userToSellOrders[seller].pop();
emit OrderSelled(msg.sender, order.price);
// Transfer Ether and tokens
(bool success, ) = payable(order.seller).call{value: order.price}("");
require(success, "TokenDivider: Transfer failed");
bool transferSuccess = IERC20(order.erc20Address).transfer(msg.sender, order.amount);
require(transferSuccess, "TokenDivider: ERC20 transfer failed");
}
  1. Dutch Auction or Minimum Bid Increment
    A Dutch auction or minimum bid increment mechanism reduces the incentive for front-running by making it less profitable for attackers to outbid legitimate users.

Implementation Example:

uint256 public minimumBidIncrement = 0.01 ether;
function buyOrder(uint256 orderIndex, address seller) external payable {
SellOrder memory order = s_userToSellOrders[seller][orderIndex];
require(msg.value >= order.price + minimumBidIncrement, "TokenDivider: Bid too low");
// Process the buy order
balances[msg.sender][order.erc20Address] += order.amount;
s_userToSellOrders[seller][orderIndex] = s_userToSellOrders[seller][s_userToSellOrders[seller].length - 1];
s_userToSellOrders[seller].pop();
emit OrderSelled(msg.sender, order.price);
// Transfer Ether and tokens
(bool success, ) = payable(order.seller).call{value: order.price}("");
require(success, "TokenDivider: Transfer failed");
bool transferSuccess = IERC20(order.erc20Address).transfer(msg.sender, order.amount);
require(transferSuccess, "TokenDivider: ERC20 transfer failed");
}
Updates

Lead Judging Commences

fishy Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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