Gasless Send
id: LS38M
title: Gasless Send
baseSeverity: M
category: ether-transfer
language: solidity
blockchain: [ethereum, polygon, bsc, arbitrum, optimism]
impact: Incomplete ETH transfers, logic bypass, stuck funds
status: draft
complexity: medium
attack_vector: external
mitigation_difficulty: easy
versions: [">=0.6.0", "<=0.8.25"]
cwe: CWE-755
swc: SWC-113
π Description
- A Gasless Send vulnerability occurs when a contract uses Solidity's address.send() or address.transfer() to send ETH, which forwards only 2300 gas.
- If the recipient is a contract with a fallback or receive function requiring more gas, the transfer silently fails, breaking core flows such as refunds, withdrawals, or payments.
- send() returns a boolean and does not revert, so failures are easy to ignore.
- transfer() does revert, but both restrict gas and are not suitable for cross-contract ETH transfers.
- Contracts like multisigs, upgradable proxies, or fallback-heavy wallets require more than 2300 gas, and fail under these methods.
- This vulnerability leads to:
- Denial of ETH to contract-based users
- Trapped funds or inconsistent state
- Refund failures in auctions, staking, or presales
π¨ Vulnerable Code
function refund(address recipient) external {
require(address(this).balance >= 1 ether, "Insufficient");
recipient.send(1 ether); // β silently fails if recipient is a contract needing >2300 gas
}
π§ͺ Exploit Scenario
Step-by-step exploit process:
- A user participates in a presale and later requests a refund.
- The contract tries to use send() to refund ETH.
- The userβs address is a contract (e.g., Gnosis Safe) that has a fallback consuming more than 2300 gas.
- The send() fails silently and returns false.
- The contract logic continues without reverting or informing the user.
- Funds remain stuck and unrecoverable from the user's perspective.
Assumptions:
- Contract uses send() or transfer() instead of call.
- Recipient may be a smart contract wallet or proxy requiring gas.
- No event is emitted or error handling performed on failure.
β Fixed Code
function refund(address payable recipient) external {
(bool success, ) = recipient.call{value: 1 ether}("");
require(success, "ETH refund failed");
}
π§ Contextual Severity
- context: "Funds sent to unknown user contracts with fallback logic"
severity: M
reasoning: "May fail for legitimate users, blocking key protocol functions"
- context: "All recipients are EOAs or known minimal contracts"
severity: L
reasoning: "Low risk; recipients unlikely to consume >2300 gas"
- context: "ETH transfers done via pull model"
severity: I
reasoning: "Vulnerability does not apply"
π‘οΈ Prevention
Primary Defenses
- Never use send() or transfer() in modern Solidity (post-0.6.0).
- Use call{value: amount}("") for ETH transfers with proper error handling.
- Emit events for every ETH transfer attempt and failure.
Additional Safeguards
- Validate recipients (e.g., known EOAs vs. contracts).
- Add fallback withdrawal or reclaim mechanism.
- Design workflows to support both pull and push payment models.
Detection Methods
- Static analysis for send() or transfer() usage.
- Fuzz testing with contracts requiring >2300 gas.
- Tools: Slither (dangerous-low-level), Foundry reentrancy/invariant tests
π°οΈ Historical Exploits
- Name: Ethereum Classic DAO Refund Failures
- Date: 2016
- Loss: ~$6M
- Post-mortem: Link to post-mortem
π Further Reading
β Vulnerability Report
id: LS38M
title: Gasless Send
severity: M
score:
impact: 4
exploitability: 4
reachability: 5
complexity: 1
detectability: 5
finalScore: 3.95
π Justifications & Analysis
- Impact: Blocks ETH transfers to valid users; may result in user fund loss or refund denial.
- Exploitability: No active exploit neededβjust use a contract wallet with >2300 gas needs.
- Reachability: Common in payout, withdrawal, and presale refund logic.
- Complexity: Extremely simple; accidental user behavior can trigger it.
- Detectability: Highly detectable via Slither, Linter, and Solidity documentation best practices.