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) ### 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: Compiler Features:
* SMTChecker: Support named arguments in function calls. * SMTChecker: Support named arguments in function calls.
* SMTChecker: Support struct constructor. * SMTChecker: Support struct constructor.

View File

@ -275,7 +275,10 @@ A contract can have at most one ``receive`` function, declared using
``receive() external payable { ... }`` ``receive() external payable { ... }``
(without the ``function`` keyword). (without the ``function`` keyword).
This function cannot have arguments, cannot return anything and must have 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 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 on plain Ether transfers (e.g. via ``.send()`` or ``.transfer()``). If no such
function exists, but a payable :ref:`fallback function <fallback-function>` 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 Fallback Function
================= =================
A contract can have at most one ``fallback`` function, declared using ``fallback () external [payable]`` A contract can have at most one ``fallback`` function, declared using either ``fallback () external [payable]``
(without the ``function`` keyword). or ``fallback (bytes calldata _input) external [payable] returns (bytes memory _output)``
This function cannot have arguments, cannot return anything and must have ``external`` visibility. (both without the ``function`` keyword).
It is executed on a call to the contract if none of the other 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 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>`. 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 The fallback function always receives data, but in order to also receive Ether
it must be marked ``payable``. 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 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 place of a receive function, it can only rely on 2300 gas being
available (see :ref:`receive Ether function <receive-ether-function>` 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. to distinguish Ether transfers from interface confusions.
.. note:: .. note::
Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve If you want to decode the input data, you can check the first four bytes
any payload supplied with the call. for the function selector and then
After having checked the first four bytes of ``msg.data``,
you can use ``abi.decode`` together with the array slice syntax to you can use ``abi.decode`` together with the array slice syntax to
decode ABI-encoded data: 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 Note that this should only be used as a last resort and
proper functions should be used instead. proper functions should be used instead.

View File

@ -84,7 +84,8 @@ contractBodyElement:
constructorDefinition constructorDefinition
| functionDefinition | functionDefinition
| modifierDefinition | modifierDefinition
| fallbackReceiveFunctionDefinition | fallbackFunctionDefinition
| receiveFunctionDefinition
| structDefinition | structDefinition
| enumDefinition | enumDefinition
| stateVariableDeclaration | stateVariableDeclaration
@ -189,9 +190,32 @@ locals[
(Semicolon | body=block); (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[ locals[
boolean visibilitySet = false, boolean visibilitySet = false,
boolean mutabilitySet = false, boolean mutabilitySet = false,
@ -199,10 +223,10 @@ locals[
boolean overrideSpecifierSet = false boolean overrideSpecifierSet = false
] ]
: :
kind=(Fallback | Receive) LParen RParen kind=Receive LParen RParen
( (
{!$visibilitySet}? visibility {$visibilitySet = true;} {!$visibilitySet}? External {$visibilitySet = true;}
| {!$mutabilitySet}? stateMutability {$mutabilitySet = true;} | {!$mutabilitySet}? Payable {$mutabilitySet = true;}
| modifierInvocation | modifierInvocation
| {!$virtualSet}? Virtual {$virtualSet = true;} | {!$virtualSet}? Virtual {$virtualSet = true;}
| {!$overrideSpecifierSet}? overrideSpecifier {$overrideSpecifierSet = true;} | {!$overrideSpecifierSet}? overrideSpecifier {$overrideSpecifierSet = true;}

View File

@ -394,6 +394,10 @@ bool OverrideProxy::OverrideComparator::operator<(OverrideComparator const& _oth
if (functionKind != _other.functionKind) if (functionKind != _other.functionKind)
return *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) if (!parameterTypes || !_other.parameterTypes)
return false; return false;
@ -574,16 +578,19 @@ void OverrideChecker::checkOverride(OverrideProxy const& _overriding, OverridePr
FunctionType const* functionType = _overriding.functionType(); FunctionType const* functionType = _overriding.functionType();
FunctionType const* superType = _super.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)) if (!functionType->hasEqualReturnTypes(*superType))
overrideError( overrideError(
_overriding, _overriding,
_super, _super,
4822_error, 4822_error,
"Overriding " + _overriding.astNodeName() + " return types differ.", "Overriding " + _overriding.astNodeName() + " return types differ.",
"Overridden " + _overriding.astNodeName() + " is here:" "Overridden " + _overriding.astNodeName() + " is here:"
); );
}
// Stricter mutability is always okay except when super is Payable // Stricter mutability is always okay except when super is Payable
if ( if (

View File

@ -1831,15 +1831,21 @@ void TypeChecker::typeCheckFallbackFunction(FunctionDefinition const& _function)
); );
if (_function.visibility() != Visibility::External) if (_function.visibility() != Visibility::External)
m_errorReporter.typeError(1159_error, _function.location(), "Fallback function must be defined as \"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()) if (
m_errorReporter.typeError(5570_error, _function.returnParameterList()->location(), "Fallback function can only have a single \"bytes memory\" return value."); _function.returnParameters().size() != 1 ||
else *type(*_function.returnParameters().front()) != *TypeProvider::bytesMemory() ||
m_errorReporter.typeError(6151_error, _function.returnParameterList()->location(), "Return values for fallback functions are not yet implemented."); _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) void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function)

View File

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

View File

@ -652,8 +652,16 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
{ {
string fallbackCode; string fallbackCode;
if (!fallback->isPayable()) if (!fallback->isPayable())
fallbackCode += callValueCheck(); fallbackCode += callValueCheck() + "\n";
fallbackCode += m_context.enqueueFunctionForCodeGeneration(*fallback) + "() stop()"; 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); 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 {} 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) {} 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) {} 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) {} 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; } 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) { } 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)".