From d759075528805a8b470dce2c8a3b9caeade62050 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 25 Nov 2014 14:43:23 +0100 Subject: [PATCH 1/3] More general function types and references. --- libsolidity/AST.cpp | 12 ++++----- libsolidity/ExpressionCompiler.cpp | 10 ++++---- libsolidity/Types.cpp | 41 +++++++++++++++++++++++++++--- libsolidity/Types.h | 38 ++++++++++++++++----------- test/solidityEndToEndTest.cpp | 20 +++++++++++++++ 5 files changed, 92 insertions(+), 29 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 168a095c8..5c07ec80e 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -467,19 +467,19 @@ void FunctionCall::checkTypeRequirements() //@todo would be nice to create a struct type from the arguments // and then ask if that is implicitly convertible to the struct represented by the // function parameters - FunctionDefinition const& fun = dynamic_cast(*expressionType).getFunction(); - vector> const& parameters = fun.getParameters(); - if (parameters.size() != m_arguments.size()) + FunctionType const& functionType = dynamic_cast(*expressionType); + TypePointers const& parameterTypes = functionType.getParameterTypes(); + if (parameterTypes.size() != m_arguments.size()) BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for function call.")); for (size_t i = 0; i < m_arguments.size(); ++i) - if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameters[i]->getType())) + if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i])) BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in function call.")); // @todo actually the return type should be an anonymous struct, // but we change it to the type of the first return value until we have structs - if (fun.getReturnParameters().empty()) + if (functionType.getReturnParameterTypes().empty()) m_type = make_shared(); else - m_type = fun.getReturnParameters().front()->getType(); + m_type = functionType.getReturnParameterTypes().front(); } } diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index de8bc1d28..9e396874c 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -183,16 +183,16 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) // Calling convention: Caller pushes return address and arguments // Callee removes them and pushes return values - FunctionDefinition const& function = dynamic_cast(*_functionCall.getExpression().getType()).getFunction(); + FunctionType const& function = dynamic_cast(*_functionCall.getExpression().getType()); eth::AssemblyItem returnLabel = m_context.pushNewTag(); std::vector> const& arguments = _functionCall.getArguments(); - if (asserts(arguments.size() == function.getParameters().size())) + if (asserts(arguments.size() == function.getParameterTypes().size())) BOOST_THROW_EXCEPTION(InternalCompilerError()); for (unsigned i = 0; i < arguments.size(); ++i) { arguments[i]->accept(*this); - appendTypeConversion(*arguments[i]->getType(), *function.getParameters()[i]->getType()); + appendTypeConversion(*arguments[i]->getType(), *function.getParameterTypes()[i]); } _functionCall.getExpression().accept(*this); @@ -200,11 +200,11 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) m_context << returnLabel; // callee adds return parameters, but removes arguments and return label - m_context.adjustStackOffset(function.getReturnParameters().size() - arguments.size() - 1); + m_context.adjustStackOffset(function.getReturnParameterTypes().size() - arguments.size() - 1); // @todo for now, the return value of a function is its first return value, so remove // all others - for (unsigned i = 1; i < function.getReturnParameters().size(); ++i) + for (unsigned i = 1; i < function.getReturnParameterTypes().size(); ++i) m_context << eth::Instruction::POP; } return false; diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 616c91831..a6b18b10e 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -189,7 +189,10 @@ u256 IntegerType::literalValue(Literal const& _literal) const return u256(value); } -const MemberList IntegerType::AddressMemberList = MemberList({{"balance", std::make_shared(256)}}); +const MemberList IntegerType::AddressMemberList = + MemberList({{"balance", make_shared(256)}, + {"send", make_shared(TypePointers({make_shared(256)}), + TypePointers())}}); bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const { @@ -299,17 +302,49 @@ u256 StructType::getStorageOffsetOfMember(string const& _name) const BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage offset of non-existing member requested.")); } +FunctionType::FunctionType(FunctionDefinition const& _function) +{ + TypePointers params; + TypePointers retParams; + params.reserve(_function.getParameters().size()); + for (ASTPointer const& var: _function.getParameters()) + params.push_back(var->getType()); + retParams.reserve(_function.getReturnParameters().size()); + for (ASTPointer const& var: _function.getReturnParameters()) + retParams.push_back(var->getType()); + swap(params, m_parameterTypes); + swap(retParams, m_returnParameterTypes); +} + bool FunctionType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) return false; FunctionType const& other = dynamic_cast(_other); - return other.m_function == m_function; + + if (m_parameterTypes.size() != other.m_parameterTypes.size() || + m_returnParameterTypes.size() != other.m_returnParameterTypes.size()) + return false; + auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }; + + if (!equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), + other.m_parameterTypes.cbegin(), typeCompare)) + return false; + if (!equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), + other.m_returnParameterTypes.cbegin(), typeCompare)) + return false; + return true; } string FunctionType::toString() const { - return "function " + m_function.getName(); + string name = "function ("; + for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) + name += (*it)->toString() + (it + 1 == m_parameterTypes.end() ? "" : ","); + name += ") returns ("; + for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it) + name += (*it)->toString() + (it + 1 == m_returnParameterTypes.end() ? "" : ","); + return name + ")"; } bool MappingType::operator==(Type const& _other) const diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 2dd4af64f..17d9053b4 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -39,6 +39,8 @@ namespace solidity // @todo realMxN, string class Type; // forward +using TypePointer = std::shared_ptr; +using TypePointers = std::vector; /** * List of members of a type. @@ -46,7 +48,6 @@ class Type; // forward class MemberList { public: - using TypePointer = std::shared_ptr; using MemberMap = std::map; MemberList() {} @@ -54,7 +55,7 @@ public: TypePointer getMemberType(std::string const& _name) const { auto it = m_memberTypes.find(_name); - return it != m_memberTypes.end() ? it->second : std::shared_ptr(); + return it != m_memberTypes.end() ? it->second : TypePointer(); } MemberMap::const_iterator begin() const { return m_memberTypes.begin(); } @@ -82,6 +83,7 @@ public: static std::shared_ptr fromElementaryTypeName(Token::Value _typeToken); static std::shared_ptr fromUserDefinedTypeName(UserDefinedTypeName const& _typeName); static std::shared_ptr fromMapping(Mapping const& _typeName); + static std::shared_ptr fromFunction(FunctionDefinition const& _function); /// @} /// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does @@ -117,7 +119,7 @@ public: /// Returns the list of all members of this type. Default implementation: no members. virtual MemberList const& getMembers() const { return EmptyMemberList; } /// Convenience method, returns the type of the given named member or an empty pointer if no such member exists. - std::shared_ptr getMemberType(std::string const& _name) const { return getMembers().getMemberType(_name); } + TypePointer getMemberType(std::string const& _name) const { return getMembers().getMemberType(_name); } virtual std::string toString() const = 0; virtual u256 literalValue(Literal const&) const @@ -246,15 +248,20 @@ private: }; /** - * The type of a function, there is one distinct type per function definition. + * The type of a function, identified by its (return) parameter types. + * @todo the return parameters should also have names, i.e. return parameters should be a struct + * type. */ class FunctionType: public Type { public: virtual Category getCategory() const override { return Category::FUNCTION; } - FunctionType(FunctionDefinition const& _function): m_function(_function) {} + explicit FunctionType(FunctionDefinition const& _function); + FunctionType(TypePointers const& _parameterTypes, TypePointers const& _returnParameterTypes): + m_parameterTypes(_parameterTypes), m_returnParameterTypes(_returnParameterTypes) {} - FunctionDefinition const& getFunction() const { return m_function; } + TypePointers const& getParameterTypes() const { return m_parameterTypes; } + TypePointers const& getReturnParameterTypes() const { return m_returnParameterTypes; } virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; @@ -263,7 +270,8 @@ public: virtual bool canLiveOutsideStorage() const override { return false; } private: - FunctionDefinition const& m_function; + TypePointers m_parameterTypes; + TypePointers m_returnParameterTypes; }; /** @@ -273,19 +281,19 @@ class MappingType: public Type { public: virtual Category getCategory() const override { return Category::MAPPING; } - MappingType(std::shared_ptr _keyType, std::shared_ptr _valueType): + MappingType(TypePointer const& _keyType, TypePointer const& _valueType): m_keyType(_keyType), m_valueType(_valueType) {} virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; virtual bool canLiveOutsideStorage() const override { return false; } - std::shared_ptr getKeyType() const { return m_keyType; } - std::shared_ptr getValueType() const { return m_valueType; } + TypePointer getKeyType() const { return m_keyType; } + TypePointer getValueType() const { return m_valueType; } private: - std::shared_ptr m_keyType; - std::shared_ptr m_valueType; + TypePointer m_keyType; + TypePointer m_valueType; }; /** @@ -312,9 +320,9 @@ class TypeType: public Type { public: virtual Category getCategory() const override { return Category::TYPE; } - TypeType(std::shared_ptr const& _actualType): m_actualType(_actualType) {} + TypeType(TypePointer const& _actualType): m_actualType(_actualType) {} - std::shared_ptr const& getActualType() const { return m_actualType; } + TypePointer const& getActualType() const { return m_actualType; } virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } @@ -323,7 +331,7 @@ public: virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } private: - std::shared_ptr m_actualType; + TypePointer m_actualType; }; diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index fc542f141..66bb0bd7f 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -774,6 +774,26 @@ BOOST_AUTO_TEST_CASE(blockchain) BOOST_CHECK(callContractFunction(0, bytes{0}, u256(28)) == toBigEndian(u256(28)) + bytes(20, 0) + toBigEndian(u256(1))); } +BOOST_AUTO_TEST_CASE(function_types) +{ + char const* sourceCode = "contract test {\n" + " function a(bool selector) returns (uint b) {\n" + " var f = fun1;\n" + " if (selector) f = fun2;\n" + " return f(9);\n" + " }\n" + " function fun1(uint x) returns (uint b) {\n" + " return 11;\n" + " }\n" + " function fun2(uint x) returns (uint b) {\n" + " return 12;\n" + " }\n" + "}\n"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction(0, bytes{0}) == toBigEndian(u256(11))); + BOOST_CHECK(callContractFunction(0, bytes{1}) == toBigEndian(u256(12))); +} + BOOST_AUTO_TEST_SUITE_END() } From 898f989aa19416cba74c6e07db05588e0c02d976 Mon Sep 17 00:00:00 2001 From: Christian Date: Tue, 25 Nov 2014 18:23:39 +0100 Subject: [PATCH 2/3] Sending ether. --- libsolidity/Compiler.cpp | 2 +- libsolidity/ExpressionCompiler.cpp | 79 +++++++++++++++++++++--------- libsolidity/ExpressionCompiler.h | 4 ++ libsolidity/Types.cpp | 16 +++++- libsolidity/Types.h | 19 ++++++- test/solidityEndToEndTest.cpp | 15 ++++++ 6 files changed, 108 insertions(+), 27 deletions(-) diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index a82ecd95a..988390d0b 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -325,7 +325,7 @@ bool Compiler::visit(ExpressionStatement& _expressionStatement) Expression& expression = _expressionStatement.getExpression(); ExpressionCompiler::compileExpression(m_context, expression); Type::Category category = expression.getType()->getCategory(); - if (category != Type::Category::VOID && category != Type::Category::MAGIC) + for (unsigned i = 0; i < expression.getType()->getSizeOnStack(); ++i) m_context << eth::Instruction::POP; return false; } diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 9e396874c..f5ab829c3 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -179,33 +179,62 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) } else { - //@todo: check for "external call" (to be stored in type) - - // Calling convention: Caller pushes return address and arguments - // Callee removes them and pushes return values FunctionType const& function = dynamic_cast(*_functionCall.getExpression().getType()); - - eth::AssemblyItem returnLabel = m_context.pushNewTag(); std::vector> const& arguments = _functionCall.getArguments(); if (asserts(arguments.size() == function.getParameterTypes().size())) BOOST_THROW_EXCEPTION(InternalCompilerError()); - for (unsigned i = 0; i < arguments.size(); ++i) + + if (function.getLocation() == FunctionType::Location::INTERNAL) { - arguments[i]->accept(*this); - appendTypeConversion(*arguments[i]->getType(), *function.getParameterTypes()[i]); + // Calling convention: Caller pushes return address and arguments + // Callee removes them and pushes return values + + eth::AssemblyItem returnLabel = m_context.pushNewTag(); + for (unsigned i = 0; i < arguments.size(); ++i) + { + arguments[i]->accept(*this); + appendTypeConversion(*arguments[i]->getType(), *function.getParameterTypes()[i]); + } + _functionCall.getExpression().accept(*this); + + m_context.appendJump(); + m_context << returnLabel; + + // callee adds return parameters, but removes arguments and return label + m_context.adjustStackOffset(function.getReturnParameterTypes().size() - arguments.size() - 1); + + // @todo for now, the return value of a function is its first return value, so remove + // all others + for (unsigned i = 1; i < function.getReturnParameterTypes().size(); ++i) + m_context << eth::Instruction::POP; + } + else if (function.getLocation() == FunctionType::Location::EXTERNAL) + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("External function calls not implemented yet.")); + } + else + { + switch (function.getLocation()) + { + case FunctionType::Location::SEND: + m_context << u256(0) << u256(0) << u256(0) << u256(0); + arguments.front()->accept(*this); + //@todo might not be necessary + appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front()); + _functionCall.getExpression().accept(*this); + m_context << u256(25) << eth::Instruction::GAS << eth::Instruction::SUB + << eth::Instruction::CALL + << eth::Instruction::POP; + break; + case FunctionType::Location::SUICIDE: + arguments.front()->accept(*this); + //@todo might not be necessary + appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front()); + m_context << eth::Instruction::SUICIDE; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Function not yet implemented.")); + } } - _functionCall.getExpression().accept(*this); - - m_context.appendJump(); - m_context << returnLabel; - - // callee adds return parameters, but removes arguments and return label - m_context.adjustStackOffset(function.getReturnParameterTypes().size() - arguments.size() - 1); - - // @todo for now, the return value of a function is its first return value, so remove - // all others - for (unsigned i = 1; i < function.getReturnParameterTypes().size(); ++i) - m_context << eth::Instruction::POP; } return false; } @@ -216,9 +245,13 @@ void ExpressionCompiler::endVisit(MemberAccess& _memberAccess) switch (_memberAccess.getExpression().getType()->getCategory()) { case Type::Category::INTEGER: - if (asserts(member == "balance")) + if (member == "balance") + m_context << eth::Instruction::BALANCE; + else if (member == "send") + { // no modification + } + else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); - m_context << eth::Instruction::BALANCE; break; case Type::Category::CONTRACT: // call function diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 3ed7848b5..83d7cdc6c 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -132,6 +132,10 @@ private: CompilerContext& m_context; LValue m_currentLValue; + /// If a "virtual" function (i.e. a bulit-in function without jump tag) is encountered, the + /// actual function is stored here. @todo prevent assignment or store it with assignment + enum class SpecialFunction { NONE, SEND, SHA3, SUICIDE, ECRECOVER, SHA256, RIPEMD160 }; + SpecialFunction m_currentSpecialFunction; }; diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index a6b18b10e..4ab53bf86 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -192,7 +192,7 @@ u256 IntegerType::literalValue(Literal const& _literal) const const MemberList IntegerType::AddressMemberList = MemberList({{"balance", make_shared(256)}, {"send", make_shared(TypePointers({make_shared(256)}), - TypePointers())}}); + TypePointers(), FunctionType::Location::SEND)}}); bool BoolType::isExplicitlyConvertibleTo(Type const& _convertTo) const { @@ -314,6 +314,7 @@ FunctionType::FunctionType(FunctionDefinition const& _function) retParams.push_back(var->getType()); swap(params, m_parameterTypes); swap(retParams, m_returnParameterTypes); + m_location = Location::INTERNAL; } bool FunctionType::operator==(Type const& _other) const @@ -347,6 +348,19 @@ string FunctionType::toString() const return name + ")"; } +unsigned FunctionType::getSizeOnStack() const +{ + switch (m_location) + { + case Location::INTERNAL: + return 1; + case Location::EXTERNAL: + return 2; + default: + return 0; + } +} + bool MappingType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 17d9053b4..630c9c213 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -115,6 +115,7 @@ public: /// Returns true if the type can be stored as a value (as opposed to a reference) on the stack, /// i.e. it behaves differently in lvalue context and in value context. virtual bool isValueType() const { return false; } + virtual unsigned getSizeOnStack() const { return 1; } /// Returns the list of all members of this type. Default implementation: no members. virtual MemberList const& getMembers() const { return EmptyMemberList; } @@ -235,6 +236,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override; + virtual unsigned getSizeOnStack() const override { return 1; /*@todo*/ } virtual std::string toString() const override; virtual MemberList const& getMembers() const override; @@ -255,10 +257,17 @@ 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 index, + /// OTHERS: special virtual function, nothing on the stack + enum class Location { INTERNAL, EXTERNAL, SEND, SHA3, SUICIDE, ECRECOVER, SHA256, RIPEMD160 }; + virtual Category getCategory() const override { return Category::FUNCTION; } explicit FunctionType(FunctionDefinition const& _function); - FunctionType(TypePointers const& _parameterTypes, TypePointers const& _returnParameterTypes): - m_parameterTypes(_parameterTypes), m_returnParameterTypes(_returnParameterTypes) {} + FunctionType(TypePointers const& _parameterTypes, TypePointers const& _returnParameterTypes, + Location _location = Location::INTERNAL): + m_parameterTypes(_parameterTypes), m_returnParameterTypes(_returnParameterTypes), + m_location(_location) {} TypePointers const& getParameterTypes() const { return m_parameterTypes; } TypePointers const& getReturnParameterTypes() const { return m_returnParameterTypes; } @@ -268,10 +277,14 @@ public: virtual bool canBeStored() const override { return false; } virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); } virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned getSizeOnStack() const override; + + Location getLocation() const { return m_location; } private: TypePointers m_parameterTypes; TypePointers m_returnParameterTypes; + Location m_location; }; /** @@ -310,6 +323,7 @@ public: virtual bool canBeStored() const override { return false; } virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable void type requested.")); } virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned getSizeOnStack() const override { return 0; } }; /** @@ -349,6 +363,7 @@ public: virtual bool operator==(Type const& _other) const; virtual bool canBeStored() const override { return false; } virtual bool canLiveOutsideStorage() const override { return true; } + virtual unsigned getSizeOnStack() const override { return 0; } virtual MemberList const& getMembers() const override { return m_members; } virtual std::string toString() const override; diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index 66bb0bd7f..80a435d1e 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -794,6 +794,21 @@ BOOST_AUTO_TEST_CASE(function_types) BOOST_CHECK(callContractFunction(0, bytes{1}) == toBigEndian(u256(12))); } +BOOST_AUTO_TEST_CASE(send_ether) +{ + char const* sourceCode = "contract test {\n" + " function a(address addr, uint amount) returns (uint ret) {\n" + " addr.send(amount);\n" + " return address(this).balance;\n" + " }\n" + "}\n"; + u256 amount(130); + compileAndRun(sourceCode, amount + 1); + u160 address(23); + BOOST_CHECK(callContractFunction(0, address, amount) == toBigEndian(u256(1))); + BOOST_CHECK_EQUAL(m_state.balance(address), amount); +} + BOOST_AUTO_TEST_SUITE_END() } From 68acfc0544c7121fa1d25d2421122b97a1e67684 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 26 Nov 2014 13:19:17 +0100 Subject: [PATCH 3/3] Global functions. --- libsolidity/AST.h | 11 ++-- libsolidity/ExpressionCompiler.cpp | 55 ++++++++++++++---- libsolidity/GlobalContext.cpp | 37 ++++++++---- libsolidity/Types.h | 2 +- test/solidityEndToEndTest.cpp | 91 +++++++++++++++++++++++++++++- 5 files changed, 167 insertions(+), 29 deletions(-) diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 89f268b33..3fb251d95 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -233,23 +233,20 @@ private: }; /** - * Pseudo AST node that is used as declaration for "this", "msg", "tx" and "block" when the - * identifier is encountered. Will never have a valid location in the source code. + * Pseudo AST node that is used as declaration for "this", "msg", "tx", "block" and the global + * functions when such an identifier is encountered. Will never have a valid location in the source code. */ class MagicVariableDeclaration: public Declaration { public: - enum class VariableKind { THIS, MSG, TX, BLOCK }; - MagicVariableDeclaration(VariableKind _kind, ASTString const& _name, std::shared_ptr const& _type): - Declaration(Location(), std::make_shared(_name)), m_kind(_kind), m_type(_type) {} + MagicVariableDeclaration(ASTString const& _name, std::shared_ptr const& _type): + Declaration(Location(), std::make_shared(_name)), m_type(_type) {} virtual void accept(ASTVisitor&) override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("MagicVariableDeclaration used inside real AST.")); } std::shared_ptr const& getType() const { return m_type; } - VariableKind getKind() const { return m_kind; } private: - VariableKind m_kind; std::shared_ptr m_type; }; diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index f5ab829c3..4dc377791 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -160,6 +160,7 @@ bool ExpressionCompiler::visit(BinaryOperation& _binaryOperation) bool ExpressionCompiler::visit(FunctionCall& _functionCall) { + using Location = FunctionType::Location; if (_functionCall.isTypeConversion()) { //@todo struct construction @@ -184,7 +185,7 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) if (asserts(arguments.size() == function.getParameterTypes().size())) BOOST_THROW_EXCEPTION(InternalCompilerError()); - if (function.getLocation() == FunctionType::Location::INTERNAL) + if (function.getLocation() == Location::INTERNAL) { // Calling convention: Caller pushes return address and arguments // Callee removes them and pushes return values @@ -208,29 +209,57 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) for (unsigned i = 1; i < function.getReturnParameterTypes().size(); ++i) m_context << eth::Instruction::POP; } - else if (function.getLocation() == FunctionType::Location::EXTERNAL) - { + else if (function.getLocation() == Location::EXTERNAL) BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("External function calls not implemented yet.")); - } else { switch (function.getLocation()) { - case FunctionType::Location::SEND: + case Location::SEND: m_context << u256(0) << u256(0) << u256(0) << u256(0); arguments.front()->accept(*this); //@todo might not be necessary - appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front()); + appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); _functionCall.getExpression().accept(*this); m_context << u256(25) << eth::Instruction::GAS << eth::Instruction::SUB << eth::Instruction::CALL << eth::Instruction::POP; break; - case FunctionType::Location::SUICIDE: + case Location::SUICIDE: arguments.front()->accept(*this); //@todo might not be necessary - appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front()); + appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); m_context << eth::Instruction::SUICIDE; + break; + case Location::SHA3: + arguments.front()->accept(*this); + appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); + // @todo move this once we actually use memory + m_context << u256(0) << eth::Instruction::MSTORE << u256(32) << u256(0) << eth::Instruction::SHA3; + break; + case Location::ECRECOVER: + case Location::SHA256: + case Location::RIPEMD160: + { + static const map contractAddresses{{Location::ECRECOVER, 1}, + {Location::SHA256, 2}, + {Location::RIPEMD160, 3}}; + u256 contractAddress = contractAddresses.find(function.getLocation())->second; + // @todo later, combine this code with external function call + for (unsigned i = 0; i < arguments.size(); ++i) + { + arguments[i]->accept(*this); + appendTypeConversion(*arguments[i]->getType(), *function.getParameterTypes()[i], true); + // @todo move this once we actually use memory + m_context << u256(i * 32) << eth::Instruction::MSTORE; + } + m_context << u256(32) << u256(0) << u256(arguments.size() * 32) << u256(0) << u256(0) + << contractAddress << u256(500) //@todo determine actual gas requirement + << eth::Instruction::CALL + << eth::Instruction::POP + << u256(0) << eth::Instruction::MLOAD; + break; + } default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Function not yet implemented.")); } @@ -246,9 +275,15 @@ void ExpressionCompiler::endVisit(MemberAccess& _memberAccess) { case Type::Category::INTEGER: if (member == "balance") + { + appendTypeConversion(*_memberAccess.getExpression().getType(), + IntegerType(0, IntegerType::Modifier::ADDRESS), true); m_context << eth::Instruction::BALANCE; + } else if (member == "send") - { // no modification + { + appendTypeConversion(*_memberAccess.getExpression().getType(), + IntegerType(0, IntegerType::Modifier::ADDRESS), true); } else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); @@ -319,7 +354,7 @@ void ExpressionCompiler::endVisit(Identifier& _identifier) Declaration* declaration = _identifier.getReferencedDeclaration(); if (MagicVariableDeclaration* magicVar = dynamic_cast(declaration)) { - if (magicVar->getKind() == MagicVariableDeclaration::VariableKind::THIS) + if (magicVar->getType()->getCategory() == Type::Category::CONTRACT) // must be "this" m_context << eth::Instruction::ADDRESS; return; } diff --git a/libsolidity/GlobalContext.cpp b/libsolidity/GlobalContext.cpp index 4120dae5e..e958352fd 100644 --- a/libsolidity/GlobalContext.cpp +++ b/libsolidity/GlobalContext.cpp @@ -33,15 +33,33 @@ namespace solidity { GlobalContext::GlobalContext(): - m_magicVariables{make_shared(MagicVariableDeclaration::VariableKind::BLOCK, - "block", - make_shared(MagicType::Kind::BLOCK)), - make_shared(MagicVariableDeclaration::VariableKind::MSG, - "msg", - make_shared(MagicType::Kind::MSG)), - make_shared(MagicVariableDeclaration::VariableKind::TX, - "tx", - make_shared(MagicType::Kind::TX))} + m_magicVariables{make_shared("block", make_shared(MagicType::Kind::BLOCK)), + make_shared("msg", make_shared(MagicType::Kind::MSG)), + make_shared("tx", make_shared(MagicType::Kind::TX)), + make_shared("suicide", + make_shared(TypePointers({std::make_shared(0, + IntegerType::Modifier::ADDRESS)}), + TypePointers(), + FunctionType::Location::SUICIDE)), + make_shared("sha3", + make_shared(TypePointers({std::make_shared(256, IntegerType::Modifier::HASH)}), + TypePointers({std::make_shared(256, IntegerType::Modifier::HASH)}), + FunctionType::Location::SHA3)), + make_shared("sha256", + make_shared(TypePointers({std::make_shared(256, IntegerType::Modifier::HASH)}), + TypePointers({std::make_shared(256, IntegerType::Modifier::HASH)}), + FunctionType::Location::SHA256)), + make_shared("ecrecover", + make_shared(TypePointers({std::make_shared(256, IntegerType::Modifier::HASH), + std::make_shared(8, IntegerType::Modifier::HASH), + std::make_shared(256, IntegerType::Modifier::HASH), + std::make_shared(256, IntegerType::Modifier::HASH)}), + TypePointers({std::make_shared(0, IntegerType::Modifier::ADDRESS)}), + FunctionType::Location::ECRECOVER)), + make_shared("ripemd160", + make_shared(TypePointers({std::make_shared(256, IntegerType::Modifier::HASH)}), + TypePointers({std::make_shared(256, IntegerType::Modifier::HASH)}), + FunctionType::Location::RIPEMD160))} { } @@ -64,7 +82,6 @@ MagicVariableDeclaration*GlobalContext::getCurrentThis() const { if (!m_thisPointer[m_currentContract]) m_thisPointer[m_currentContract] = make_shared( - MagicVariableDeclaration::VariableKind::THIS, "this", make_shared(*m_currentContract)); return m_thisPointer[m_currentContract].get(); diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 630c9c213..b655f9e0d 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -356,7 +356,7 @@ private: class MagicType: public Type { public: - enum class Kind { BLOCK, MSG, TX }; //@todo should be unified with MagicVariableDeclaration::VariableKind; + enum class Kind { BLOCK, MSG, TX }; virtual Category getCategory() const override { return Category::MAGIC; } MagicType(Kind _kind); diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index 80a435d1e..8b25d4031 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -27,12 +27,14 @@ #include #include #include +#include using namespace std; namespace dev { -/// Provider another overload for toBigEndian to encode arguments and return values. +/// Provides additional overloads for toBigEndian to encode arguments and return values. +inline bytes toBigEndian(byte _value) { return bytes({_value}); } inline bytes toBigEndian(bool _value) { return bytes({byte(_value)}); } namespace solidity @@ -809,6 +811,93 @@ BOOST_AUTO_TEST_CASE(send_ether) BOOST_CHECK_EQUAL(m_state.balance(address), amount); } +BOOST_AUTO_TEST_CASE(suicide) +{ + char const* sourceCode = "contract test {\n" + " function a(address receiver) returns (uint ret) {\n" + " suicide(receiver);\n" + " return 10;\n" + " }\n" + "}\n"; + u256 amount(130); + compileAndRun(sourceCode, amount); + u160 address(23); + BOOST_CHECK(callContractFunction(0, address) == bytes()); + BOOST_CHECK(!m_state.addressHasCode(m_contractAddress)); + BOOST_CHECK_EQUAL(m_state.balance(address), amount); +} + +BOOST_AUTO_TEST_CASE(sha3) +{ + char const* sourceCode = "contract test {\n" + " function a(hash input) returns (hash sha3hash) {\n" + " return sha3(input);\n" + " }\n" + "}\n"; + compileAndRun(sourceCode); + auto f = [&](u256 const& _x) -> u256 + { + return dev::sha3(toBigEndian(_x)); + }; + testSolidityAgainstCpp(0, f, u256(4)); + testSolidityAgainstCpp(0, f, u256(5)); + testSolidityAgainstCpp(0, f, u256(-1)); +} + +BOOST_AUTO_TEST_CASE(sha256) +{ + char const* sourceCode = "contract test {\n" + " function a(hash input) returns (hash sha256hash) {\n" + " return sha256(input);\n" + " }\n" + "}\n"; + compileAndRun(sourceCode); + auto f = [&](u256 const& _input) -> u256 + { + h256 ret; + dev::sha256(dev::ref(toBigEndian(_input)), bytesRef(&ret[0], 32)); + return ret; + }; + testSolidityAgainstCpp(0, f, u256(4)); + testSolidityAgainstCpp(0, f, u256(5)); + testSolidityAgainstCpp(0, f, u256(-1)); +} + +BOOST_AUTO_TEST_CASE(ripemd) +{ + char const* sourceCode = "contract test {\n" + " function a(hash input) returns (hash sha256hash) {\n" + " return ripemd160(input);\n" + " }\n" + "}\n"; + compileAndRun(sourceCode); + auto f = [&](u256 const& _input) -> u256 + { + h256 ret; + dev::ripemd160(dev::ref(toBigEndian(_input)), bytesRef(&ret[0], 32)); + return u256(ret) >> (256 - 160); + }; + testSolidityAgainstCpp(0, f, u256(4)); + testSolidityAgainstCpp(0, f, u256(5)); + testSolidityAgainstCpp(0, f, u256(-1)); +} + +BOOST_AUTO_TEST_CASE(ecrecover) +{ + char const* sourceCode = "contract test {\n" + " function a(hash h, uint8 v, hash r, hash s) returns (address addr) {\n" + " return ecrecover(h, v, r, s);\n" + " }\n" + "}\n"; + compileAndRun(sourceCode); + u256 h("0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c"); + byte v = 28; + u256 r("0x73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f"); + u256 s("0xeeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"); + u160 addr("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"); + BOOST_CHECK(callContractFunction(0, h, v, r, s) == toBigEndian(addr)); +} + BOOST_AUTO_TEST_SUITE_END() }