Why Use the Withdrawal Pattern?

Jim McDonald
Jul 13, 2017 · 3 min read

I no longer post directly on Medium; to view the latest copy of this article please go to https://www.wealdtech.com/articles/why-use-the-withdrawal-pattern/


The article Building Ethereum Payment Channels gave rise to the question of why closing a channel adds funds to a balance rather than simply returning funds to the channel participants. This mechanism is known as the withdrawal pattern, and this article provides an explanation of why it is necessary.

The issue arises when an Ethereum transaction attempts to release funds to an address not under the control of the person sending the transaction. A much-simplified example contract that does this is shown below:

(Please note that this contract is purely for illustrative purposes and is totally unsuitable for any real work.)

The way this contract operates is as follows:

  • the sender sends a deposit() transaction, with the address of the recipient
  • the recipient sends a split() transaction, which sends half of the deposit to the recipient and half back to the sender

Or graphically:

There are no loops or conditionals so control flow is very easy to follow. What could go wrong?

The issue is due to the fact that accounts and contracts are both valid participants in transactions, and that contracts do whatever they have been programmed to do when they receive funds as part of a transaction.

When a contract receives funds it invokes a special function on the contract, called the fallback function. This allows the contract to have control over the control flow of the transaction that invoked it.

A purely malevolent contract is shown below:

The way this contract operates is as follows:

  • the sender sends a forward() transaction, with the address of the recipient. This forwards the call to the BadSplitter contract described previously
  • when the recipient attempts to call split() in the BadSplitter contract they find that their transaction fails. This is because BadSplitter’s call to sender.transfer() invokes the fallback function in BadSender, which immediately fails and causes the entire transaction to fail

Or graphically:

It is important to understand that reverting the transaction cancels all actions taken by it, so the state of Ethereum is as if the transaction never took place. If this were not the case, the contract could simply be written to send funds to the recipient before the sender and avoid the issue.

As mentioned above this is a purely malevolent contract, in that it denies the recipient access to their funds unconditionally. Given that a smart contract is a program, however, a more advanced BadSender could allow or deny the transaction based on time, balance of another account, an internal flag etc. The malicious possibilities are endless and allow the sender to hold the recipient to ransom.

In the context of payment channels, this has negative implications because it would be possible for a bad sender to revert the transaction whenever the recipient attempts to close the channel, denying the recipient their promised funds. Once the expiry time for the channel has closed the sender could expire the channel and receive all of their deposited funds and permanently depriving the recipient of their promised funds.

The solution to this issue is to use the withdrawal pattern. By keeping track of balances internally within the contract and forcing each user to withdraw their own funds it makes redundant the fact that the other party in the transaction might be a malicious contract. More details about the withdrawal pattern are available in the official solidity documentation.

Jim McDonald

Written by

Focusing on real-world use cases for Ethereum

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade