Merge pull request #10214 from ethereum/fallbackReturn

Allow fallback function to return data.
This commit is contained in:
chriseth 2020-11-23 19:49:59 +01:00 committed by GitHub
commit ee657f5361
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 309 additions and 45 deletions

View File

@ -1,5 +1,8 @@
### 0.7.6 (unreleased)
Language Features:
* The fallback function can now also have a single ``calldata`` argument (equaling ``msg.data``) and return ``bytes memory`` (which will not be ABI-encoded but returned as-is).
Compiler Features:
* SMTChecker: Support named arguments in function calls.
* SMTChecker: Support struct constructor.

View File

@ -275,7 +275,10 @@ A contract can have at most one ``receive`` function, declared using
``receive() external payable { ... }``
(without the ``function`` keyword).
This function cannot have arguments, cannot return anything and must have
``external`` visibility and ``payable`` state mutability. It is executed on a
``external`` visibility and ``payable`` state mutability.
It can be virtual, can override and can have modifiers.
The receive function 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>`
@ -339,15 +342,22 @@ Below you can see an example of a Sink contract that uses function ``receive``.
Fallback Function
=================
A contract can have at most one ``fallback`` function, declared using ``fallback () external [payable]``
(without the ``function`` keyword).
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
A contract can have at most one ``fallback`` function, declared using either ``fallback () external [payable]``
or ``fallback (bytes calldata _input) external [payable] returns (bytes memory _output)``
(both without the ``function`` keyword).
This function must have ``external`` visibility. A fallback function can be virtual, can override
and can have modifiers.
The fallback function 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``.
If the version with parameters is used, ``_input`` will contain the full data sent to the contract
(equal to ``msg.data``) and can return data in ``_output``. The returned data will not be
ABI-encoded. Instead it will be returned without modifications (not even padding).
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>`
@ -364,12 +374,11 @@ operations as long as there is enough gas passed on to it.
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.
After having checked the first four bytes of ``msg.data``,
If you want to decode the input data, you can check the first four bytes
for the function selector and then
you can use ``abi.decode`` together with the array slice syntax to
decode ABI-encoded data:
``(c, d) = abi.decode(msg.data[4:], (uint256, uint256));``
``(c, d) = abi.decode(_input[4:], (uint256, uint256));``
Note that this should only be used as a last resort and
proper functions should be used instead.

View File

