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
+26 -44
View File
@@ -107,6 +107,9 @@ bool ContractLevelChecker::LessFunction::operator()(FunctionDefinition const* _a
if (_a->name() != _b->name())
return _a->name() < _b->name();
if (_a->kind() != _b->kind())
return _a->kind() < _b->kind();
return boost::lexicographical_compare(
FunctionType(*_a).asCallableFunction(false)->parameterTypes(),
FunctionType(*_b).asCallableFunction(false)->parameterTypes(),
@@ -133,12 +136,11 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract)
checkAmbiguousOverrides(_contract);
checkAbstractFunctions(_contract);
checkBaseConstructorArguments(_contract);
checkConstructor(_contract);
checkFallbackFunction(_contract);
checkExternalTypeClashes(_contract);
checkHashCollisions(_contract);
checkLibraryRequirements(_contract);
checkBaseABICompatibility(_contract);
checkPayableFallbackWithoutReceive(_contract);
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
@@ -150,6 +152,7 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
map<string, vector<FunctionDefinition const*>> functions;
FunctionDefinition const* constructor = nullptr;
FunctionDefinition const* fallback = nullptr;
FunctionDefinition const* receive = nullptr;
for (FunctionDefinition const* function: _contract.definedFunctions())
if (function->isConstructor())
{
@@ -171,6 +174,16 @@ void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _co
);
fallback = function;
}
else if (function->isReceive())
{
if (receive)
m_errorReporter.declarationError(
function->location(),
SecondarySourceLocation().append("Another declaration is here:", receive->location()),
"Only one receive function is allowed."
);
receive = function;
}
else
{
solAssert(!function->name().empty(), "");
@@ -496,48 +509,6 @@ void ContractLevelChecker::annotateBaseConstructorArguments(
}
void ContractLevelChecker::checkConstructor(ContractDefinition const& _contract)
{
FunctionDefinition const* constructor = _contract.constructor();
if (!constructor)
return;
if (!constructor->returnParameters().empty())
m_errorReporter.typeError(constructor->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (constructor->stateMutability() != StateMutability::NonPayable && constructor->stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
constructor->location(),
"Constructor must be payable or non-payable, but is \"" +
stateMutabilityToString(constructor->stateMutability()) +
"\"."
);
if (constructor->visibility() != FunctionDefinition::Visibility::Public && constructor->visibility() != FunctionDefinition::Visibility::Internal)
m_errorReporter.typeError(constructor->location(), "Constructor must be public or internal.");
}
void ContractLevelChecker::checkFallbackFunction(ContractDefinition const& _contract)
{
FunctionDefinition const* fallback = _contract.fallbackFunction();
if (!fallback)
return;
if (_contract.isLibrary())
m_errorReporter.typeError(fallback->location(), "Libraries cannot have fallback functions.");
if (fallback->stateMutability() != StateMutability::NonPayable && fallback->stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
fallback->location(),
"Fallback function must be payable or non-payable, but is \"" +
stateMutabilityToString(fallback->stateMutability()) +
"\"."
);
if (!fallback->parameters().empty())
m_errorReporter.typeError(fallback->parameterList().location(), "Fallback function cannot take parameters.");
if (!fallback->returnParameters().empty())
m_errorReporter.typeError(fallback->returnParameterList()->location(), "Fallback function cannot return values.");
if (fallback->visibility() != FunctionDefinition::Visibility::External)
m_errorReporter.typeError(fallback->location(), "Fallback function must be defined as \"external\".");
}
void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _contract)
{
map<string, vector<pair<Declaration const*, FunctionTypePointer>>> externalDeclarations;
@@ -876,3 +847,14 @@ ContractLevelChecker::ModifierMultiSet const& ContractLevelChecker::inheritedMod
return m_contractBaseModifiers[_contract] = set;
}
void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition const& _contract)
{
if (auto const* fallback = _contract.fallbackFunction())
if (fallback->isPayable() && !_contract.interfaceFunctionList().empty() && !_contract.receiveFunction())
m_errorReporter.warning(
_contract.location(),
"This contract has a payable fallback function, but no receive ether function. Consider adding a receive ether function.",
SecondarySourceLocation{}.append("The payable fallback function is defined here.", fallback->location())
);
}
+3 -2
View File
@@ -83,8 +83,6 @@ private:
FunctionDefinition const* _baseConstructor,
ASTNode const* _argumentNode
);
void checkConstructor(ContractDefinition const& _contract);
void checkFallbackFunction(ContractDefinition const& _contract);
/// Checks that different functions with external visibility end up having different
/// external argument types (i.e. different signature).
void checkExternalTypeClashes(ContractDefinition const& _contract);
@@ -108,6 +106,9 @@ private:
FunctionMultiSet const& inheritedFunctions(ContractDefinition const* _contract) const;
ModifierMultiSet const& inheritedModifiers(ContractDefinition const* _contract) const;
/// Warns if the contract has a payable fallback, but no receive ether function.
void checkPayableFallbackWithoutReceive(ContractDefinition const& _contract);
langutil::ErrorReporter& m_errorReporter;
/// Cache for inheritedFunctions().
+1 -1
View File
@@ -295,7 +295,7 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function)
{
if (_function.noVisibilitySpecified())
{
string suggestedVisibility = _function.isFallback() || m_isInterface ? "external" : "public";
string suggestedVisibility = _function.isFallback() || _function.isReceive() || m_isInterface ? "external" : "public";
m_errorReporter.syntaxError(
_function.location(),
"No visibility specified. Did you intend to add \"" + suggestedVisibility + "\"?"
+76 -1
View File
@@ -326,11 +326,12 @@ bool TypeChecker::visit(StructDefinition const& _struct)
bool TypeChecker::visit(FunctionDefinition const& _function)
{
bool isLibraryFunction = _function.inContractKind() == ContractDefinition::ContractKind::Library;
if (_function.isPayable())
{
if (isLibraryFunction)
m_errorReporter.typeError(_function.location(), "Library functions cannot be payable.");
if (!_function.isConstructor() && !_function.isFallback() && !_function.isPartOfExternalInterface())
if (_function.isOrdinary() && !_function.isPartOfExternalInterface())
m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable.");
}
auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) {
@@ -424,6 +425,15 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
m_errorReporter.typeError(_function.location(), "Constructor must be implemented if declared.");
else if (isLibraryFunction && _function.visibility() <= FunctionDefinition::Visibility::Internal)
m_errorReporter.typeError(_function.location(), "Internal library function must be implemented if declared.");
if (_function.isFallback())
typeCheckFallbackFunction(_function);
else if (_function.isReceive())
typeCheckReceiveFunction(_function);
else if (_function.isConstructor())
typeCheckConstructor(_function);
return false;
}
@@ -1703,6 +1713,71 @@ void TypeChecker::typeCheckFunctionCall(
typeCheckFunctionGeneralChecks(_functionCall, _functionType);
}
void TypeChecker::typeCheckFallbackFunction(FunctionDefinition const& _function)
{
solAssert(_function.isFallback(), "");
if (_function.inContractKind() == ContractDefinition::ContractKind::Library)
m_errorReporter.typeError(_function.location(), "Libraries cannot have fallback functions.");
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
_function.location(),
"Fallback function must be payable or non-payable, but is \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
if (_function.visibility() != FunctionDefinition::Visibility::External)
m_errorReporter.typeError(_function.location(), "Fallback function must be defined as \"external\".");
if (!_function.returnParameters().empty())
{
if (_function.returnParameters().size() > 1 || *type(*_function.returnParameters().front()) != *TypeProvider::bytesMemory())
m_errorReporter.typeError(_function.returnParameterList()->location(), "Fallback function can only have a single \"bytes memory\" return value.");
else
m_errorReporter.typeError(_function.returnParameterList()->location(), "Return values for fallback functions are not yet implemented.");
}
if (!_function.parameters().empty())
m_errorReporter.typeError(_function.parameterList().location(), "Fallback function cannot take parameters.");
}
void TypeChecker::typeCheckReceiveFunction(FunctionDefinition const& _function)
{
solAssert(_function.isReceive(), "");
if (_function.inContractKind() == ContractDefinition::ContractKind::Library)
m_errorReporter.typeError(_function.location(), "Libraries cannot have receive ether functions.");
if (_function.stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
_function.location(),
"Receive ether function must be payable, but is \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
if (_function.visibility() != FunctionDefinition::Visibility::External)
m_errorReporter.typeError(_function.location(), "Receive ether function must be defined as \"external\".");
if (!_function.returnParameters().empty())
m_errorReporter.typeError(_function.returnParameterList()->location(), "Receive ether function cannot return values.");
if (!_function.parameters().empty())
m_errorReporter.typeError(_function.parameterList().location(), "Receive ether function cannot take parameters.");
}
void TypeChecker::typeCheckConstructor(FunctionDefinition const& _function)
{
solAssert(_function.isConstructor(), "");
if (!_function.returnParameters().empty())
m_errorReporter.typeError(_function.returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (_function.stateMutability() != StateMutability::NonPayable && _function.stateMutability() != StateMutability::Payable)
m_errorReporter.typeError(
_function.location(),
"Constructor must be payable or non-payable, but is \"" +
stateMutabilityToString(_function.stateMutability()) +
"\"."
);
if (_function.visibility() != FunctionDefinition::Visibility::Public && _function.visibility() != FunctionDefinition::Visibility::Internal)
m_errorReporter.typeError(_function.location(), "Constructor must be public or internal.");
}
void TypeChecker::typeCheckABIEncodeFunctions(
FunctionCall const& _functionCall,
FunctionTypePointer _functionType
+4
View File
@@ -96,6 +96,10 @@ private:
FunctionTypePointer _functionType
);
void typeCheckFallbackFunction(FunctionDefinition const& _function);
void typeCheckReceiveFunction(FunctionDefinition const& _function);
void typeCheckConstructor(FunctionDefinition const& _function);
/// Performs general number and type checks of arguments against function call and struct ctor FunctionCall node parameters.
void typeCheckFunctionGeneralChecks(
FunctionCall const& _functionCall,
+1
View File
@@ -174,6 +174,7 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef)
!_funDef.body().statements().empty() &&
!_funDef.isConstructor() &&
!_funDef.isFallback() &&
!_funDef.isReceive() &&
!_funDef.annotation().superFunction
)
m_errorReporter.warning(