diff options
-rw-r--r-- | Changelog.md | 1 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 19 | ||||
-rw-r--r-- | libsolidity/ast/Types.cpp | 6 | ||||
-rw-r--r-- | libsolidity/ast/Types.h | 4 | ||||
-rw-r--r-- | test/libsolidity/ErrorCheck.cpp | 2 | ||||
-rw-r--r-- | test/libsolidity/SolidityNameAndTypeResolution.cpp | 70 |
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"( |