diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 2764eb7b1..500ae9b05 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -35,6 +35,56 @@ namespace dev namespace solidity { +std::pair const* MemberList::getMemberStorageOffset(string const& _name) const +{ + if (!m_storageOffsets) + { + bigint slotOffset = 0; + unsigned byteOffset = 0; + map> offsets; + for (auto const& nameAndType: m_memberTypes) + { + TypePointer const& type = nameAndType.second; + if (!type->canBeStored()) + continue; + if (byteOffset + type->getStorageBytes() > 32) + { + // would overflow, go to next slot + ++slotOffset; + byteOffset = 0; + } + if (slotOffset >= bigint(1) << 256) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage.")); + offsets[nameAndType.first] = make_pair(u256(slotOffset), byteOffset); + solAssert(type->getStorageSize() >= 1, "Invalid storage size."); + if (type->getStorageSize() == 1 && byteOffset + type->getStorageBytes() <= 32) + byteOffset += type->getStorageBytes(); + else + { + slotOffset += type->getStorageSize(); + byteOffset = 0; + } + } + if (byteOffset > 0) + ++slotOffset; + if (slotOffset >= bigint(1) << 256) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage.")); + m_storageSize = u256(slotOffset); + m_storageOffsets.reset(new decltype(offsets)(move(offsets))); + } + if (m_storageOffsets->count(_name)) + return &((*m_storageOffsets)[_name]); + else + return nullptr; +} + +u256 const& MemberList::getStorageSize() const +{ + // trigger lazy computation + getMemberStorageOffset(""); + return m_storageSize; +} + TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) { char const* tokenCstr = Token::toString(_typeToken); @@ -751,12 +801,7 @@ bool StructType::operator==(Type const& _other) const u256 StructType::getStorageSize() const { - bigint size = 0; - for (pair const& member: getMembers()) - size += member.second->getStorageSize(); - if (size >= bigint(1) << 256) - BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Struct too large for storage.")); - return max(1, u256(size)); + return max(1, getMembers().getStorageSize()); } bool StructType::canLiveOutsideStorage() const @@ -787,6 +832,7 @@ MemberList const& StructType::getMembers() const u256 StructType::getStorageOffsetOfMember(string const& _name) const { + //@todo cache member offset? u256 offset; for (ASTPointer const& variable: m_struct.getMembers()) @@ -811,6 +857,15 @@ bool EnumType::operator==(Type const& _other) const return other.m_enum == m_enum; } +unsigned EnumType::getStorageBytes() const +{ + size_t elements = m_enum.getMembers().size(); + if (elements <= 1) + return 1; + else + return dev::bytesRequired(elements - 1); +} + string EnumType::toString() const { return string("enum ") + m_enum.getName(); @@ -955,6 +1010,13 @@ string FunctionType::toString() const return name + ")"; } +u256 FunctionType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable function type requested.")); +} + unsigned FunctionType::getSizeOnStack() const { Location location = m_location; @@ -1077,6 +1139,13 @@ string MappingType::toString() const return "mapping(" + getKeyType()->toString() + " => " + getValueType()->toString() + ")"; } +u256 VoidType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable void type requested.")); +} + bool TypeType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -1085,6 +1154,13 @@ bool TypeType::operator==(Type const& _other) const return *getActualType() == *other.getActualType(); } +u256 TypeType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable type type requested.")); +} + MemberList const& TypeType::getMembers() const { // We need to lazy-initialize it because of recursive references. @@ -1122,6 +1198,13 @@ ModifierType::ModifierType(const ModifierDefinition& _modifier) swap(params, m_parameterTypes); } +u256 ModifierType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable type type requested.")); +} + bool ModifierType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 4b0df694d..5e4970780 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -60,12 +60,19 @@ public: return it.second; return TypePointer(); } + /// @returns the offset of the given member in storage slots and bytes inside a slot or + /// a nullptr if the member is not part of storage. + std::pair const* getMemberStorageOffset(std::string const& _name) const; + /// @returns the number of storage slots occupied by the members. + u256 const& getStorageSize() const; MemberMap::const_iterator begin() const { return m_memberTypes.begin(); } MemberMap::const_iterator end() const { return m_memberTypes.end(); } private: MemberMap m_memberTypes; + mutable u256 m_storageSize = 0; + mutable std::unique_ptr>> m_storageOffsets; }; /** @@ -97,6 +104,8 @@ public: /// @returns a pointer to _a or _b if the other is implicitly convertible to it or nullptr otherwise static TypePointer commonType(TypePointer const& _a, TypePointer const& _b); + /// Calculates the + virtual Category getCategory() const = 0; virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const @@ -126,9 +135,15 @@ public: unsigned getCalldataEncodedSize() const { return getCalldataEncodedSize(true); } /// @returns true if the type is dynamically encoded in calldata virtual bool isDynamicallySized() const { return false; } - /// @returns number of bytes required to hold this value in storage. + /// @returns the number of storage slots required to hold this value in storage. /// For dynamically "allocated" types, it returns the size of the statically allocated head, virtual u256 getStorageSize() const { return 1; } + /// Multiple small types can be packed into a single storage slot. If such a packing is possible + /// this function @returns the size in bytes smaller than 32. Data is moved to the next slot if + /// it does not fit. + /// In order to avoid computation at runtime of whether such moving is necessary, structs and + /// array data (not each element) always start a new slot. + virtual unsigned getStorageBytes() const { return 32; } /// Returns true if the type can be stored in storage. virtual bool canBeStored() const { return true; } /// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping. @@ -179,6 +194,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual unsigned getCalldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; } + virtual unsigned getStorageBytes() const override { return m_bits / 8; } virtual bool isValueType() const override { return true; } virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; } @@ -251,6 +267,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; virtual unsigned getCalldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } + virtual unsigned getStorageBytes() const override { return m_bytes; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "bytes" + dev::toString(m_bytes); } @@ -275,6 +292,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; virtual unsigned getCalldataEncodedSize(bool _padded) const { return _padded ? 32 : 1; } + virtual unsigned getStorageBytes() const override { return 1; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "bool"; } @@ -348,6 +366,7 @@ public: virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; + virtual unsigned getStorageBytes() const override { return 20; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override; @@ -411,6 +430,7 @@ public: virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; virtual unsigned getSizeOnStack() const override { return 1; } + virtual unsigned getStorageBytes() const override; virtual std::string toString() const override; virtual bool isValueType() const override { return true; } @@ -480,7 +500,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; 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 u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override; virtual MemberList const& getMembers() const override; @@ -565,7 +585,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string toString() const override { return "void"; } 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 u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } }; @@ -585,7 +605,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } - virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } + virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } @@ -611,7 +631,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual bool canBeStored() const override { return false; } - virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } + virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } virtual bool operator==(Type const& _other) const override; diff --git a/test/SolidityTypes.cpp b/test/SolidityTypes.cpp new file mode 100644 index 000000000..8defd1d89 --- /dev/null +++ b/test/SolidityTypes.cpp @@ -0,0 +1,82 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2015 + * Unit tests for the type system of Solidity. + */ + +#include +#include + +using namespace std; + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(SolidityTypes) + +BOOST_AUTO_TEST_CASE(storage_layout_simple) +{ + MemberList members(MemberList::MemberMap({ + {string("first"), Type::fromElementaryTypeName("uint128")}, + {string("second"), Type::fromElementaryTypeName("uint120")}, + {string("wraps"), Type::fromElementaryTypeName("uint16")} + })); + BOOST_REQUIRE_EQUAL(u256(2), members.getStorageSize()); + BOOST_REQUIRE(members.getMemberStorageOffset("first") != nullptr); + BOOST_REQUIRE(members.getMemberStorageOffset("second") != nullptr); + BOOST_REQUIRE(members.getMemberStorageOffset("wraps") != nullptr); + BOOST_CHECK(*members.getMemberStorageOffset("first") == make_pair(u256(0), unsigned(0))); + BOOST_CHECK(*members.getMemberStorageOffset("second") == make_pair(u256(0), unsigned(16))); + BOOST_CHECK(*members.getMemberStorageOffset("wraps") == make_pair(u256(1), unsigned(0))); +} + +BOOST_AUTO_TEST_CASE(storage_layout_mapping) +{ + MemberList members(MemberList::MemberMap({ + {string("first"), Type::fromElementaryTypeName("uint128")}, + {string("second"), make_shared( + Type::fromElementaryTypeName("uint8"), + Type::fromElementaryTypeName("uint8") + )}, + {string("third"), Type::fromElementaryTypeName("uint16")}, + {string("final"), make_shared( + Type::fromElementaryTypeName("uint8"), + Type::fromElementaryTypeName("uint8") + )}, + })); + BOOST_REQUIRE_EQUAL(u256(4), members.getStorageSize()); + BOOST_REQUIRE(members.getMemberStorageOffset("first") != nullptr); + BOOST_REQUIRE(members.getMemberStorageOffset("second") != nullptr); + BOOST_REQUIRE(members.getMemberStorageOffset("third") != nullptr); + BOOST_REQUIRE(members.getMemberStorageOffset("final") != nullptr); + BOOST_CHECK(*members.getMemberStorageOffset("first") == make_pair(u256(0), unsigned(0))); + BOOST_CHECK(*members.getMemberStorageOffset("second") == make_pair(u256(1), unsigned(0))); + BOOST_CHECK(*members.getMemberStorageOffset("third") == make_pair(u256(2), unsigned(0))); + BOOST_CHECK(*members.getMemberStorageOffset("final") == make_pair(u256(3), unsigned(0))); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +}