From f28bede34a632a0c2417be8399c3daa864092bb0 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 14 Jul 2020 10:32:11 +0200 Subject: [PATCH] Refactor storageSizeUpperBound. --- libsolidity/analysis/ContractLevelChecker.cpp | 18 ++++++ libsolidity/analysis/ContractLevelChecker.h | 2 + libsolidity/analysis/TypeChecker.cpp | 16 +---- libsolidity/ast/Types.cpp | 61 ++++++------------- libsolidity/ast/Types.h | 7 ++- .../largeTypes/oversized_array.sol | 2 +- .../largeTypes/oversized_contract.sol | 2 +- .../oversized_contract_inheritance.sol | 12 ++++ .../largeTypes/oversized_struct.sol | 2 +- 9 files changed, 63 insertions(+), 59 deletions(-) create mode 100644 test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp index c0da54564..fc391f196 100644 --- a/libsolidity/analysis/ContractLevelChecker.cpp +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -60,6 +60,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract) checkLibraryRequirements(_contract); checkBaseABICompatibility(_contract); checkPayableFallbackWithoutReceive(_contract); + checkStorageSize(_contract); return Error::containsOnlyWarnings(m_errorReporter.errors()); } @@ -458,3 +459,20 @@ void ContractLevelChecker::checkPayableFallbackWithoutReceive(ContractDefinition SecondarySourceLocation{}.append("The payable fallback function is defined here.", fallback->location()) ); } + +void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract) +{ + bigint size = 0; + vector variables; + for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) + for (VariableDeclaration const* variable: contract->stateVariables()) + if (!(variable->isConstant() || variable->immutable())) + { + size += variable->annotation().type->storageSizeUpperBound(); + if (size >= bigint(1) << 256) + { + m_errorReporter.typeError(7676_error, _contract.location(), "Contract too large for storage."); + break; + } + } +} diff --git a/libsolidity/analysis/ContractLevelChecker.h b/libsolidity/analysis/ContractLevelChecker.h index e40cb8b0d..3a0d7d740 100644 --- a/libsolidity/analysis/ContractLevelChecker.h +++ b/libsolidity/analysis/ContractLevelChecker.h @@ -83,6 +83,8 @@ private: /// Warns if the contract has a payable fallback, but no receive ether function. void checkPayableFallbackWithoutReceive(ContractDefinition const& _contract); + /// Error if the contract requires too much storage + void checkStorageSize(ContractDefinition const& _contract); OverrideChecker m_overrideChecker; langutil::ErrorReporter& m_errorReporter; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index aa316cb48..0c47f2073 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -92,19 +92,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract) for (auto const& n: _contract.subNodes()) n->accept(*this); - bigint size = 0; - vector variables; - for (ContractDefinition const* contract: boost::adaptors::reverse(m_currentContract->annotation().linearizedBaseContracts)) - for (VariableDeclaration const* variable: contract->stateVariables()) - if (!(variable->isConstant() || variable->immutable())) - { - size += storageSizeUpperBound(*(variable->annotation().type)); - if (size >= bigint(1) << 256) - { - m_errorReporter.typeError(7676_error, m_currentContract->location(), "Contract too large for storage."); - break; - } - } + m_currentContract = nullptr; return false; } @@ -566,7 +554,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) " in small quantities per transaction."; }; - if (storageSizeUpperBound(*varType) >= bigint(1) << 64) + if (varType->storageSizeUpperBound() >= bigint(1) << 64) { if (_variable.isStateVariable()) m_errorReporter.warning(3408_error, _variable.location(), collisionMessage(_variable.name(), true)); diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 546e6ee63..825fc8d0c 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -63,37 +63,6 @@ struct TypeComp }; using TypeSet = std::set; -bigint storageSizeUpperBoundInner( - Type const& _type, - set& _structsSeen -) -{ - switch (_type.category()) - { - case Type::Category::Array: - { - auto const& t = dynamic_cast(_type); - if (!t.isDynamicallySized()) - return storageSizeUpperBoundInner(*t.baseType(), _structsSeen) * t.length(); - break; - } - case Type::Category::Struct: - { - auto const& t = dynamic_cast(_type); - solAssert(!_structsSeen.count(&t.structDefinition()), "Recursive struct."); - bigint size = 1; - _structsSeen.insert(&t.structDefinition()); - for (auto const& m: t.members(nullptr)) - size += storageSizeUpperBoundInner(*m.type, _structsSeen); - _structsSeen.erase(&t.structDefinition()); - return size; - } - default: - break; - } - return bigint(1); -} - void oversizedSubtypesInner( Type const& _type, bool _includeType, @@ -106,7 +75,7 @@ void oversizedSubtypesInner( case Type::Category::Array: { auto const& t = dynamic_cast(_type); - if (_includeType && storageSizeUpperBound(t) >= bigint(1) << 64) + if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64) _oversizedSubtypes.insert(&t); oversizedSubtypesInner(*t.baseType(), t.isDynamicallySized(), _structsSeen, _oversizedSubtypes); break; @@ -116,7 +85,7 @@ void oversizedSubtypesInner( auto const& t = dynamic_cast(_type); if (_structsSeen.count(&t.structDefinition())) return; - if (_includeType && storageSizeUpperBound(t) >= bigint(1) << 64) + if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64) _oversizedSubtypes.insert(&t); _structsSeen.insert(&t.structDefinition()); for (auto const& m: t.members(nullptr)) @@ -231,12 +200,6 @@ util::Result transformParametersToExternal(TypePointers const& _pa } -bigint solidity::frontend::storageSizeUpperBound(frontend::Type const& _type) -{ - set structsSeen; - return storageSizeUpperBoundInner(_type, structsSeen); -} - vector solidity::frontend::oversizedSubtypes(frontend::Type const& _type) { set structsSeen; @@ -1847,7 +1810,7 @@ BoolResult ArrayType::validForLocation(DataLocation _loc) const break; } case DataLocation::Storage: - if (storageSizeUpperBound(*this) >= bigint(1) << 256) + if (storageSizeUpperBound() >= bigint(1) << 256) return BoolResult::err("Type too large for storage."); break; } @@ -1888,6 +1851,14 @@ bool ArrayType::isDynamicallyEncoded() const return isDynamicallySized() || baseType()->isDynamicallyEncoded(); } +bigint ArrayType::storageSizeUpperBound() const +{ + if (isDynamicallySized()) + return 1; + else + return length() * baseType()->storageSizeUpperBound(); +} + u256 ArrayType::storageSize() const { if (isDynamicallySized()) @@ -2347,6 +2318,14 @@ u256 StructType::memoryDataSize() const return size; } +bigint StructType::storageSizeUpperBound() const +{ + bigint size = 1; + for (auto const& member: members(nullptr)) + size += member.type->storageSizeUpperBound(); + return size; +} + u256 StructType::storageSize() const { return max(1, members(nullptr).storageSize()); @@ -2491,7 +2470,7 @@ BoolResult StructType::validForLocation(DataLocation _loc) const if ( _loc == DataLocation::Storage && - storageSizeUpperBound(*this) >= bigint(1) << 256 + storageSizeUpperBound() >= bigint(1) << 256 ) return BoolResult::err("Type too large for storage."); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index b05021630..155fa7f14 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -59,7 +59,6 @@ using BoolResult = util::Result; namespace solidity::frontend { -bigint storageSizeUpperBound(frontend::Type const& _type); std::vector oversizedSubtypes(frontend::Type const& _type); inline rational makeRational(bigint const& _numerator, bigint const& _denominator) @@ -251,6 +250,10 @@ public: /// @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 storageSize() const { return 1; } + /// @returns an upper bound on the total storage size required by this type, descending + /// into structs and statically-sized arrays. This is mainly to ensure that the storage + /// slot allocation algorithm does not overflow, it is not a protection against collisions. + virtual bigint storageSizeUpperBound() 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. @@ -788,6 +791,7 @@ public: unsigned calldataEncodedTailSize() const override; bool isDynamicallySized() const override { return m_hasDynamicLength; } bool isDynamicallyEncoded() const override; + bigint storageSizeUpperBound() const override; u256 storageSize() const override; bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } bool nameable() const override { return true; } @@ -952,6 +956,7 @@ public: unsigned calldataEncodedTailSize() const override; bool isDynamicallyEncoded() const override; u256 memoryDataSize() const override; + bigint storageSizeUpperBound() const override; u256 storageSize() const override; bool canLiveOutsideStorage() const override { return true; } bool nameable() const override { return true; } diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol index 951840fd4..123773403 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_array.sol @@ -4,5 +4,5 @@ contract C { uint[2**255][2] a; } // ---- -// TypeError 1534: (77-94): Type too large for storage. // TypeError 7676: (60-97): Contract too large for storage. +// TypeError 1534: (77-94): Type too large for storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol index 69f8b1024..44c7274e6 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_contract.sol @@ -5,6 +5,6 @@ contract C { uint[2**255] b; } // ---- +// TypeError 7676: (60-114): Contract too large for storage. // Warning 3408: (77-91): Variable "a" covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. // Warning 3408: (97-111): Variable "b" covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. -// TypeError 7676: (60-114): Contract too large for storage. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol new file mode 100644 index 000000000..24073d4c0 --- /dev/null +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_contract_inheritance.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >= 0.0; +contract C { + uint[2**255] a; +} +contract D is C { + uint[2**255] b; +} +// ---- +// TypeError 7676: (95-134): Contract too large for storage. +// Warning 3408: (77-91): Variable "a" covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. +// Warning 3408: (117-131): Variable "b" covers a large part of storage and thus makes collisions likely. Either use mappings or dynamic arrays and allow their size to be increased only in small quantities per transaction. diff --git a/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol b/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol index a3f3a4160..757ec9e4a 100644 --- a/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol +++ b/test/libsolidity/syntaxTests/largeTypes/oversized_struct.sol @@ -8,5 +8,5 @@ contract C { S s; } // ---- -// TypeError 1534: (146-149): Type too large for storage. // TypeError 7676: (60-152): Contract too large for storage. +// TypeError 1534: (146-149): Type too large for storage.