diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 86f54e25c..041b6277d 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -497,20 +497,21 @@ void FunctionCall::checkTypeRequirements() // and then ask if that is implicitly convertible to the struct represented by the // function parameters TypePointers const& parameterTypes = functionType->getParameterTypes(); - if (functionType->getLocation() != FunctionType::Location::SHA3 && parameterTypes.size() != m_arguments.size()) + if (!functionType->takesArbitraryParameters() && parameterTypes.size() != m_arguments.size()) BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for function call.")); if (m_names.empty()) { for (size_t i = 0; i < m_arguments.size(); ++i) - if (functionType->getLocation() != FunctionType::Location::SHA3 && + if (!functionType->takesArbitraryParameters() && !m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i])) BOOST_THROW_EXCEPTION(m_arguments[i]->createTypeError("Invalid type for argument in function call.")); } else { - if (functionType->getLocation() == FunctionType::Location::SHA3) - BOOST_THROW_EXCEPTION(createTypeError("Named arguments cannnot be used for SHA3.")); + if (functionType->takesArbitraryParameters()) + BOOST_THROW_EXCEPTION(createTypeError("Named arguments cannnot be used for functions " + "that take arbitrary parameters.")); auto const& parameterNames = functionType->getParameterNames(); if (parameterNames.size() != m_names.size()) BOOST_THROW_EXCEPTION(createTypeError("Some argument names are missing.")); diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index f649f1a4f..1d3f23e69 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -206,7 +206,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) TypePointers const& parameterTypes = function.getParameterTypes(); vector> const& callArguments = _functionCall.getArguments(); vector> const& callArgumentNames = _functionCall.getNames(); - if (function.getLocation() != Location::SHA3) + if (!function.takesArbitraryParameters()) solAssert(callArguments.size() == parameterTypes.size(), ""); vector> arguments; @@ -318,7 +318,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); appendExternalFunctionCall(FunctionType(TypePointers{}, TypePointers{}, - Location::External, true, true), {}, true); + Location::External, false, true, true), {}, true); break; case Location::Suicide: arguments.front()->accept(*this); @@ -327,7 +327,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; case Location::SHA3: { - unsigned length = appendArgumentsCopyToMemory(arguments, TypePointers(), 0, false); + unsigned length = appendArgumentsCopyToMemory(arguments, TypePointers(), 0, function.padArguments()); m_context << u256(length) << u256(0) << eth::Instruction::SHA3; break; } @@ -700,21 +700,30 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con if (stackTypeCategory == Type::Category::String) { + StaticStringType const& typeOnStack = dynamic_cast(_typeOnStack); if (targetTypeCategory == Type::Category::Integer) { // conversion from string to hash. no need to clean the high bit // only to shift right because of opposite alignment IntegerType const& targetIntegerType = dynamic_cast(_targetType); - StaticStringType const& typeOnStack = dynamic_cast(_typeOnStack); solAssert(targetIntegerType.isHash(), "Only conversion between String and Hash is allowed."); solAssert(targetIntegerType.getNumBits() == typeOnStack.getNumBytes() * 8, "The size should be the same."); m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; } else { + // clear lower-order bytes for conversion to shorter strings - we always clean solAssert(targetTypeCategory == Type::Category::String, "Invalid type conversion requested."); - // nothing to do, strings are high-order-bit-aligned - //@todo clear lower-order bytes if we allow explicit conversion to shorter strings + StaticStringType const& targetType = dynamic_cast(_targetType); + if (targetType.getNumBytes() < typeOnStack.getNumBytes()) + { + if (targetType.getNumBytes() == 0) + m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; + else + m_context << (u256(1) << (256 - targetType.getNumBytes() * 8)) + << eth::Instruction::DUP1 << eth::Instruction::SWAP2 + << eth::Instruction::DIV << eth::Instruction::MUL; + } } } else if (stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::Contract || @@ -776,7 +785,8 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio vector> const& _arguments, bool bare) { - solAssert(_arguments.size() == _functionType.getParameterTypes().size(), ""); + solAssert(_functionType.takesArbitraryParameters() || + _arguments.size() == _functionType.getParameterTypes().size(), ""); // Assumed stack content here: // @@ -800,7 +810,10 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio // reserve space for the function identifier unsigned dataOffset = bare ? 0 : CompilerUtils::dataStartOffset; - dataOffset += appendArgumentsCopyToMemory(_arguments, _functionType.getParameterTypes(), dataOffset); + // For bare call, activate "4 byte pad exception": If the first argument has exactly 4 bytes, + // do not pad it to 32 bytes. + dataOffset += appendArgumentsCopyToMemory(_arguments, _functionType.getParameterTypes(), dataOffset, + _functionType.padArguments(), bare); //@todo only return the first return value for now Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr : @@ -839,7 +852,8 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio unsigned ExpressionCompiler::appendArgumentsCopyToMemory(vector> const& _arguments, TypePointers const& _types, unsigned _memoryOffset, - bool _padToWordBoundaries) + bool _padToWordBoundaries, + bool _padExceptionIfFourBytes) { solAssert(_types.empty() || _types.size() == _arguments.size(), ""); unsigned length = 0; @@ -848,8 +862,12 @@ unsigned ExpressionCompiler::appendArgumentsCopyToMemory(vectoraccept(*this); TypePointer const& expectedType = _types.empty() ? _arguments[i]->getType()->getRealType() : _types[i]; appendTypeConversion(*_arguments[i]->getType(), *expectedType, true); + bool pad = _padToWordBoundaries; + // Do not pad if the first argument has exactly four bytes + if (i == 0 && pad && _padExceptionIfFourBytes && expectedType->getCalldataEncodedSize() == 4) + pad = false; length += appendTypeMoveToMemory(*expectedType, _arguments[i]->getLocation(), - _memoryOffset + length, _padToWordBoundaries); + _memoryOffset + length, pad); } return length; } diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index df8a0516e..3c94d74c2 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -97,7 +97,8 @@ private: unsigned appendArgumentsCopyToMemory(std::vector> const& _arguments, TypePointers const& _types = {}, unsigned _memoryOffset = 0, - bool _padToWordBoundaries = true); + bool _padToWordBoundaries = true, + bool _padExceptionIfFourBytes = false); /// Appends code that moves a stack element of the given type to memory /// @returns the number of bytes moved to memory unsigned appendTypeMoveToMemory(Type const& _type, Location const& _location, unsigned _memoryOffset, diff --git a/libsolidity/GlobalContext.cpp b/libsolidity/GlobalContext.cpp index 8443e7bd9..60de5105f 100644 --- a/libsolidity/GlobalContext.cpp +++ b/libsolidity/GlobalContext.cpp @@ -40,7 +40,7 @@ m_magicVariables(vector>{make_shared< make_shared("suicide", make_shared(strings{"address"}, strings{}, FunctionType::Location::Suicide)), make_shared("sha3", - make_shared(strings{"hash"}, strings{"hash"}, FunctionType::Location::SHA3)), + make_shared(strings(), strings{"hash"}, FunctionType::Location::SHA3, true)), make_shared("log0", make_shared(strings{"hash"},strings{}, FunctionType::Location::Log0)), make_shared("log1", @@ -52,11 +52,11 @@ m_magicVariables(vector>{make_shared< make_shared("log4", make_shared(strings{"hash", "hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log4)), make_shared("sha256", - make_shared(strings{"hash"}, strings{"hash"}, FunctionType::Location::SHA256)), + make_shared(strings(), strings{"hash"}, FunctionType::Location::SHA256, true)), make_shared("ecrecover", make_shared(strings{"hash", "hash8", "hash", "hash"}, strings{"address"}, FunctionType::Location::ECRecover)), make_shared("ripemd160", - make_shared(strings{"hash"}, strings{"hash160"}, FunctionType::Location::RIPEMD160))}) + make_shared(strings(), strings{"hash160"}, FunctionType::Location::RIPEMD160, true))}) { } diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 02c293b7d..6fd9e8b4c 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -210,10 +210,7 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe const MemberList IntegerType::AddressMemberList = MemberList({{"balance", make_shared(256)}, - {"callstring32", make_shared(strings{"string32"}, strings{}, - FunctionType::Location::Bare)}, - {"callstring32string32", make_shared(strings{"string32", "string32"}, - strings{}, FunctionType::Location::Bare)}, + {"call", make_shared(strings(), strings(), FunctionType::Location::Bare, true)}, {"send", make_shared(strings{"uint"}, strings{}, FunctionType::Location::Send)}}); IntegerConstantType::IntegerConstantType(Literal const& _literal) @@ -401,13 +398,16 @@ bool StaticStringType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool StaticStringType::isExplicitlyConvertibleTo(Type const& _convertTo) const { + if (_convertTo.getCategory() == getCategory()) + return true; if (_convertTo.getCategory() == Category::Integer) { IntegerType const& convertTo = dynamic_cast(_convertTo); if (convertTo.isHash() && (m_bytes * 8 == convertTo.getNumBits())) return true; } - return isImplicitlyConvertibleTo(_convertTo); + + return false; } bool StaticStringType::operator==(Type const& _other) const @@ -766,10 +766,10 @@ MemberList const& FunctionType::getMembers() const map members{ {"gas", make_shared(parseElementaryTypeVector({"uint"}), TypePointers{copyAndSetGasOrValue(true, false)}, - Location::SetGas, m_gasSet, m_valueSet)}, + Location::SetGas, false, m_gasSet, m_valueSet)}, {"value", make_shared(parseElementaryTypeVector({"uint"}), TypePointers{copyAndSetGasOrValue(false, true)}, - Location::SetValue, m_gasSet, m_valueSet)}}; + Location::SetValue, false, m_gasSet, m_valueSet)}}; if (m_location == Location::Creation) members.erase("gas"); m_members.reset(new MemberList(members)); @@ -808,6 +808,7 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) const { return make_shared(m_parameterTypes, m_returnParameterTypes, m_location, + m_arbitraryParameters, m_gasSet || _setGas, m_valueSet || _setValue); } diff --git a/libsolidity/Types.h b/libsolidity/Types.h index d09782954..05090c7a2 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -367,14 +367,15 @@ public: explicit FunctionType(VariableDeclaration const& _varDecl); explicit FunctionType(EventDefinition const& _event); FunctionType(strings const& _parameterTypes, strings const& _returnParameterTypes, - Location _location = Location::Internal): + Location _location = Location::Internal, bool _arbitraryParameters = false): FunctionType(parseElementaryTypeVector(_parameterTypes), parseElementaryTypeVector(_returnParameterTypes), - _location) {} + _location, _arbitraryParameters) {} FunctionType(TypePointers const& _parameterTypes, TypePointers const& _returnParameterTypes, Location _location = Location::Internal, - bool _gasSet = false, bool _valueSet = false): + bool _arbitraryParameters = false, bool _gasSet = false, bool _valueSet = false): m_parameterTypes(_parameterTypes), m_returnParameterTypes(_returnParameterTypes), - m_location(_location), m_gasSet(_gasSet), m_valueSet(_valueSet) {} + m_location(_location), + m_arbitraryParameters(_arbitraryParameters), m_gasSet(_gasSet), m_valueSet(_valueSet) {} TypePointers const& getParameterTypes() const { return m_parameterTypes; } std::vector const& getParameterNames() const { return m_parameterNames; } @@ -407,6 +408,9 @@ public: /// Can contain a nullptr in which case indicates absence of documentation ASTPointer getDocumentation() const; + /// true iff arguments are to be padded to multiples of 32 bytes for external calls + bool padArguments() const { return !(m_location == Location::SHA3 || m_location == Location::SHA256 || m_location == Location::RIPEMD160); } + bool takesArbitraryParameters() const { return m_arbitraryParameters; } bool gasSet() const { return m_gasSet; } bool valueSet() const { return m_valueSet; } @@ -422,6 +426,8 @@ private: std::vector m_parameterNames; std::vector m_returnParameterNames; Location const m_location; + /// true iff the function takes an arbitrary number of arguments of arbitrary types + bool const m_arbitraryParameters = false; bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack bool const m_valueSet = false; ///< true iff the value to be sent is on the stack bool m_isConstant; diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index 5bd1e8578..31b80894e 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -2201,6 +2201,29 @@ BOOST_AUTO_TEST_CASE(sha3_multiple_arguments_with_string_literals) bytes({0x66, 0x6f, 0x6f})))); } +BOOST_AUTO_TEST_CASE(generic_call) +{ + char const* sourceCode = R"**( + contract receiver { + uint public received; + function receive(uint256 x) { received = x; } + } + contract sender { + function doSend(address rec) returns (uint d) + { + string4 signature = string4(string32(sha3("receive(uint256)"))); + rec.call.value(2)(signature, 23); + return receiver(rec).received(); + } + } + )**"; + compileAndRun(sourceCode, 0, "receiver"); + u160 const c_receiverAddress = m_contractAddress; + compileAndRun(sourceCode, 50, "sender"); + BOOST_REQUIRE(callContractFunction("doSend(address)", c_receiverAddress) == encodeArgs(23)); + BOOST_CHECK_EQUAL(m_state.balance(m_contractAddress), 50 - 2); +} + BOOST_AUTO_TEST_SUITE_END() }