Skip to content

Latest commit

 

History

History
82 lines (63 loc) · 3.75 KB

030.md

File metadata and controls

82 lines (63 loc) · 3.75 KB

Scruffy Chartreuse Wolverine

Medium

Inadequate Handling of ETH and WETH Transfers

Description

Both NounsAuctionHouseV2.sol and NounsAuctionHouseV3.sol implement ETH transfer mechanisms that attempt to send ETH directly to recipients.

If the direct ETH transfer fails—typically due to the recipient being a contract that requires more than the provided 30,000 gas stipend—the contracts fallback to wrapping the ETH into WETH and transferring WETH instead. This behavior is encapsulated in the _safeTransferETHWithFallback and _safeTransferETH internal functions.

https://github.com/sherlock-audit/2024-11-nounsdao/blob/main/nouns-monorepo/packages/nouns-contracts/contracts/NounsAuctionHouseV3.sol#L365

function _safeTransferETHWithFallback(address to, uint256 amount) internal {
    if (!_safeTransferETH(to, amount)) {
        IWETH(weth).deposit{ value: amount }();
        IERC20(weth).transfer(to, amount);
    }
}

function _safeTransferETH(address to, uint256 value) internal returns (bool) {
    bool success;
    assembly {
        success := call(30000, to, value, 0, 0, 0, 0)
    }
    return success;
}

This approach is intended to ensure that ETH transfers do not fail due to gas limitations in recipient contracts. However, it introduces potential vulnerabilities and unintended behaviors, especially concerning the fallback to WETH transfers.

Prerequisites

  • The recipient must be a contract with a fallback or receive function that either: a. Consistently consumes more than 30,000 gas, causing direct ETH transfers to fail. b. Intentionally or unintentionally mishandle WETH transfers.
  • An attacker could deploy a contract designed to exploit the fallback mechanism, either by reverting ETH transfers or manipulating WETH behavior.
  • The attacker deploys a contract with a fallback or receive function that consumes excessive gas or deliberately reverts ETH transfers.
  • The attacker participates in an auction by placing a bid using the malicious contract as the recipient.
  • Upon being outbid or settling an auction, the auction house attempts to refund the previous bidder (the malicious contract) by calling _safeTransferETHWithFallback.
  • The direct ETH transfer fails due to the malicious contract's fallback behavior.
  • The auction house then attempts to wrap the ETH into WETH and transfer WETH.
  • The auction house might be unable to process refunds correctly, leading to stalled auctions or funds being trapped within the contract.

Mitigation Recommendations

Instead of actively pushing ETH refunds to bidders, implement a withdrawal mechanism where bidders can claim their refunds.

mapping(address => uint256) public pendingReturns;

function createBid(uint256 nounId, uint32 clientId) public payable override {
    // ... existing logic ...

    if (lastBidder != address(0)) {
        pendingReturns[lastBidder] += _auction.amount;
    }

    // ... remaining logic ...
}

function withdraw() public {
    uint256 amount = pendingReturns[msg.sender];
    require(amount > 0, "No funds to withdraw");

    pendingReturns[msg.sender] = 0;

    (bool sent, ) = msg.sender.call{ value: amount }("");
    require(sent, "Failed to send ETH");
}

Adjust the gas stipend for ETH transfers to accommodate typical recipient contract needs without exposing excessive gas.

function _safeTransferETH(address to, uint256 value) internal returns (bool) {
    bool success;
    assembly {
        success := call(gas(), to, value, 0, 0, 0, 0)
    }
    return success;
}