@ -84,7 +84,8 @@ contractBodyElement:
constructorDefinition
| functionDefinition
| modifierDefinition
| fallbackReceiveFunctionDefinition
| fallbackFunctionDefinition
| receiveFunctionDefinition
| structDefinition
| enumDefinition
| stateVariableDeclaration
@ -189,9 +190,32 @@ locals[
(Semicolon | body=block);
/**
* Definitions of the special fallback and receive functions.
* Definition of the special fallback function.
*/
fallbackReceiveFunctionDefinition
fallbackFunctionDefinition
locals[
boolean visibilitySet = false,
boolean mutabilitySet = false,
boolean virtualSet = false,
boolean overrideSpecifierSet = false,
boolean hasParameters = false
]
:
kind=Fallback LParen (parameterList { $hasParameters = true; } )? RParen
(
{!$visibilitySet}? External {$visibilitySet = true;}
| {!$mutabilitySet}? stateMutability {$mutabilitySet = true;}
| modifierInvocation
| {!$virtualSet}? Virtual {$virtualSet = true;}
| {!$overrideSpecifierSet}? overrideSpecifier {$overrideSpecifierSet = true;}
)*
( {$hasParameters}? Returns LParen returnParameters=parameterList RParen | {!$hasParameters}? )
(Semicolon | body=block);
/**
* Definition of the special receive function.
*/
receiveFunctionDefinition
locals[
boolean visibilitySet = false,
boolean mutabilitySet = false,
@ -199,10 +223,10 @@ locals[
boolean overrideSpecifierSet = false
]
:
kind=(Fallback | Receive) LParen RParen
kind=Receive LParen RParen
(
{!$visibilitySet}? visibility {$visibilitySet = true;}
| {!$mutabilitySet}? stateMutability {$mutabilitySet = true;}
{!$visibilitySet}? External {$visibilitySet = true;}
| {!$mutabilitySet}? Payable {$mutabilitySet = true;}
| modifierInvocation
| {!$virtualSet}? Virtual {$virtualSet = true;}
| {!$overrideSpecifierSet}? overrideSpecifier {$overrideSpecifierSet = true;}

View File

@ -394,6 +394,10 @@ bool OverrideProxy::OverrideComparator::operator<(OverrideComparator const& _oth
if (functionKind != _other.functionKind)
return *functionKind < *_other.functionKind;
// Parameters do not matter for non-regular functions.
if (functionKind != Token::Function)
return false;
if (!parameterTypes || !_other.parameterTypes)
return false;
@ -574,16 +578,19 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
FunctionType const* functionType = _overriding.functionType();
FunctionType const* superType = _super.functionType();
solAssert(functionType->hasEqualParameterTypes(*superType), "Override doesn't have equal parameters!");
if (_overriding.functionKind() != Token::Fallback)
{
solAssert(functionType->hasEqualParameterTypes(*superType), "Override doesn't have equal parameters!");
if (!functionType->hasEqualReturnTypes(*superType))
overrideError(
_overriding,
_super,
4822_error,
"Overriding " + _overriding.astNodeName() + " return types differ.",
"Overridden " + _overriding.astNodeName() + " is here:"
);
if (!functionType->hasEqualReturnTypes(*superType))
overrideError(
_overriding,
_super,
4822_error,
"Overriding " + _overriding.astNodeName() + " return types differ.",
"Overridden " + _overriding.astNodeName() + " is here:"
);
}
// Stricter mutability is always okay except when super is Payable
if (

View File

@ -1831,15 +1831,21 @@ void TypeChecker::typeCheckFallbackFunction(FunctionDefinition const& _function)
);
if (_function.visibility() != Visibility::External)
m_errorReporter.typeError(1159_error, _function.location(), "Fallback function must be defined as \"external\".");
if (!_function.returnParameters().empty())
if (!_function.returnParameters().empty() || !_function.parameters().empty())
{
if (_function.returnParameters().size() > 1 || *type(*_function.returnParameters().front()) != *TypeProvider::bytesMemory())
m_errorReporter.typeError(5570_error, _function.returnParameterList()->location(), "Fallback function can only have a single \"bytes memory\" return value.");
else
m_errorReporter.typeError(6151_error, _function.returnParameterList()->location(), "Return values for fallback functions are not yet implemented.");
if (
_function.returnParameters().size() != 1 ||
*type(*_function.returnParameters().front()) != *TypeProvider::bytesMemory() ||
_function.parameters().size() != 1 ||
*type(*_function.parameters().front()) != *TypeProvider::bytesCalldata()
)
m_errorReporter.typeError(
5570_error,
_function.returnParameterList()->location(),
"Fallback function either has to have the signature \"fallback()\" or \"fallback(bytes calldata) returns (bytes memory)\"."
);
}
if (!_function.parameters().empty())
m_errorReporter.typeError(3978_error, _function.parameterList().location(), "Fallback function cannot take parameters.");
}
void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function)

View File

@ -467,10 +467,21 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
appendCallValueCheck();
solAssert(fallback->isFallback(), "");
solAssert(FunctionType(*fallback).parameterTypes().empty(), "");
solAssert(FunctionType(*fallback).returnParameterTypes().empty(), "");
m_context.setStackOffset(0);
if (!FunctionType(*fallback).parameterTypes().empty())
m_context << u256(0) << Instruction::CALLDATASIZE;
fallback->accept(*this);
m_context << Instruction::STOP;
if (FunctionType(*fallback).returnParameterTypes().empty())
m_context << Instruction::STOP;
else
{
m_context << Instruction::DUP1 << Instruction::MLOAD << Instruction::SWAP1;
m_context << u256(0x20) << Instruction::ADD;
m_context << Instruction::RETURN;
}
}
else
m_context.appendRevert("Unknown signature and no fallback defined");
@ -479,6 +490,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
for (auto const& it: interfaceFunctions)
{
m_context.setStackOffset(1);
FunctionTypePointer const& functionType = it.second;
solAssert(functionType->hasDeclaration(), "");
CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration());
@ -588,7 +600,9 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
// reserve additional slots: [retarg0] ... [retargm]
unsigned parametersSize = CompilerUtils::sizeOnStack(_function.parameters());
if (!_function.isConstructor())
if (_function.isFallback())
m_context.adjustStackOffset(static_cast<int>(parametersSize));
else if (!_function.isConstructor())
// adding 1 for return address.
m_context.adjustStackOffset(static_cast<int>(parametersSize) + 1);
for (ASTPointer<VariableDeclaration> const& variable: _function.parameters())
@ -628,7 +642,8 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
unsigned const c_returnValuesSize = CompilerUtils::sizeOnStack(_function.returnParameters());
vector<int> stackLayout;
stackLayout.push_back(static_cast<int>(c_returnValuesSize)); // target of return address
if (!_function.isConstructor() && !_function.isFallback())
stackLayout.push_back(static_cast<int>(c_returnValuesSize)); // target of return address
stackLayout += vector<int>(c_argumentsSize, -1); // discard all arguments
for (size_t i = 0; i < c_returnValuesSize; ++i)
stackLayout.push_back(static_cast<int>(i));
@ -639,7 +654,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
errinfo_sourceLocation(_function.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
while (stackLayout.back() != static_cast<int>(stackLayout.size() - 1))
while (!stackLayout.empty() && stackLayout.back() != static_cast<int>(stackLayout.size() - 1))
if (stackLayout.back() < 0)
{
m_context << Instruction::POP;

View File

@ -652,8 +652,16 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
{
string fallbackCode;
if (!fallback->isPayable())
fallbackCode += callValueCheck();
fallbackCode += m_context.enqueueFunctionForCodeGeneration(*fallback) + "() stop()";
fallbackCode += callValueCheck() + "\n";
if (fallback->parameters().empty())
fallbackCode += m_context.enqueueFunctionForCodeGeneration(*fallback) + "() stop()";
else
{
solAssert(fallback->parameters().size() == 1 && fallback->returnParameters().size() == 1, "");
fallbackCode += "let retval := " + m_context.enqueueFunctionForCodeGeneration(*fallback) + "(0, calldatasize())\n";
fallbackCode += "return(add(retval, 0x20), mload(retval))\n";
}
t("fallback", fallbackCode);
}

View File

@ -0,0 +1,18 @@
contract A {
uint public x;
fallback () external {
if (x == 2) return;
x++;
}
}
// ====
// compileViaYul: also
// ----
// ()
// x() -> 1
// ()
// x() -> 2
// ()
// x() -> 2
// ()
// x() -> 2

View File

@ -0,0 +1,17 @@
contract A {
uint public x;
fallback (bytes calldata _input) external returns (bytes memory) {
x = _input.length;
return "";
}
function f() public returns (bool, bytes memory) {
(bool success, bytes memory retval) = address(this).call("abc");
return (success, retval);
}
}
// ====
// compileViaYul: also
// EVMVersion: >=byzantium
// ----
// f() -> 0x01, 0x40, 0x00
// x() -> 3

View File

@ -0,0 +1,16 @@
contract A {
bytes public x;
fallback (bytes calldata _input) external returns (bytes memory) {
x = _input;
return "";
}
function f() public returns (bool, bytes memory) {
(bool success, bytes memory retval) = address(this).call("abc");
return (success, retval);
}
}
// ====
// EVMVersion: >=byzantium
// ----
// f() -> 0x01, 0x40, 0x00
// x() -> 0x20, 3, "abc"

View File

@ -0,0 +1,19 @@
contract A {
fallback (bytes calldata _input) virtual external returns (bytes memory) {
return _input;
}
}
contract B is A {
fallback (bytes calldata _input) override external returns (bytes memory) {
return "xyz";
}
function f() public returns (bool, bytes memory) {
(bool success, bytes memory retval) = address(this).call("abc");
return (success, retval);
}
}
// ====
// EVMVersion: >=byzantium
// compileViaYul: also
// ----
// f() -> 0x01, 0x40, 0x03, 0x78797a0000000000000000000000000000000000000000000000000000000000

View File

@ -0,0 +1,18 @@
contract A {
fallback (bytes calldata _input) virtual external returns (bytes memory) {
return _input;
}
}
contract B is A {
fallback () override external {
}
function f() public returns (bool, bytes memory) {
(bool success, bytes memory retval) = address(this).call("abc");
return (success, retval);
}
}
// ====
// EVMVersion: >=byzantium
// compileViaYul: also
// ----
// f() -> 1, 0x40, 0x00

View File

@ -0,0 +1,22 @@
contract A {
fallback (bytes calldata _input) virtual external returns (bytes memory) {
return _input;
}
}
contract B {
fallback (bytes calldata _input) virtual external returns (bytes memory) {
return "xyz";
}
}
contract C is B, A {
fallback () external override (B, A) {}
function f() public returns (bool, bytes memory) {
(bool success, bytes memory retval) = address(this).call("abc");
return (success, retval);
}
}
// ====
// EVMVersion: >=byzantium
// compileViaYul: also
// ----
// f() -> 0x01, 0x40, 0x00

View File

@ -0,0 +1,14 @@
contract A {
fallback (bytes calldata _input) external returns (bytes memory) {
return _input;
}
function f() public returns (bool, bytes memory) {
(bool success, bytes memory retval) = address(this).call("abc");
return (success, retval);
}
}
// ====
// compileViaYul: also
// EVMVersion: >=byzantium
// ----
// f() -> 0x01, 0x40, 0x03, 0x6162630000000000000000000000000000000000000000000000000000000000

View File

@ -2,4 +2,4 @@ contract C {
fallback(uint256) external {}
}
// ----
// TypeError 3978: (25-34): Fallback function cannot take parameters.
// TypeError 5570: (44-44): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".

View File

@ -0,0 +1,6 @@
contract C {
fallback(bytes calldata _input) external returns (bytes memory _output) {}
fallback() external {}
}
// ----
// DeclarationError 7301: (96-118): Only one fallback function is allowed.

View File

@ -0,0 +1,9 @@
contract C {
fallback(bytes calldata _input) external returns (bytes memory _output) {}
}
contract D is C {
fallback() external {}
}
// ----
// TypeError 9456: (116-138): Overriding function is missing "override" specifier.
// TypeError 4334: (17-91): Trying to override non-virtual function. Did you forget to add "virtual"?

View File

@ -0,0 +1,7 @@
contract C {
fallback(bytes calldata _input) external virtual returns (bytes memory _output) {}
}
contract D is C {
fallback() external override {}
}
// ----

View File

@ -0,0 +1,13 @@
contract D {
fallback(bytes memory) external returns (bytes memory) {}
}
contract E {
fallback(bytes memory) external returns (bytes calldata) {}
}
contract F {
fallback(bytes calldata) external returns (bytes calldata) {}
}
// ----
// TypeError 5570: (57-71): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".
// TypeError 5570: (134-150): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".
// TypeError 5570: (215-231): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".

View File

@ -0,0 +1,20 @@
contract A {
fallback (bytes calldata _input) external returns (bytes memory) {
return _input;
}
}
contract B {
fallback (bytes calldata _input) external returns (bytes memory) {
return "xyz";
}
}
contract C is B, A {
function f() public returns (bool, bytes memory) {
(bool success, bytes memory retval) = address(this).call("abc");
return (success, retval);
}
}
// ====
// EVMVersion: >=byzantium
// ----
// TypeError 6480: (229-420): Derived contract must override function "". Two or more base classes define function with same name and parameter types.

View File

@ -0,0 +1,9 @@
contract C {
fallback() external returns (bytes memory _output) {}
}
contract D {
fallback(bytes calldata _input) external {}
}
// ----
// TypeError 5570: (45-67): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".
// TypeError 5570: (131-131): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".

View File

@ -2,4 +2,4 @@ contract C {
fallback() external returns (bytes memory, bytes memory) {}
}
// ----
// TypeError 5570: (45-73): Fallback function can only have a single "bytes memory" return value.
// TypeError 5570: (45-73): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".

View File

@ -2,4 +2,4 @@ contract C {
fallback() external returns (uint256) {}
}
// ----
// TypeError 5570: (45-54): Fallback function can only have a single "bytes memory" return value.
// TypeError 5570: (45-54): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".

View File

@ -2,4 +2,4 @@ contract C {
fallback() external returns (bytes memory) {}
}
// ----
// TypeError 6151: (45-59): Return values for fallback functions are not yet implemented.
// TypeError 5570: (45-59): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".

View File

@ -0,0 +1,4 @@
contract C {
fallback(bytes calldata _input) external returns (bytes memory _output) {}
}
// ----

View File

@ -3,4 +3,4 @@ contract C {
fallback(uint a) external { x = 2; }
}
// ----
// TypeError 3978: (37-45): Fallback function cannot take parameters.
// TypeError 5570: (55-55): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".

View File

@ -2,4 +2,4 @@ contract C {
fallback() external returns (uint) { }
}
// ----
// TypeError 5570: (45-51): Fallback function can only have a single "bytes memory" return value.
// TypeError 5570: (45-51): Fallback function either has to have the signature "fallback()" or "fallback(bytes calldata) returns (bytes memory)".