Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Sending Ether Cheat Sheet

Sending Ether Cheat Sheet

TLDR

🥇 Instead of sending Ether, use the withdrawal pattern

🥈 If you really need to send Ether, use a safe wrapper like OpenZeppelin's Address.sendValue(addr, amount)

🥉 If you really need to send Ether without dependencies, use (bool success, ) = addr.call{value: amount}("")

Nifty Table

Reverts on failure Returns false on failure
Sends all gas .sendValue() .call()
Sends 2300 gas .transfer() .send()

More details

  • addr.transfer(amount) [docs]

    • not recommended anymore, because during hard forks the gas price of certain opcodes can change (meaning that it can break some transfers that used to work) [Consensys blog post]
  • addr.send(amount)

    • low-level counterpart to .transfer(), should be avoided for the same reason
  • addr.call{value: amount}("")

    • check with (bool success, ) = addr.call{value: amount}("")
  • OpenZeppelin's Address.sendValue(addr, amount) [code]

    • replacement for Solidity's transfer

⚠️ Always be mindful of these:

  • Ether transfer can always include code execution, so be careful about reentrancy
  • it is always possible to force Ether transfer into a contract (e.g. using selfdestruct(x))
  • when the recipient of a transfer (i.e. a call with empty calldata) is a contract, its receive() external payable function is called if it exists [receive-ether-function docs]
  • if it does not exist, its fallback function is called
  • in your receive and callback functions, make sure you use less than the 2300 gas stipend (but beware that the pricing of opcodes can change during hard forks)
  • the EVM considers that calls to non-existing contracts always succeed. Solidity normally includes an extcodesize(addr) != 0 check before calling other contracts, but this check is not included for low-level calls on addresses (.transfer(), .send() and .call())
  • From the sending-and-receiving-ether docs again:

Sending Ether can fail due to the call depth going above 1024. Since the caller is in total control of the call depth, they can force the transfer to fail

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment