There are some non-obvious concepts here and some heavily loaded terms all of which can lead to confusion about the safe way to proceed.
Either the new bidder or the outbid bidder could be hostile contracts. There are various things they could do to interfere with the correct/expected execution of this function.
As a general heuristic, we can think of them as "untrusted contracts".
There are a number of defensive habits we can employ to defeat mischief by untrusted contracts. Among these are:
- Avoid dealing with more than one untrusted contract at a time.
- Avoid mixing up multiple concerns in a single function/transaction.
- The
withdraw()
function offers a good example of a safe send
pattern that's also important.*
Immediate refund would violate #1 & #2 by 1) interacting with both the untrusted sender and the untrusted receiver in a single transaction and 2) mixing up the concern of processing the new bid with the concern of refunding the previous highest bid.
As alluded to in the remarks, there is a call stack attack that can cause a .send()
to silently fail. In the context of this contract, if bidding and refunds were intermingled that would mean the bidder could possibly outbid the high bid and prevent the refund. That would be bad.
Handled as separate concerns, it's harder to imagine how the bidder can interfere with refunds, or how the refund can interfere with the bid.
The withdrawal pattern does indeed insert an extra step. It's not necessarily a human-facing step because much can be done in the front-end to call the right function(s) when needed. In any case, security must take priority over convenience.
Hope it helps.
// Withdraw a bid that was overbid.
function withdraw() {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `send` returns (see the remark above about
// conditions -> effects -> interaction).
pendingReturns[msg.sender] = 0;
msg.sender.transfer(amount);
}
}