diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 6dc728405..40ed1fd8e 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -178,6 +178,16 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract) map, FunctionTypePointer> interfaceFunctions = _contract.getInterfaceFunctions(); map, const eth::AssemblyItem> callDataUnpackerEntryPoints; + FunctionDefinition const* fallback = _contract.getFallbackFunction(); + eth::AssemblyItem notFound = m_context.newTag(); + // shortcut messages without data if we have many functions in order to be able to receive + // ether with constant gas + if (interfaceFunctions.size() > 5 || fallback) + { + m_context << eth::Instruction::CALLDATASIZE << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(notFound); + } + // retrieve the function signature hash from the calldata if (!interfaceFunctions.empty()) CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true); @@ -189,7 +199,10 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract) m_context << eth::dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << eth::Instruction::EQ; m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first)); } - if (FunctionDefinition const* fallback = _contract.getFallbackFunction()) + m_context.appendJumpTo(notFound); + + m_context << notFound; + if (fallback) { eth::AssemblyItem returnTag = m_context.pushNewTag(); fallback->accept(*this); @@ -198,6 +211,7 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract) } else m_context << eth::Instruction::STOP; // function not found + for (auto const& it: interfaceFunctions) { FunctionTypePointer const& functionType = it.second; diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 62df9205f..bac967d84 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -521,6 +521,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; case Location::Send: _functionCall.getExpression().accept(*this); + m_context << u256(0); // do not send gas (there still is the stipend) arguments.front()->accept(*this); appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); @@ -532,7 +533,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) strings(), Location::Bare, false, - false, + true, true ), {} @@ -1057,10 +1058,15 @@ void ExpressionCompiler::appendExternalFunctionCall( unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize); unsigned valueStackPos = m_context.currentToBaseStackOffset(1); + bool returnSuccessCondition = + _functionType.getLocation() == FunctionType::Location::Bare || + _functionType.getLocation() == FunctionType::Location::BareCallCode; //@todo only return the first return value for now Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr : _functionType.getReturnParameterTypes().front().get(); unsigned retSize = firstType ? firstType->getCalldataEncodedSize() : 0; + if (returnSuccessCondition) + retSize = 0; // return value actually is success condition m_context << u256(retSize) << u256(0); if (_functionType.isBareCall()) @@ -1111,19 +1117,28 @@ void ExpressionCompiler::appendExternalFunctionCall( else m_context << eth::Instruction::CALL; - //Propagate error condition (if CALL pushes 0 on stack). - m_context << eth::Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + unsigned remainsSize = + 1 + // contract address + _functionType.valueSet() + + _functionType.gasSet() + + !_functionType.isBareCall(); - if (_functionType.valueSet()) - m_context << eth::Instruction::POP; - if (_functionType.gasSet()) - m_context << eth::Instruction::POP; - if (!_functionType.isBareCall()) - m_context << eth::Instruction::POP; - m_context << eth::Instruction::POP; // pop contract address + if (returnSuccessCondition) + m_context << eth::swapInstruction(remainsSize); + else + { + //Propagate error condition (if CALL pushes 0 on stack). + m_context << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(m_context.errorTag()); + } - if (_functionType.getLocation() == FunctionType::Location::RIPEMD160) + CompilerUtils(m_context).popStackSlots(remainsSize); + + if (returnSuccessCondition) + { + // already there + } + else if (_functionType.getLocation() == FunctionType::Location::RIPEMD160) { // fix: built-in contract returns right-aligned data CompilerUtils(m_context).loadFromMemory(0, IntegerType(160), false, true); diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 03e8e4ee0..1316bbc37 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -317,9 +317,9 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe const MemberList IntegerType::AddressMemberList({ {"balance", make_shared(256)}, - {"call", make_shared(strings(), strings(), FunctionType::Location::Bare, true)}, - {"callcode", make_shared(strings(), strings(), FunctionType::Location::BareCallCode, true)}, - {"send", make_shared(strings{"uint"}, strings{}, FunctionType::Location::Send)} + {"call", make_shared(strings(), strings{"bool"}, FunctionType::Location::Bare, true)}, + {"callcode", make_shared(strings(), strings{"bool"}, FunctionType::Location::BareCallCode, true)}, + {"send", make_shared(strings{"uint"}, strings{"bool"}, FunctionType::Location::Send)} }); IntegerConstantType::IntegerConstantType(Literal const& _literal) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 1d0f1fc22..89ed81e23 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4187,6 +4187,28 @@ BOOST_AUTO_TEST_CASE(positive_integers_to_signed) BOOST_CHECK(callContractFunction("q()") == encodeArgs(250)); } +BOOST_AUTO_TEST_CASE(failing_send) +{ + char const* sourceCode = R"( + contract Helper { + uint[] data; + function () { + data[9]; // trigger exception + } + } + contract Main { + function callHelper(address _a) returns (bool r, uint bal) { + r = !_a.send(5); + bal = this.balance; + } + } + )"; + compileAndRun(sourceCode, 0, "Helper"); + u160 const c_helperAddress = m_contractAddress; + compileAndRun(sourceCode, 20, "Main"); + BOOST_REQUIRE(callContractFunction("callHelper(address)", c_helperAddress) == encodeArgs(true, 20)); +} + BOOST_AUTO_TEST_SUITE_END() }