The Pull Payment Pattern - Withdraw payments when outbid

Also note that any sort of math like this _should_ be checking for overflows, which we'll talk about later.

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the Million Ether Homepage course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Million Ether Homepage, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Million Ether Homepage

Let's get back to the million EtherPage contract. Now that we have a grasp on how to send payments, let's issue refunds when a pixel is outbid. Taking a look at our color pixel function, the obvious solution would be after we verified that the new bid price is higher than the sold price, we should dot send the funds back to the previous owner. That code might look like this. So we check that the message value is greater than the pixel sold price, and then we call pixel dot owner dot transfer pixel dot sold price. That looks pretty clean. But wait a second, what do you think about this code? Are there any problems or potential attacks? Remember that dot transfer will revert all changes if sending the funds fail. So if an attacker could craft a contract, which buys the pixel, and then always deliberately fails when you try to send it value, the result would be that no one could ever buy that pixel again. Every time someone outbid them, this contract would try to transfer the funds. But the transfer would fail, which means the contract would fail to instate the new bid and recolor the pixel. This sort of situation also comes up whenever you're looping over sending payments. For example, if you have a for loop where you need to send payments to several different contracts, make sure one contract failing won't prevent the addresses later in the loop from receiving their funds. This problem shows up in Ethereum smart contracts all the time. So much so that the solution has a name called the poll payment pattern. The idea applied here means that instead of sending the Ether refund in line, that is when a new pixel is purchased, we keep track of pending refunds internally. Then the account that receives the refund must submit a separate transaction to pull the funds out. Let's implement this pattern in our contract. We'll start by adding a pending refunds property, which is a mapping from an address to an amount. This is a lot like the list of accounts we had in our bank contract in the earlier video. It's a mapping from an address to a Uint, it's public, and we'll call it pending refunds. When we color a pixel, instead of sending the Ether directly, we'll credit the pixel owner's account with a refund. First we check that the pixel owner is not an empty address. And then we add on depending refunds, the old pixel owner's original price. Again, remember this doesn't transfer any Ether out of the contract. This is just internal bookkeeping. And also note again that any sort of math like this should be checking for overflows, but we'll talk about that later. And lastly, we'll write the withdraw refund function. And also, I want to mention that this function is based off of the Open Zeppelin Poll Payment Library. You can find the URL in the notes. So the function withdraw refunds, first, takes the pay as the message dot sender, and we look up the payment in pending refunds. We require that the payment not be zero, and we also require this dot balance be greater than the amount of the payment. This dot balance is the amount of Ether that belongs to this contract. Next, we zero out the pending refunds for this payee, then we send the payee the payment and require that it succeeded. If it fails, we revert all of our state changes so far. Let's try it out. First, we can deploy this contract. Now let's buy a pixel for one Ether. Now let's try buying that pixel for fewer Ether from the other account. Notice that it doesn't run, and it returns quickly too. And that's because a remix will try to run the transaction locally before sending a real transaction to the network. Now let's check the pending refund for our original account. Great, we can see our refund here. Now let's try to withdraw our refund. It worked. Notice the balance of Ether in our account went up. Let's check our refund balance now. It's zero, great.