diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index f6865d757c1f..2a0e1e918bf9 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -81,6 +81,9 @@ class Compiler /// UndefinedItem if it does not exist yet. eth::AssemblyItem functionEntryLabel(FunctionDefinition const& _function) const; + /// @returns all state variables along with their storage slot and byte offset of the value inside the slot. + std::map> const& stateVariables() const { return m_context.stateVariables(); } + private: bool const m_optimize; unsigned const m_optimizeRuns; diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 098472f7b178..47d1c8489385 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -141,6 +141,8 @@ class CompilerContext unsigned currentToBaseStackOffset(unsigned _offset) const; /// @returns pair of slot and byte offset of the value inside this slot. std::pair storageLocationOfVariable(Declaration const& _declaration) const; + /// @returns all state variables along with their storage slot and byte offset of the value inside the slot. + std::map> const& stateVariables() const { return m_stateVariables; } /// Appends a JUMPI instruction to a new tag and @returns the tag eth::AssemblyItem appendConditionalJump() { return m_asm->appendJumpI().tag(); } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 4ff14aa27d38..113e5231a8e0 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -477,6 +478,24 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const return *_contract.devDocumentation; } +Json::Value const& CompilerStack::storageInfo(string const& _contractName) const +{ + return storageInfo(contract(_contractName)); +} + +Json::Value const& CompilerStack::storageInfo(Contract const& _contract) const +{ + if (m_stackState < CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + + solAssert(_contract.contract, ""); + + // caches the result + if (!_contract.storageInfo) + _contract.storageInfo.reset(new Json::Value(StorageInfo::generate(_contract.compiler.get()))); + return *_contract.storageInfo; +} + Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const { Json::Value methodIdentifiers(Json::objectValue); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 13c9cc7a1a49..58ac8691d633 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -239,6 +239,10 @@ class CompilerStack: boost::noncopyable /// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions Json::Value gasEstimates(std::string const& _contractName) const; + /// @returns a JSON representing the internal storage layout of the contract. + /// Prerequisite: Successful call to compile. + Json::Value const& storageInfo(std::string const& _contractName) const; + private: /** * Information pertaining to one source unit, filled gradually during parsing and compilation. @@ -262,6 +266,7 @@ class CompilerStack: boost::noncopyable mutable std::unique_ptr abi; mutable std::unique_ptr userDocumentation; mutable std::unique_ptr devDocumentation; + mutable std::unique_ptr storageInfo; mutable std::unique_ptr sourceMapping; mutable std::unique_ptr runtimeSourceMapping; }; @@ -299,6 +304,7 @@ class CompilerStack: boost::noncopyable Json::Value const& contractABI(Contract const&) const; Json::Value const& natspecUser(Contract const&) const; Json::Value const& natspecDev(Contract const&) const; + Json::Value const& storageInfo(Contract const&) const; /// @returns the offset of the entry point of the given function into the list of assembly items /// or zero if it is not found or does not exist. diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index ee9b14406e77..d4619dc8f223 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -510,7 +510,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) string file = contractName.substr(0, colon); string name = contractName.substr(colon + 1); - // ABI, documentation and metadata + // ABI, documentation, storage and metadata Json::Value contractData(Json::objectValue); if (isArtifactRequested(outputSelection, file, name, "abi")) contractData["abi"] = m_compilerStack.contractABI(contractName); @@ -520,6 +520,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) contractData["userdoc"] = m_compilerStack.natspecUser(contractName); if (isArtifactRequested(outputSelection, file, name, "devdoc")) contractData["devdoc"] = m_compilerStack.natspecDev(contractName); + if (isArtifactRequested(outputSelection, file, name, "storageLayoutMap")) + contractData["storageLayoutMap"] = m_compilerStack.storageInfo(contractName); // EVM Json::Value evmData(Json::objectValue); diff --git a/libsolidity/interface/StorageInfo.cpp b/libsolidity/interface/StorageInfo.cpp new file mode 100644 index 000000000000..469c4d8dfdb3 --- /dev/null +++ b/libsolidity/interface/StorageInfo.cpp @@ -0,0 +1,120 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author Santiago Palladino + * @date 2018 + * Outputs contract storage layout information + */ + +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +Json::Value StorageInfo::generate(Compiler const* _compiler) +{ + Json::Value storage(Json::arrayValue); + + if(_compiler == NULL) + { + return storage; + } + + for (auto it: _compiler->stateVariables()) + { + if (auto decl = dynamic_cast(it.first)) + { + auto location = it.second; + auto member = MemberList::Member(decl->name(), decl->type(), decl); + auto memberData = processMember(member, location); + + // Assume that the parent scope of a state variable is a contract + auto parent = ((Declaration*)decl->scope()); + if (parent != NULL) + { + memberData["contract"] = parent->name(); + } + + storage.append(memberData); + } + } + + return storage; +} + + +Json::Value StorageInfo::processMember(MemberList::Member const& member, pair const& location) +{ + Json::Value data; + + data["name"] = member.name; + data["slot"] = location.first.str(); + data["offset"] = to_string(location.second); + data["type"] = processType(member.type); + + return data; +} + + +Json::Value StorageInfo::processType(TypePointer const& type) +{ + Json::Value data; + + // Common type info + data["name"] = type->canonicalName(); + data["size"] = type->storageSize().str(); + + // Only include storageBytes if storageSize is 1, otherwise it always returns 32 + if (type->storageSize() == 1) + { + data["bytes"] = to_string(type->storageBytes()); + } + + // Recursively visit complex types (structs, mappings, and arrays) + if (type->category() == Type::Category::Struct) + { + auto childStruct = static_pointer_cast(type); + if (!childStruct->recursive()) + { + Json::Value members(Json::arrayValue); + for(auto member: childStruct->members(nullptr)) + { + auto offsets = childStruct->storageOffsetsOfMember(member.name); + auto memberData = processMember(member, offsets); + members.append(memberData); + } + data["members"] = members; + } + } + else if (type->category() == Type::Category::Mapping) { + auto map = static_pointer_cast(type); + data["key"] = processType(map->keyType()); + data["value"] = processType(map->valueType()); + } + else if (type->category() == Type::Category::Array) { + auto array = static_pointer_cast(type); + if (!array->isByteArray()) + { + data["base"] = processType(array->baseType()); + } + } + + return data; +} \ No newline at end of file diff --git a/libsolidity/interface/StorageInfo.h b/libsolidity/interface/StorageInfo.h new file mode 100644 index 000000000000..198ee53e8e32 --- /dev/null +++ b/libsolidity/interface/StorageInfo.h @@ -0,0 +1,52 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author Santiago Palladino + * @date 2018 + * Outputs contract storage layout information + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +// Forward declarations +class Compiler; + +class StorageInfo +{ +public: + /// Get the storage layout of a contract + /// @param _compiler The compiler used for the contract + /// @return A JSON representation of the contract's storage layout + static Json::Value generate(Compiler const* _compiler); + +private: + static Json::Value processType(TypePointer const& type); + static Json::Value processMember(MemberList::Member const& member, std::pair const& location); +}; +} +} diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index bd5e2eb1b495..a0c7ce06e664 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -112,6 +112,7 @@ static string const g_strSources = "sources"; static string const g_strSourceList = "sourceList"; static string const g_strSrcMap = "srcmap"; static string const g_strSrcMapRuntime = "srcmap-runtime"; +static string const g_strStorageInfo = "storage-layout"; static string const g_strStandardJSON = "standard-json"; static string const g_strStrictAssembly = "strict-assembly"; static string const g_strPrettyJson = "pretty-json"; @@ -172,7 +173,8 @@ static set const g_combinedJsonArgs g_strOpcodes, g_strSignatureHashes, g_strSrcMap, - g_strSrcMapRuntime + g_strSrcMapRuntime, + g_strStorageInfo }; /// Possible arguments to for --machine @@ -932,6 +934,8 @@ void CommandLineInterface::handleCombinedJSON() contractData[g_strNatspecDev] = dev::jsonCompactPrint(m_compiler->natspecDev(contractName)); if (requests.count(g_strNatspecUser)) contractData[g_strNatspecUser] = dev::jsonCompactPrint(m_compiler->natspecUser(contractName)); + if (requests.count(g_strStorageInfo)) + contractData[g_strStorageInfo] = dev::jsonCompactPrint(m_compiler->storageInfo(contractName)); } bool needsSourceList = requests.count(g_strAst) || requests.count(g_strSrcMap) || requests.count(g_strSrcMapRuntime); diff --git a/test/libsolidity/SolidityStorageInfoJson.cpp b/test/libsolidity/SolidityStorageInfoJson.cpp new file mode 100644 index 000000000000..d19b13ea7dc8 --- /dev/null +++ b/test/libsolidity/SolidityStorageInfoJson.cpp @@ -0,0 +1,474 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . + */ + +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +class JSONStorageChecker +{ +public: + JSONStorageChecker(): m_compilerStack() {} + + void checkInterface(std::string const& _code, std::string const& _expectedStorageString) + { + m_compilerStack.reset(false); + m_compilerStack.addSource("", "pragma solidity >=0.0;\n" + _code); + m_compilerStack.setEVMVersion(dev::test::Options::get().evmVersion()); + m_compilerStack.setOptimiserSettings(dev::test::Options::get().optimize); + BOOST_REQUIRE_MESSAGE(m_compilerStack.compile(), "Compiling contract failed"); + + Json::Value generatedStorage = m_compilerStack.storageInfo(m_compilerStack.lastContractName()); + Json::Value expectedStorage; + BOOST_REQUIRE(jsonParseStrict(_expectedStorageString, expectedStorage)); + + // Sort both expected and generated json arrays before comparison, as we don't care about the order + std::vector generated(generatedStorage.begin(), generatedStorage.end()); + std::vector expected(expectedStorage.begin(), expectedStorage.end()); + std::sort(generated.begin(), generated.end()); + std::sort(expected.begin(), expected.end()); + + BOOST_CHECK_MESSAGE( + expected == generated, + "Expected:\n" << expectedStorage.toStyledString() << + "\n but got:\n" << generatedStorage.toStyledString() + ); + } + +protected: + CompilerStack m_compilerStack; +}; + +BOOST_FIXTURE_TEST_SUITE(SolidityStorageInfoJSON, JSONStorageChecker) + +BOOST_AUTO_TEST_CASE(single_var_test) +{ + char const* sourceCode = R"( + contract test { + uint a; + } + )"; + + char const* interface = R"([ + { "name": "a", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "uint256", "size": "1", "bytes": "32" } + } + ])"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(multiple_var_test) +{ + char const* sourceCode = R"( + contract test { + uint a; + uint b; + uint c; + } + )"; + + char const* interface = R"([ + { "name": "a", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "b", "contract": "test", "offset": "0", "slot": "1", + "type": { "name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "c", "contract": "test", "offset": "0", "slot": "2", + "type": { "name": "uint256", "size": "1", "bytes": "32" } } + ])"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(packing_test) +{ + char const* sourceCode = R"( + contract test { + uint64 a; + uint64 b; + uint128 c; + uint256 d; + } + )"; + + char const* interface = R"([ + { "name": "a", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "uint64", "size": "1", "bytes": "8" } }, + { "name": "b", "contract": "test", "offset": "8", "slot": "0", + "type": { "name": "uint64", "size": "1", "bytes": "8" } }, + { "name": "c", "contract": "test", "offset": "16", "slot": "0", + "type": { "name": "uint128", "size": "1", "bytes": "16" } }, + { "name": "d", "contract": "test", "offset": "0", "slot": "1", + "type": { "name": "uint256", "size": "1", "bytes": "32" } } + ])"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(bool_test) +{ + char const* sourceCode = R"( + contract test { + bool a; + bool b; + } + )"; + + char const* interface = R"([ + { "name": "a", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "bool", "size": "1", "bytes": "1" } }, + { "name": "b", "contract": "test", "offset": "1", "slot": "0", + "type": { "name": "bool", "size": "1", "bytes": "1" } } + ])"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(string_test) +{ + char const* sourceCode = R"( + contract test { + string a; + string b; + } + )"; + + char const* interface = R"([ + { "name": "a", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "string", "size": "1", "bytes": "32" } }, + { "name": "b", "contract": "test", "offset": "0", "slot": "1", + "type": { "name": "string", "size": "1", "bytes": "32" } } + ])"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(array_test) +{ + char const* sourceCode = R"( + contract test { + uint[10] arr; + } + )"; + + char const* interface = R"JSON([ + { "name": "arr", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "uint256[10]", "size": "10", + "base": { "name": "uint256", "size": "1", "bytes": "32" } } } + ])JSON"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(dynamic_array_test) +{ + char const* sourceCode = R"( + contract test { + uint[] arr; + } + )"; + + char const* interface = R"JSON([ + { "name": "arr", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "uint256[]", "size": "1", "bytes": "32", + "base": { "name": "uint256", "size": "1", "bytes": "32" } } } + ])JSON"; + + checkInterface(sourceCode, interface); +} + + +BOOST_AUTO_TEST_CASE(mapping_test) +{ + char const* sourceCode = R"( + contract test { + mapping(uint => uint128) m; + } + )"; + + char const* interface = R"JSON([ + { "name": "m", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "mapping(uint256 => uint128)", "size": "1", "bytes": "32", + "key": { "name": "uint256", "size": "1", "bytes": "32" }, + "value": { "name": "uint128", "size": "1", "bytes": "16" } } } + ])JSON"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(struct_test) +{ + char const* sourceCode = R"( + contract test { + struct foo { + uint a; + uint b; + } + foo f; + } + )"; + + char const* interface = R"JSON([ + { "name": "f", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "test.foo", "size": "2", + "members": [ + { "name": "a", "offset": "0", "slot": "0", + "type": {"name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "b", "offset": "0", "slot": "1", + "type": {"name": "uint256", "size": "1", "bytes": "32" } } ] } } + ])JSON"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(nested_struct_test) +{ + char const* sourceCode = R"( + contract test { + struct bar { + uint a; + uint b; + } + struct foo { + bar a; + uint b; + } + foo f; + } + )"; + + char const* interface = R"JSON([ + { "name": "f", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "test.foo", "size": "3", + "members": [ + { "name": "a", "offset": "0", "slot": "0", + "type": {"name": "test.bar", "size": "2", + "members": [ + { "name": "a", "offset": "0", "slot": "0", + "type": {"name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "b", "offset": "0", "slot": "1", + "type": {"name": "uint256", "size": "1", "bytes": "32" } } ] } }, + { "name": "b", "offset": "0", "slot": "2", + "type": {"name": "uint256", "size": "1", "bytes": "32" } } ] } } + ])JSON"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(deeply_nested_struct_test) +{ + char const* sourceCode = R"( + contract test { + struct grandchild { + uint a; + uint b; + } + struct child { + grandchild a; + uint b; + } + struct foo { + child a; + uint b; + } + foo f; + } + )"; + + char const* interface = R"JSON([ + { "name": "f", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "test.foo", "size": "4", + "members": [ + { "name": "a", "offset": "0", "slot": "0", + "type": { "name": "test.child", "size": "3", + "members": [ + { "name": "a", "offset": "0", "slot": "0", + "type": { "name": "test.grandchild", "size": "2", + "members": [ + { "name": "a", "offset": "0", "slot": "0", + "type": {"name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "b", "offset": "0", "slot": "1", + "type": {"name": "uint256", "size": "1", "bytes": "32" } } ] } }, + { "name": "b", "offset": "0", "slot": "2", + "type": {"name": "uint256", "size": "1", "bytes": "32" } } ] } }, + { "name": "b", "offset": "0", "slot": "3", + "type": {"name": "uint256", "size": "1", "bytes": "32" } } ] } } + ])JSON"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(recursive_struct_test) +{ + char const* sourceCode = R"( + contract test { + struct Foo { + Bar[] bars; + } + struct Bar { + Foo foo; + } + Bar b; + } + )"; + + // Recursive structs are not visited + char const* interface = R"JSON([ + { "name": "b", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "test.Bar", "size": "1", "bytes": "32" } } + ])JSON"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(dynamic_array_of_struct_test) +{ + char const* sourceCode = R"( + contract test { + struct foo { + uint a; + uint b; + } + foo[] foos; + } + )"; + + char const* interface = R"JSON([ + { "name": "foos", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "test.foo[]", "size": "1", "bytes": "32", + "base": { "name": "test.foo", "size": "2", + "members": [ + { "name": "a", "offset": "0", "slot": "0", + "type": {"name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "b", "offset": "0", "slot": "1", + "type": {"name": "uint256", "size": "1", "bytes": "32" } } ] } } } + ])JSON"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(mapping_of_struct_test) +{ + char const* sourceCode = R"( + contract test { + struct foo { + uint a; + uint b; + } + mapping(string => foo) foos; + } + )"; + + char const* interface = R"JSON([ + { "name": "foos", "contract": "test", "offset": "0", "slot": "0", + "type": { "name": "mapping(string => test.foo)", "size": "1", "bytes": "32", + "key": { "name": "string", "size": "1", "bytes": "32" }, + "value": { "name": "test.foo", "size": "2", + "members": [ + { "name": "a", "offset": "0", "slot": "0", + "type": {"name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "b", "offset": "0", "slot": "1", + "type": {"name": "uint256", "size": "1", "bytes": "32" } } ] } } } + ])JSON"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(inheritance_test) +{ + char const* sourceCode = R"( + contract base { + uint a; + } + + contract test is base { + uint b; + } + )"; + + char const* interface = R"([ + { "name": "a", "contract": "base", "offset": "0", "slot": "0", + "type": { "name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "b", "contract": "test", "offset": "0", "slot": "1", + "type": { "name": "uint256", "size": "1", "bytes": "32" } } + ])"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(multiple_inheritance_test) +{ + char const* sourceCode = R"( + contract base { + uint b; + } + + contract child1 is base { + uint c1; + } + + contract child2 is base { + uint c2; + } + + contract test is child1, child2 { + uint t; + } + )"; + + char const* interface = R"([ + { "name": "b", "contract": "base", "offset": "0", "slot": "0", + "type": { "name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "c1", "contract": "child1", "offset": "0", "slot": "1", + "type": { "name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "c2", "contract": "child2", "offset": "0", "slot": "2", + "type": { "name": "uint256", "size": "1", "bytes": "32" } }, + { "name": "t", "contract": "test", "offset": "0", "slot": "3", + "type": { "name": "uint256", "size": "1", "bytes": "32" } } + ])"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_CASE(abstract_contract_test) +{ + char const* sourceCode = R"( + contract test { + uint a; + function f() internal; + } + )"; + + char const* interface = R"([ + ])"; + + checkInterface(sourceCode, interface); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +}