Split fallback function and introduce "fallback()" and "receive()" syntax.

This commit is contained in:
Daniel Kirchner
2019-11-04 17:17:58 +01:00
parent 3d625cc239
commit 3321fc56ea
137 changed files with 1340 additions and 386 deletions
+94 -42
View File
@@ -222,23 +222,23 @@ This behaviour is also in line with the ``STATICCALL`` opcode.
not do state-changing operations, but it cannot check that the contract that will be called
at runtime is actually of that type.
.. index:: ! fallback function, function;fallback
.. index:: ! receive ether function, function;receive ! receive
.. _fallback-function:
.. _receive-ether-function:
Fallback Function
=================
Receive Ether Function
======================
A contract can have exactly one unnamed function. This function cannot have
arguments, cannot return anything and has to have ``external`` visibility.
It is executed on a call to the contract if none of the other
functions match the given function identifier (or if no data was supplied at
all).
Furthermore, this function is executed whenever the contract receives plain
Ether (without data). To receive Ether and add it to the total balance of the contract, the fallback function
must be marked ``payable``. If no such function exists, the contract cannot receive
Ether through regular transactions and throws an exception.
A contract can have at most one ``receive`` function, declared using ``receive() external payable``.
This function cannot have arguments, cannot return anything and must have
``external`` visibility and ``payable`` state mutability. It is executed on a
call to the contract with empty calldata. This is the function that is executed
on plain Ether transfers (e.g. via `.send()` or `.transfer()`). If no such
function exists, but a payable :ref:`fallback function <fallback-function>`
exists, the fallback function will be called on a plain Ether transfer. If
neither a receive Ether nor a payable fallback function is present, the
contract cannot receive Ether through regular transactions and throws an
exception.
In the worst case, the fallback function can only rely on 2300 gas being
available (for example when `send` or `transfer` is used), leaving little
@@ -250,33 +250,64 @@ will consume more gas than the 2300 gas stipend:
- Calling an external function which consumes a large amount of gas
- Sending Ether
.. warning::
Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``)
but do not define a receive Ether function or a payable fallback function
throw an exception, sending back the Ether (this was different
before Solidity v0.4.0). So if you want your contract to receive Ether,
you have to implement a receive Ether function (using payable fallback functions for receiving Ether is
not recommended, since it would not fail on interface confusions).
.. warning::
A contract without a receive Ether function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`)
or as a destination of a ``selfdestruct``.
A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it.
It also means that ``address(this).balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the receive Ether function).
Below you can see an example of a Sink contract that uses function ``receive``.
::
pragma solidity >=0.5.0 <0.7.0;
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
receive() external payable { }
}
.. index:: ! fallback function, function;fallback
.. _fallback-function:
Fallback Function
=================
A contract can have at most one ``fallback`` function, declared using ``fallback () external [payable]``.
This function cannot have arguments, cannot return anything and must have ``external`` visibility.
It is executed on a call to the contract if none of the other
functions match the given function signature, or if no data was supplied at
all and there is no :ref:`receive Ether function <receive-ether-function>`.
The fallback function always receives data, but in order to also receive Ether
it must be marked ``payable``.
In the worst case, if a payable fallback function is also used in place of a receive function, it can only rely on 2300 gas being
available (see :ref:`receive Ether function <receive-ether-function>` for a brief description of the implications of this).
Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it.
.. warning::
The fallback function is also executed if the caller meant to call
a function that is not available. If you want to implement the fallback
function only to receive ether, you should add a check
like ``require(msg.data.length == 0)`` to prevent invalid calls.
.. warning::
Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``)
but do not define a fallback function
throw an exception, sending back the Ether (this was different
before Solidity v0.4.0). So if you want your contract to receive Ether,
you have to implement a payable fallback function.
.. warning::
A contract without a payable fallback function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`)
or as a destination of a ``selfdestruct``.
A ``payable`` fallback function is also executed for plain Ether transfers, if no :ref:`receive Ether function <receive-ether-function>`
is present. It is recommended to always define a receive Ether function as well, if you define a payable fallback function
to distinguish Ether transfers from interface confusions.
.. note::
Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve
any payload supplied with the call.
A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it.
It also means that ``address(this).balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the fallback function).
::
pragma solidity >=0.5.0 <0.7.0;
@@ -287,15 +318,23 @@ Like any function, the fallback function can execute complex operations as long
// Sending Ether to this contract will cause an exception,
// because the fallback function does not have the `payable`
// modifier.
function() external { x = 1; }
fallback() external { x = 1; }
uint x;
}
contract TestPayable {
// This function is called for all messages sent to
// this contract, except plain Ether transfers
// (there is no other function except the receive function).
// Any call with non-empty calldata to this contract will execute
// the fallback function (even if Ether is sent along with the call).
fallback() external payable { x = 1; y = msg.value; }
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
function() external payable { }
// This function is called for plain Ether transfers, i.e.
// for every call with empty calldata.
receive() external payable { x = 2; y = msg.value; }
uint x;
uint y;
}
contract Caller {
@@ -305,14 +344,27 @@ Like any function, the fallback function can execute complex operations as long
// results in test.x becoming == 1.
// address(test) will not allow to call ``send`` directly, since ``test`` has no payable
// fallback function. It has to be converted to the ``address payable`` type via an
// intermediate conversion to ``uint160`` to even allow calling ``send`` on it.
address payable testPayable = address(uint160(address(test)));
// fallback function.
// It has to be converted to the ``address payable`` type to even allow calling ``send`` on it.
address payable testPayable = payable(address(test));
// If someone sends ether to that contract,
// If someone sends Ether to that contract,
// the transfer will fail, i.e. this returns false here.
return testPayable.send(2 ether);
}
function callTestPayable(TestPayable test) public returns (bool) {
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1 and test.y becoming 0.
(success,) = address(test).call.value(1)(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1 and test.y becoming 1.
// If someone sends Ether to that contract, the receive function in TestPayable will be called.
require(address(test).send(2 ether));
// results in test.x becoming == 2 and test.y becoming 2 ether.
}
}
.. index:: ! overload