Withdrawal Pattern
A security pattern where users pull funds from a contract rather than the contract pushing funds to users, reducing reentrancy risk.
The withdrawal pattern is a fundamental security design where users actively pull their funds from a contract rather than the contract automatically pushing funds to users. This pull-based approach significantly reduces the attack surface for reentrancy attacks and other vulnerabilities that arise from unexpected external calls.
Push vs Pull Payments
In a push payment model, the contract sends funds to users automatically:
1// Dangerous push pattern2function distributeRewards(address[] calldata winners) external {3 for (uint i = 0; i < winners.length; i++) {4 // External call in a loop - multiple risks5 payable(winners[i]).transfer(reward);6 }7}
This approach has several problems. If any recipient is a contract that reverts on receive, the entire distribution fails. Malicious contracts can consume excessive gas or attempt reentrancy. The function may hit gas limits with many recipients.
The withdrawal pattern inverts this relationship:
1// Safe pull pattern2mapping(address => uint256) public pendingWithdrawals;34function recordWin(address winner, uint256 amount) internal {5 pendingWithdrawals[winner] += amount;6}78function withdraw() external {9 uint256 amount = pendingWithdrawals[msg.sender];10 require(amount > 0, "Nothing to withdraw");1112 pendingWithdrawals[msg.sender] = 0;1314 (bool success, ) = msg.sender.call{value: amount}("");15 require(success, "Transfer failed");16}
Now each user is responsible for claiming their own funds. A malicious or broken recipient contract only affects its own withdrawal, not others.
Security Benefits
The withdrawal pattern provides multiple security advantages:
Reentrancy isolation: When combined with the checks-effects-interactions pattern (updating state before the external call), reentrancy attempts have no effect. The attacker's balance is already zero before they can re-enter.
Failure isolation: If one user's withdrawal fails, other users are unaffected. Compare this to push patterns where one problematic recipient can block everyone.
Gas efficiency: Users pay gas for their own withdrawals. The contract doesn't need to iterate through recipients or worry about gas limits for large distributions.
Predictable execution: Each withdrawal is a single, self-contained operation. There are no loops, no arrays of recipients, and no complex failure scenarios to handle.
Implementing the Pattern Correctly
Even the withdrawal pattern can be implemented insecurely. Follow these guidelines:
Update state before external calls: Always set the user's balance to zero before sending funds. This prevents reentrancy even without a reentrancy guard.
1function withdraw() external {2 uint256 amount = pendingWithdrawals[msg.sender];3 pendingWithdrawals[msg.sender] = 0; // State update FIRST45 (bool success, ) = msg.sender.call{value: amount}("");6 require(success, "Transfer failed");7}
Use call instead of transfer: The transfer and send functions forward only 2300 gas, which can cause legitimate contract wallets to fail. Use call with proper checks.
Handle failure appropriately: Decide whether failed withdrawals should revert (returning funds to pending state) or simply return false. Most implementations revert.
Consider partial withdrawals: Some applications benefit from allowing users to withdraw specific amounts rather than their entire balance.
When Push Is Acceptable
The withdrawal pattern isn't always necessary. Push payments may be appropriate when:
- The recipient is a trusted, known contract (like a protocol treasury)
- Failure should block the entire operation (like atomic swaps)
- The number of recipients is small and fixed
- Gas costs of multiple withdrawal transactions outweigh security benefits
Even in these cases, implement push payments carefully with reentrancy protection and failure handling.
Withdrawal Pattern in DeFi
Major DeFi protocols extensively use the withdrawal pattern:
Lending protocols: Borrowers withdraw collateral and lenders withdraw deposits through explicit withdraw functions.
Yield aggregators: Users deposit funds and later withdraw principal plus yield through pull-based interfaces.
Staking contracts: Stakers claim rewards through dedicated claim functions rather than receiving automatic distributions.
Auction contracts: Outbid participants withdraw their returned bids; the contract doesn't push refunds automatically.
Testing Withdrawal Implementations
When testing withdrawal functionality:
- Verify correct balance updates before and after withdrawal
- Test withdrawal of exact balance, partial amounts, and zero
- Attempt reentrancy attacks against the withdrawal function
- Test withdrawal to both EOAs and contract accounts
- Verify behavior when contract has insufficient funds
- Test concurrent withdrawals from multiple users
The withdrawal pattern is one of the most important security patterns in smart contract development. Mastering it prevents entire categories of vulnerabilities and is expected in any production DeFi contract.
Articles Using This Term
Learn more about Withdrawal Pattern in these articles:
Related Terms
Reentrancy Attack
A vulnerability where external calls allow malicious contracts to recursively call back before state updates complete.
Reentrancy Guard
Smart contract security pattern preventing attackers from recursively calling functions to drain funds during execution.
Checks-Effects-Interactions
Solidity security pattern ordering operations to validate inputs, update state, then make external calls preventing reentrancy vulnerabilities.
Need expert guidance on Withdrawal Pattern?
Our team at Zealynx has deep expertise in blockchain security and DeFi protocols. Whether you need an audit or consultation, we're here to help.
Get a Quote

