Pieces Protocol

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

[M-9] Gas Limit Issues in TokenDivider::buyOrder Due to Use of call for Ether Transfers

Summary

The buyOrder function uses call to transfer Ether to the seller and the contract owner. While call is flexible and allows for arbitrary data, it does not impose a gas limit, which can lead to out-of-gas errors if the recipient is a contract with a complex fallback function. This could cause the transaction to fail, leaving the contract in an inconsistent state.

Vulnerability Details

This vulnerability arises because call forwards all remaining gas to the recipient. If the recipient is a contract with a complex or malicious fallback function, it could consume excessive gas, causing the transaction to revert due to an out-of-gas error. This would leave the contract in an inconsistent state, as the state updates (e.g., balances and sell orders) would already have been applied before the Ether transfer.

Impact

Medium Impact: If the recipient is a contract with a complex fallback function, the call could consume excessive gas, causing the transaction to fail.

This could leave the contract in an inconsistent state and prevent users from completing their purchases.

Tools Used

Slither, Foundry

Recommendations

Recommended Mitigation:
To prevent out-of-gas errors, use transfer or send for small Ether transfers, or implement a gas limit for call. Here’s how to implement these fixes:

  1. Use transfer or send:

These functions have a fixed gas stipend of 2300 gas, which is sufficient for simple Ether transfers but prevents complex fallback functions from executing.

  1. Implement a Gas Limit for call:

If you need to use call, specify a gas limit to prevent excessive gas consumption.

function buyOrder(uint256 orderIndex, address seller) external payable {
// Existing checks...
// Transfer Ether to the seller with a gas limit
(bool success, ) = payable(order.seller).call{value: (order.price - sellerFee), gas: 2300}("");
if (!success) {
revert TokenDivider__TransferFailed();
}
// Transfer Ether to the owner with a gas limit
(bool taxSuccess, ) = payable(owner()).call{value: fee, gas: 2300}("");
if (!taxSuccess) {
revert TokenDivider__TransferFailed();
}
// Transfer ERC20 tokens
bool transferSuccess = IERC20(order.erc20Address).transfer(msg.sender, order.amount);
if (!transferSuccess) {
revert TransferFailed();
}
}

Why these mitigations work:

  1. transfer or send: These functions impose a gas limit of 2300, which is sufficient for simple Ether transfers but prevents complex fallback functions from executing.

  2. Gas Limit for call: Specifying a gas limit ensures that the call does not consume excessive gas, preventing out-of-gas errors.

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.