diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 2f98ce4f6..9842901e7 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -746,7 +746,7 @@ void FunctionCall::checkTypeRequirements(TypePointers const*) //@todo for structs, we have to check the number of arguments to be equal to the // number of non-mapping members if (m_arguments.size() != 1) - BOOST_THROW_EXCEPTION(createTypeError("More than one argument for explicit type conversion.")); + BOOST_THROW_EXCEPTION(createTypeError("Exactly one argument expected for explicit type conversion.")); if (!isPositionalCall) BOOST_THROW_EXCEPTION(createTypeError("Type conversion cannot allow named arguments.")); if (!m_arguments.front()->getType()->isExplicitlyConvertibleTo(*type.getActualType())) diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 76d05bd0a..838ee264e 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -458,9 +458,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; } case Location::External: + case Location::CallCode: case Location::Bare: + case Location::BareCallCode: _functionCall.getExpression().accept(*this); - appendExternalFunctionCall(function, arguments, function.getLocation() == Location::Bare); + appendExternalFunctionCall(function, arguments); break; case Location::Creation: { @@ -527,13 +529,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) TypePointers{}, strings(), strings(), - Location::External, + Location::Bare, false, true, true ), - {}, - true + {} ); break; case Location::Suicide: @@ -622,7 +623,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << contractAddresses.find(function.getLocation())->second; for (unsigned i = function.getSizeOnStack(); i > 0; --i) m_context << eth::swapInstruction(i); - appendExternalFunctionCall(function, arguments, true); + appendExternalFunctionCall(function, arguments); break; } default: @@ -685,7 +686,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) IntegerType(0, IntegerType::Modifier::Address), true); m_context << eth::Instruction::BALANCE; } - else if (member == "send" || member.substr(0, min(member.size(), 4)) == "call") + else if ((set{"send", "call", "callcode"}).count(member)) appendTypeConversion(*_memberAccess.getExpression().getType(), IntegerType(0, IntegerType::Modifier::Address), true); else @@ -1031,9 +1032,10 @@ void ExpressionCompiler::appendHighBitsCleanup(IntegerType const& _typeOnStack) m_context << ((u256(1) << _typeOnStack.getNumBits()) - 1) << eth::Instruction::AND; } -void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functionType, - vector> const& _arguments, - bool bare) +void ExpressionCompiler::appendExternalFunctionCall( + FunctionType const& _functionType, + vector> const& _arguments +) { solAssert(_functionType.takesArbitraryParameters() || _arguments.size() == _functionType.getParameterTypes().size(), ""); @@ -1047,7 +1049,7 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio unsigned gasValueSize = (_functionType.gasSet() ? 1 : 0) + (_functionType.valueSet() ? 1 : 0); - unsigned contractStackPos = m_context.currentToBaseStackOffset(1 + gasValueSize + (bare ? 0 : 1)); + unsigned contractStackPos = m_context.currentToBaseStackOffset(1 + gasValueSize + (_functionType.isBareCall() ? 0 : 1)); unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize); unsigned valueStackPos = m_context.currentToBaseStackOffset(1); @@ -1057,7 +1059,7 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio unsigned retSize = firstType ? firstType->getCalldataEncodedSize() : 0; m_context << u256(retSize) << u256(0); - if (bare) + if (_functionType.isBareCall()) m_context << u256(0); else { @@ -1074,7 +1076,8 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio _arguments, _functionType.getParameterTypes(), _functionType.padArguments(), - bare, + _functionType.getLocation() == FunctionType::Location::Bare || + _functionType.getLocation() == FunctionType::Location::BareCallCode, _functionType.takesArbitraryParameters() ); @@ -1093,14 +1096,20 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio // send all gas except the amount needed to execute "SUB" and "CALL" // @todo this retains too much gas for now, needs to be fine-tuned. m_context << u256(50 + (_functionType.valueSet() ? 9000 : 0) + 25000) << eth::Instruction::GAS << eth::Instruction::SUB; - m_context << eth::Instruction::CALL; + if ( + _functionType.getLocation() == FunctionType::Location::CallCode || + _functionType.getLocation() == FunctionType::Location::BareCallCode + ) + m_context << eth::Instruction::CALLCODE; + else + m_context << eth::Instruction::CALL; auto tag = m_context.appendConditionalJump(); m_context << eth::Instruction::STOP << tag; // STOP if CALL leaves 0. if (_functionType.valueSet()) m_context << eth::Instruction::POP; if (_functionType.gasSet()) m_context << eth::Instruction::POP; - if (!bare) + if (!_functionType.isBareCall()) m_context << eth::Instruction::POP; m_context << eth::Instruction::POP; // pop contract address diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 45a2311ef..954e32c84 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -98,8 +98,10 @@ private: void appendHighBitsCleanup(IntegerType const& _typeOnStack); /// Appends code to call a function of the given type with the given arguments. - void appendExternalFunctionCall(FunctionType const& _functionType, std::vector> const& _arguments, - bool bare = false); + void appendExternalFunctionCall( + FunctionType const& _functionType, + std::vector> const& _arguments + ); /// Appends code that evaluates the given arguments and moves the result to memory encoded as /// specified by the ABI. The memory offset is expected to be on the stack and is updated by /// this call. If @a _padToWordBoundaries is set to false, all values are concatenated without diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 7a5b309d9..d1f51decf 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -316,6 +316,7 @@ 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)} }); @@ -1115,9 +1116,11 @@ unsigned FunctionType::getSizeOnStack() const } unsigned size = 0; - if (location == Location::External) + if (location == Location::External || location == Location::CallCode) size = 2; - else if (location == Location::Internal || location == Location::Bare) + else if (location == Location::Bare || location == Location::BareCallCode) + size = 1; + else if (location == Location::Internal) size = 1; if (m_gasSet) size++; @@ -1156,6 +1159,7 @@ MemberList const& FunctionType::getMembers() const case Location::SHA256: case Location::RIPEMD160: case Location::Bare: + case Location::BareCallCode: if (!m_members) { MemberList::MemberMap members{ @@ -1228,6 +1232,21 @@ bool FunctionType::hasEqualArgumentTypes(FunctionType const& _other) const ); } +bool FunctionType::isBareCall() const +{ + switch (m_location) + { + case Location::Bare: + case Location::BareCallCode: + case Location::ECRecover: + case Location::SHA256: + case Location::RIPEMD160: + return true; + default: + return false; + } +} + string FunctionType::externalSignature(std::string const& _name) const { std::string funcName = _name; diff --git a/libsolidity/Types.h b/libsolidity/Types.h index da2fcdb89..a69df964c 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -540,17 +540,32 @@ private: class FunctionType: public Type { public: - /// The meaning of the value(s) on the stack referencing the function: - /// INTERNAL: jump tag, EXTERNAL: contract address + function identifier, - /// BARE: contract address (non-abi contract call) - /// OTHERS: special virtual function, nothing on the stack + /// How this function is invoked on the EVM. /// @todo This documentation is outdated, and Location should rather be named "Type" - enum class Location { Internal, External, Creation, Send, - SHA3, Suicide, - ECRecover, SHA256, RIPEMD160, - Log0, Log1, Log2, Log3, Log4, Event, - SetGas, SetValue, BlockHash, - Bare }; + enum class Location + { + Internal, ///< stack-call using plain JUMP + External, ///< external call using CALL + CallCode, ///< extercnal call using CALLCODE, i.e. not exchanging the storage + Bare, ///< CALL without function hash + BareCallCode, ///< CALLCODE without function hash + Creation, ///< external call using CREATE + Send, ///< CALL, but without data and gas + SHA3, ///< SHA3 + Suicide, ///< SUICIDE + ECRecover, ///< CALL to special contract for ecrecover + SHA256, ///< CALL to special contract for sha256 + RIPEMD160, ///< CALL to special contract for ripemd160 + Log0, + Log1, + Log2, + Log3, + Log4, + Event, ///< syntactic sugar for LOG* + SetGas, ///< modify the default gas value for the function call + SetValue, ///< modify the default value transfer for the function call + BlockHash ///< BLOCKHASH + }; virtual Category getCategory() const override { return Category::Function; } @@ -620,6 +635,8 @@ public: /// @returns true if the types of parameters are equal (does't check return parameter types) bool hasEqualArgumentTypes(FunctionType const& _other) const; + /// @returns true if the ABI is used for this call (only meaningful for external calls) + bool isBareCall() const; Location const& getLocation() const { return m_location; } /// @returns the external signature of this function type given the function name /// If @a _name is not provided (empty string) then the @c m_declaration member of the diff --git a/test/libethereum/stateOriginal.cpp b/test/libethereum/stateOriginal.cpp index 82d6288d6..e7a078182 100644 --- a/test/libethereum/stateOriginal.cpp +++ b/test/libethereum/stateOriginal.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include "../TestHelper.h" using namespace std; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index ed5f1acdf..6713382fa 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2558,6 +2558,37 @@ BOOST_AUTO_TEST_CASE(generic_call) BOOST_CHECK_EQUAL(m_state.balance(m_contractAddress), 50 - 2); } +BOOST_AUTO_TEST_CASE(generic_callcode) +{ + char const* sourceCode = R"**( + contract receiver { + uint public received; + function receive(uint256 x) { received = x; } + } + contract sender { + uint public received; + function doSend(address rec) returns (uint d) + { + bytes4 signature = bytes4(bytes32(sha3("receive(uint256)"))); + rec.callcode.value(2)(signature, 23); + return receiver(rec).received(); + } + } + )**"; + compileAndRun(sourceCode, 0, "receiver"); + u160 const c_receiverAddress = m_contractAddress; + compileAndRun(sourceCode, 50, "sender"); + u160 const c_senderAddress = m_contractAddress; + BOOST_CHECK(callContractFunction("doSend(address)", c_receiverAddress) == encodeArgs(0)); + BOOST_CHECK(callContractFunction("received()") == encodeArgs(23)); + m_contractAddress = c_receiverAddress; + BOOST_CHECK(callContractFunction("received()") == encodeArgs(0)); + BOOST_CHECK(m_state.storage(c_receiverAddress).empty()); + BOOST_CHECK(!m_state.storage(c_senderAddress).empty()); + BOOST_CHECK_EQUAL(m_state.balance(c_receiverAddress), 0); + BOOST_CHECK_EQUAL(m_state.balance(c_senderAddress), 50); +} + BOOST_AUTO_TEST_CASE(store_bytes) { // this test just checks that the copy loop does not mess up the stack