Sending Ether: send vs. transfer vs. call

Published by Mario Oettler on

Smart contracts can send Ether to EOAs or other smart contracts. There are three commands that we can use for that sake – send, transfer, and call.

All three commands are members of the address payable type.

transfer()

The syntax of the transfer function looks like that:

receivingAddress.transfer(amount); 

The transfer function fails under two conditions:

  1. The balance of the sending smart contract is not large enough,
  2. The receiving contract rejects the payment.

In a failure case, the transfer function reverts.

If a payment is made, either the fallback() or receive() function in the receiving contract is triggered. This provides the opportunity for the receiving contract to react upon a payment.

Transfer forwards 2300 gas to the receiving contract. This is only a very small amount that is sufficient to trigger an event. It is definitely not enough to execute complex code.

send()

receivingAddress.send(10); 

Send is similar to transfer. But if the payment fails, it will not revert. Instead, it returns false. The failure handling is left to the calling contract.

If a payment is made, either the fallback() or receive() function in the receiving contract is executed. This provides the opportunity for the receiving contract to react upon a payment.

Send forwards 2300 gas to the receiving contract.

Problems with send() and transfer()

Both functions were considered the go-to solution for making payments to EOAs or other smart contracts. But since the amount of gas they forward to the called smart contract is very low, the called smart contract can easily run out of gas. This would make the payment impossible.

The problem here is that even if the receiving contract is designed carefully not to exceed the gas limit, future changes in the gas costs for some opcodes can break this design.

Therefore, the recommended way of sending payments is the function call().

call()

The function call() is designed for more individual interactions between smart contracts. It can call a function by name and send Ether to it. It is now the recommended way of sending Ether from a smart contract.

A simple call-statement looks like that:

(bool success, bytes memory data)= receivingAddress.call{value: 100}("");

Let’s have a look on the right side. The value specifies how many wei are transferred to the receiving address. In the round brackets, we can add additional data like a function signature of a called function and parameters.

If nothing is given there, the fallback() function or the receive() function is called.  

Issues with call()

With call(), the EVM transfers all gas to the receiving contract, if not stated otherwise. This allows the contract to execute complex operations at the expense of the function caller.

Another issue is that it allows for so-called re-entrancy attacks. This means that the receiver contract calls the function again where the call() statement is given. If the sender contract is improperly coded, it can result in draining larger amounts of funds from it than planned. This issue requires more awareness by the contract authors.

Categories: