Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to output storage layout info for a contract #4017

Closed
wants to merge 9 commits into from
3 changes: 3 additions & 0 deletions libsolidity/codegen/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Declaration const*, std::pair<u256, unsigned>> const& stateVariables() const { return m_context.stateVariables(); }

private:
bool const m_optimize;
unsigned const m_optimizeRuns;
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/codegen/CompilerContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<u256, unsigned> 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<Declaration const*, std::pair<u256, unsigned>> const& stateVariables() const { return m_stateVariables; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to go through ContractType instead of the compiler context.


/// Appends a JUMPI instruction to a new tag and @returns the tag
eth::AssemblyItem appendConditionalJump() { return m_asm->appendJumpI().tag(); }
Expand Down
19 changes: 19 additions & 0 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <libsolidity/codegen/Compiler.h>
#include <libsolidity/formal/SMTChecker.h>
#include <libsolidity/interface/ABI.h>
#include <libsolidity/interface/StorageInfo.h>
#include <libsolidity/interface/Natspec.h>
#include <libsolidity/interface/GasEstimator.h>

Expand Down Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -262,6 +266,7 @@ class CompilerStack: boost::noncopyable
mutable std::unique_ptr<Json::Value const> abi;
mutable std::unique_ptr<Json::Value const> userDocumentation;
mutable std::unique_ptr<Json::Value const> devDocumentation;
mutable std::unique_ptr<Json::Value const> storageInfo;
mutable std::unique_ptr<std::string const> sourceMapping;
mutable std::unique_ptr<std::string const> runtimeSourceMapping;
};
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
120 changes: 120 additions & 0 deletions libsolidity/interface/StorageInfo.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @author Santiago Palladino <[email protected]>
* @date 2018
* Outputs contract storage layout information
*/

#include <libsolidity/interface/StorageInfo.h>
#include <libsolidity/codegen/Compiler.h>
#include <libsolidity/ast/AST.h>

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<VariableDeclaration const*>(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<u256, unsigned> 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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps better to call this storageSlots or numberOfSlots?


// 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<const StructType>(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<const MappingType>(type);
data["key"] = processType(map->keyType());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key type should be processed differently

data["value"] = processType(map->valueType());
}
else if (type->category() == Type::Category::Array) {
auto array = static_pointer_cast<const ArrayType>(type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Big difference between statically-sized and dynamically-sized arrays

if (!array->isByteArray())
{
data["base"] = processType(array->baseType());
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs assertion that there is no other category.

return data;
}
52 changes: 52 additions & 0 deletions libsolidity/interface/StorageInfo.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @author Santiago Palladino <[email protected]>
* @date 2018
* Outputs contract storage layout information
*/

#pragma once

#include <string>
#include <memory>
#include <json/json.h>
#include <libsolidity/codegen/Compiler.h>
#include <libsolidity/ast/Types.h>

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<u256, unsigned> const& location);
};
}
}
6 changes: 5 additions & 1 deletion solc/CommandLineInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -172,7 +173,8 @@ static set<string> const g_combinedJsonArgs
g_strOpcodes,
g_strSignatureHashes,
g_strSrcMap,
g_strSrcMapRuntime
g_strSrcMapRuntime,
g_strStorageInfo
};

/// Possible arguments to for --machine
Expand Down Expand Up @@ -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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd hope not to support it on the commandline, rather expect people to use it through standard json IO.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I remove it from the command line? It only affects the combined-json.

}

bool needsSourceList = requests.count(g_strAst) || requests.count(g_strSrcMap) || requests.count(g_strSrcMapRuntime);
Expand Down
Loading