aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Changelog.md1
-rw-r--r--libsolidity/analysis/TypeChecker.cpp19
-rw-r--r--libsolidity/ast/Types.cpp6
-rw-r--r--libsolidity/ast/Types.h4
-rw-r--r--test/libsolidity/ErrorCheck.cpp2
-rw-r--r--test/libsolidity/SolidityNameAndTypeResolution.cpp70
6 files changed, 101 insertions, 1 deletions
diff --git a/Changelog.md b/Changelog.md
index 075523d7..f84112e8 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -2,6 +2,7 @@
Features:
* Inline Assembly: Show useful error message if trying to access calldata variables.
+ * Type Checker: Disallow value transfers to contracts without a payable fallback function
Bugfixes:
* Type Checker: Fix invalid "specify storage keyword" warning for reference members of structs.
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index 90043b43..4e5a11ed 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -1631,6 +1631,25 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
annotation.isLValue = annotation.referencedDeclaration->isLValue();
}
+ if (exprType->category() == Type::Category::Contract)
+ {
+ if (auto callType = dynamic_cast<FunctionType const*>(type(_memberAccess).get()))
+ {
+ auto kind = callType->kind();
+ auto contractType = dynamic_cast<ContractType const*>(exprType.get());
+ solAssert(!!contractType, "Should be contract type.");
+
+ if (
+ (kind == FunctionType::Kind::Send || kind == FunctionType::Kind::Transfer) &&
+ !contractType->isPayable()
+ )
+ m_errorReporter.typeError(
+ _memberAccess.location(),
+ "Value transfer to a contract without a payable fallback function."
+ );
+ }
+ }
+
// TODO some members might be pure, but for example `address(0x123).balance` is not pure
// although every subexpression is, so leaving this limited for now.
if (auto tt = dynamic_cast<TypeType const*>(exprType.get()))
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index 6ecf509d..b54407c6 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -1230,6 +1230,12 @@ bool ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const
_convertTo.category() == Category::Contract;
}
+bool ContractType::isPayable() const
+{
+ auto fallbackFunction = m_contract.fallbackFunction();
+ return fallbackFunction && fallbackFunction->isPayable();
+}
+
TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const
{
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index 66eb039f..bd5da30a 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -675,6 +675,10 @@ public:
}
bool isSuper() const { return m_super; }
+
+ // @returns true if and only if the contract has a payable fallback function
+ bool isPayable() const;
+
ContractDefinition const& contractDefinition() const { return m_contract; }
/// Returns the function type of the constructor modified to return an object of the contract's type.
diff --git a/test/libsolidity/ErrorCheck.cpp b/test/libsolidity/ErrorCheck.cpp
index 9b0f9fb7..7a66c6c7 100644
--- a/test/libsolidity/ErrorCheck.cpp
+++ b/test/libsolidity/ErrorCheck.cpp
@@ -32,7 +32,7 @@ bool dev::solidity::searchErrorMessage(Error const& _err, std::string const& _su
{
if (errorMessage->find(_substr) == std::string::npos)
{
- cout << "Expected message \"" << _substr << "\" but found" << *errorMessage << endl;
+ cout << "Expected message \"" << _substr << "\" but found \"" << *errorMessage << "\".\n";
return false;
}
return true;
diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp
index 2ee5baac..f6f8b8c9 100644
--- a/test/libsolidity/SolidityNameAndTypeResolution.cpp
+++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp
@@ -6146,6 +6146,76 @@ BOOST_AUTO_TEST_CASE(callable_crash)
CHECK_ERROR(text, TypeError, "Type is not callable");
}
+BOOST_AUTO_TEST_CASE(error_transfer_non_payable_fallback)
+{
+ char const* text = R"(
+ contract A {
+ function() {}
+ }
+
+ contract B {
+ A a;
+
+ function() {
+ a.transfer(100);
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Value transfer to a contract without a payable fallback function.");
+}
+
+BOOST_AUTO_TEST_CASE(error_transfer_no_fallback)
+{
+ char const* text = R"(
+ contract A {}
+
+ contract B {
+ A a;
+
+ function() {
+ a.transfer(100);
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Value transfer to a contract without a payable fallback function.");
+}
+
+BOOST_AUTO_TEST_CASE(error_send_non_payable_fallback)
+{
+ char const* text = R"(
+ contract A {
+ function() {}
+ }
+
+ contract B {
+ A a;
+
+ function() {
+ require(a.send(100));
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Value transfer to a contract without a payable fallback function.");
+}
+
+BOOST_AUTO_TEST_CASE(does_not_error_transfer_payable_fallback)
+{
+ char const* text = R"(
+ contract A {
+ function() payable {}
+ }
+
+ contract B {
+ A a;
+
+ function() {
+ a.transfer(100);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
BOOST_AUTO_TEST_CASE(returndatacopy_as_variable)
{
char const* text = R"(