From e8c39062f821598ce9d42b00dddd61403f7fab8e Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 4 Dec 2014 19:38:24 +0100 Subject: [PATCH] Calling functions of other contracts. --- libsolidity/ExpressionCompiler.cpp | 139 ++++++++++++++++++----------- libsolidity/ExpressionCompiler.h | 7 +- libsolidity/Types.cpp | 33 ++++++- libsolidity/Types.h | 9 +- test/solidityEndToEndTest.cpp | 135 +++++++++++++++++++++++++++- 5 files changed, 259 insertions(+), 64 deletions(-) diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index c3c7116e4..352b0e6d8 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -185,7 +185,9 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) if (asserts(arguments.size() == function.getParameterTypes().size())) BOOST_THROW_EXCEPTION(InternalCompilerError()); - if (function.getLocation() == Location::INTERNAL) + switch (function.getLocation()) + { + case Location::INTERNAL: { // Calling convention: Caller pushes return address and arguments // Callee removes them and pushes return values @@ -208,61 +210,90 @@ bool ExpressionCompiler::visit(FunctionCall& _functionCall) // all others for (unsigned i = 1; i < function.getReturnParameterTypes().size(); ++i) m_context << eth::Instruction::POP; + break; } - else if (function.getLocation() == Location::EXTERNAL) - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("External function calls not implemented yet.")); - else + case Location::EXTERNAL: { - switch (function.getLocation()) - { - 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(), true); - _functionCall.getExpression().accept(*this); - m_context << u256(25) << eth::Instruction::GAS << eth::Instruction::SUB - << eth::Instruction::CALL - << eth::Instruction::POP; - break; - case Location::SUICIDE: - arguments.front()->accept(*this); - //@todo might not be necessary - 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: + unsigned dataOffset = 1; // reserve one byte for the function index + for (unsigned i = 0; i < arguments.size(); ++i) { - 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; + arguments[i]->accept(*this); + Type const& type = *function.getParameterTypes()[i]; + appendTypeConversion(*arguments[i]->getType(), type); + unsigned const numBytes = type.getCalldataEncodedSize(); + if (numBytes == 0 || numBytes > 32) + BOOST_THROW_EXCEPTION(CompilerError() + << errinfo_sourceLocation(arguments[i]->getLocation()) + << errinfo_comment("Type " + type.toString() + " not yet supported.")); + if (numBytes != 32) + m_context << (u256(1) << ((32 - numBytes) * 8)) << eth::Instruction::MUL; + m_context << u256(dataOffset) << eth::Instruction::MSTORE; + dataOffset += numBytes; } - default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Function not yet implemented.")); + //@todo only return the first return value for now + unsigned retSize = function.getReturnParameterTypes().empty() ? 0 + : function.getReturnParameterTypes().front()->getCalldataEncodedSize(); + // CALL arguments: outSize, outOff, inSize, inOff, value, addr, gas (stack top) + m_context << u256(retSize) << u256(0) << u256(dataOffset) << u256(0) << u256(0); + _functionCall.getExpression().accept(*this); // pushes addr and function index + m_context << u256(0) << eth::Instruction::MSTORE8 + << u256(25) << eth::Instruction::GAS << eth::Instruction::SUB + << eth::Instruction::CALL + << eth::Instruction::POP; // @todo do not ignore failure indicator + if (retSize == 32) + m_context << u256(0) << eth::Instruction::MLOAD; + else if (retSize > 0) + m_context << (u256(1) << ((32 - retSize) * 8)) + << u256(0) << eth::Instruction::MLOAD << eth::Instruction::DIV; + break; + } + 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(), true); + _functionCall.getExpression().accept(*this); + m_context << u256(25) << eth::Instruction::GAS << eth::Instruction::SUB + << eth::Instruction::CALL + << eth::Instruction::POP; + break; + case Location::SUICIDE: + arguments.front()->accept(*this); + //@todo might not be necessary + 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("Invalid function type.")); } } return false; @@ -289,9 +320,11 @@ void ExpressionCompiler::endVisit(MemberAccess& _memberAccess) BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); break; case Type::Category::CONTRACT: - // call function - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Contract variables not yet implemented.")); + { + ContractType const& type = dynamic_cast(*_memberAccess.getExpression().getType()); + m_context << type.getFunctionIndex(member); break; + } case Type::Category::MAGIC: // we can ignore the kind of magic and only look at the name of the member if (member == "coinbase") diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 83d7cdc6c..fbecbdc8e 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -31,9 +31,10 @@ class AssemblyItem; // forward } namespace solidity { -class CompilerContext; // forward -class Type; // forward -class IntegerType; // forward +// forward declarations +class CompilerContext; +class Type; +class IntegerType; /** * Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 7e07b1162..3829016f5 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -140,7 +140,7 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - return _convertTo.getCategory() == getCategory(); + return _convertTo.getCategory() == getCategory() || _convertTo.getCategory() == Category::CONTRACT; } bool IntegerType::acceptsBinaryOperator(Token::Value _operator) const @@ -247,6 +247,31 @@ string ContractType::toString() const return "contract " + m_contract.getName(); } +MemberList const& ContractType::getMembers() const +{ + // We need to lazy-initialize it because of recursive references. + if (!m_members) + { + map> members; + for (FunctionDefinition const* function: m_contract.getInterfaceFunctions()) + members[function->getName()] = make_shared(*function, false); + m_members.reset(new MemberList(members)); + } + return *m_members; +} + +unsigned ContractType::getFunctionIndex(string const& _functionName) const +{ + unsigned index = 0; + for (FunctionDefinition const* function: m_contract.getInterfaceFunctions()) + { + if (function->getName() == _functionName) + return index; + ++index; + } + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Index of non-existing contract function requested.")); +} + bool StructType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -302,7 +327,7 @@ 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) +FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal) { TypePointers params; TypePointers retParams; @@ -314,7 +339,7 @@ FunctionType::FunctionType(FunctionDefinition const& _function) retParams.push_back(var->getType()); swap(params, m_parameterTypes); swap(retParams, m_returnParameterTypes); - m_location = Location::INTERNAL; + m_location = _isInternal ? Location::INTERNAL : Location::EXTERNAL; } bool FunctionType::operator==(Type const& _other) const @@ -323,6 +348,8 @@ bool FunctionType::operator==(Type const& _other) const return false; FunctionType const& other = dynamic_cast(_other); + if (m_location != other.m_location) + return false; if (m_parameterTypes.size() != other.m_parameterTypes.size() || m_returnParameterTypes.size() != other.m_returnParameterTypes.size()) return false; diff --git a/libsolidity/Types.h b/libsolidity/Types.h index b655f9e0d..8e2f4803b 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -214,10 +214,17 @@ public: virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool operator==(Type const& _other) const override; virtual u256 getStorageSize() const override; + virtual bool isValueType() const override { return true; } virtual std::string toString() const override; + virtual MemberList const& getMembers() const override; + + unsigned getFunctionIndex(std::string const& _functionName) const; + private: ContractDefinition const& m_contract; + /// List of member types, will be lazy-initialized because of recursive references. + mutable std::unique_ptr m_members; }; /** @@ -263,7 +270,7 @@ public: enum class Location { INTERNAL, EXTERNAL, SEND, SHA3, SUICIDE, ECRECOVER, SHA256, RIPEMD160 }; virtual Category getCategory() const override { return Category::FUNCTION; } - explicit FunctionType(FunctionDefinition const& _function); + explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); FunctionType(TypePointers const& _parameterTypes, TypePointers const& _returnParameterTypes, Location _location = Location::INTERNAL): m_parameterTypes(_parameterTypes), m_returnParameterTypes(_returnParameterTypes), diff --git a/test/solidityEndToEndTest.cpp b/test/solidityEndToEndTest.cpp index 8b25d4031..9e02438e8 100644 --- a/test/solidityEndToEndTest.cpp +++ b/test/solidityEndToEndTest.cpp @@ -47,9 +47,11 @@ class ExecutionFramework public: ExecutionFramework() { g_logVerbosity = 0; } - bytes const& compileAndRun(string const& _sourceCode, u256 const& _value = 0) + bytes const& compileAndRun(string const& _sourceCode, u256 const& _value = 0, string const& _contractName = "") { - bytes code = dev::solidity::CompilerStack::staticCompile(_sourceCode); + dev::solidity::CompilerStack compiler; + compiler.compile(_sourceCode); + bytes code = compiler.getBytecode(_contractName); sendMessage(code, true, _value); BOOST_REQUIRE(!m_output.empty()); return m_output; @@ -115,6 +117,7 @@ private: void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0) { + m_state.addBalance(m_sender, _value); // just in case eth::Executive executive(m_state); eth::Transaction t = _isCreation ? eth::Transaction(_value, m_gasPrice, m_gas, _data, 0, KeyPair::create().sec()) : eth::Transaction(_value, m_gasPrice, m_gas, m_contractAddress, _data, 0, KeyPair::create().sec()); @@ -127,7 +130,7 @@ private: catch (...) {} if (_isCreation) { - BOOST_REQUIRE(!executive.create(Address(), _value, m_gasPrice, m_gas, &_data, Address())); + BOOST_REQUIRE(!executive.create(m_sender, _value, m_gasPrice, m_gas, &_data, m_sender)); m_contractAddress = executive.newAddress(); BOOST_REQUIRE(m_contractAddress); BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); @@ -135,14 +138,16 @@ private: else { BOOST_REQUIRE(m_state.addressHasCode(m_contractAddress)); - BOOST_REQUIRE(!executive.call(m_contractAddress, Address(), _value, m_gasPrice, &_data, m_gas, Address())); + BOOST_REQUIRE(!executive.call(m_contractAddress, m_sender, _value, m_gasPrice, &_data, m_gas, m_sender)); } BOOST_REQUIRE(executive.go()); + m_state.noteSending(m_sender); executive.finalize(); m_output = executive.out().toVector(); } protected: + Address m_sender; Address m_contractAddress; eth::State m_state; u256 const m_gasPrice = 100 * eth::szabo; @@ -898,6 +903,128 @@ BOOST_AUTO_TEST_CASE(ecrecover) BOOST_CHECK(callContractFunction(0, h, v, r, s) == toBigEndian(addr)); } +BOOST_AUTO_TEST_CASE(inter_contract_calls) +{ + char const* sourceCode = R"( + contract Helper { + function multiply(uint a, uint b) returns (uint c) { + return a * b; + } + } + contract Main { + Helper h; + function callHelper(uint a, uint b) returns (uint c) { + return h.multiply(a, b); + } + function getHelper() returns (address haddress) { + return address(h); + } + function setHelper(address haddress) { + h = Helper(haddress); + } + })"; + compileAndRun(sourceCode, 0, "Helper"); + u160 const helperAddress = m_contractAddress; + compileAndRun(sourceCode, 0, "Main"); + BOOST_REQUIRE(callContractFunction(2, helperAddress) == bytes()); + BOOST_REQUIRE(callContractFunction(1, helperAddress) == toBigEndian(helperAddress)); + u256 a(3456789); + u256 b("0x282837623374623234aa74"); + BOOST_REQUIRE(callContractFunction(0, a, b) == toBigEndian(a * b)); +} + +BOOST_AUTO_TEST_CASE(inter_contract_calls_with_complex_parameters) +{ + char const* sourceCode = R"( + contract Helper { + function sel(uint a, bool select, uint b) returns (uint c) { + if (select) return a; else return b; + } + } + contract Main { + Helper h; + function callHelper(uint a, bool select, uint b) returns (uint c) { + return h.sel(a, select, b) * 3; + } + function getHelper() returns (address haddress) { + return address(h); + } + function setHelper(address haddress) { + h = Helper(haddress); + } + })"; + compileAndRun(sourceCode, 0, "Helper"); + u160 const helperAddress = m_contractAddress; + compileAndRun(sourceCode, 0, "Main"); + BOOST_REQUIRE(callContractFunction(2, helperAddress) == bytes()); + BOOST_REQUIRE(callContractFunction(1, helperAddress) == toBigEndian(helperAddress)); + u256 a(3456789); + u256 b("0x282837623374623234aa74"); + BOOST_REQUIRE(callContractFunction(0, a, true, b) == toBigEndian(a * 3)); + BOOST_REQUIRE(callContractFunction(0, a, false, b) == toBigEndian(b * 3)); +} + +BOOST_AUTO_TEST_CASE(inter_contract_calls_accessing_this) +{ + char const* sourceCode = R"( + contract Helper { + function getAddress() returns (address addr) { + return address(this); + } + } + contract Main { + Helper h; + function callHelper() returns (address addr) { + return h.getAddress(); + } + function getHelper() returns (address addr) { + return address(h); + } + function setHelper(address addr) { + h = Helper(addr); + } + })"; + compileAndRun(sourceCode, 0, "Helper"); + u160 const helperAddress = m_contractAddress; + compileAndRun(sourceCode, 0, "Main"); + BOOST_REQUIRE(callContractFunction(2, helperAddress) == bytes()); + BOOST_REQUIRE(callContractFunction(1, helperAddress) == toBigEndian(helperAddress)); + BOOST_REQUIRE(callContractFunction(0) == toBigEndian(helperAddress)); +} + +BOOST_AUTO_TEST_CASE(calls_to_this) +{ + char const* sourceCode = R"( + contract Helper { + function invoke(uint a, uint b) returns (uint c) { + return this.multiply(a, b, 10); + } + function multiply(uint a, uint b, uint8 c) returns (uint ret) { + return a * b + c; + } + } + contract Main { + Helper h; + function callHelper(uint a, uint b) returns (uint ret) { + return h.invoke(a, b); + } + function getHelper() returns (address addr) { + return address(h); + } + function setHelper(address addr) { + h = Helper(addr); + } + })"; + compileAndRun(sourceCode, 0, "Helper"); + u160 const helperAddress = m_contractAddress; + compileAndRun(sourceCode, 0, "Main"); + BOOST_REQUIRE(callContractFunction(2, helperAddress) == bytes()); + BOOST_REQUIRE(callContractFunction(1, helperAddress) == toBigEndian(helperAddress)); + u256 a(3456789); + u256 b("0x282837623374623234aa74"); + BOOST_REQUIRE(callContractFunction(0, a, b) == toBigEndian(a * b + 10)); +} + BOOST_AUTO_TEST_SUITE_END() }