Add type error when attempting value transfer to a non-payable contract

This commit is contained in:
Federico Bond 2017-07-08 22:42:42 -03:00
parent 757c500bda
commit f20b150f38
5 changed files with 100 additions and 0 deletions

View File

@ -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.

View File

@ -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()))

View File

@ -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();

View File

@ -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.

View File

@ -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"(