Merge pull request #9407 from ethereum/refactorStorageSizeUpperBound

Refactor storageSizeUpperBound.
This commit is contained in:
Daniel Kirchner 2020-07-14 12:17:45 +02:00 committed by GitHub
commit 874f4d5447
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 63 additions and 59 deletions

View File

@ -60,6 +60,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract)
checkLibraryRequirements(_contract); checkLibraryRequirements(_contract);
checkBaseABICompatibility(_contract); checkBaseABICompatibility(_contract);
checkPayableFallbackWithoutReceive(_contract); checkPayableFallbackWithoutReceive(_contract);
checkStorageSize(_contract);
return Error::containsOnlyWarnings(m_errorReporter.errors()); 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()) SecondarySourceLocation{}.append("The payable fallback function is defined here.", fallback->location())
); );
} }
void ContractLevelChecker::checkStorageSize(ContractDefinition const& _contract)
{
bigint size = 0;
vector<VariableDeclaration const*> 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;
}
}
}

View File

@ -83,6 +83,8 @@ private:
/// Warns if the contract has a payable fallback, but no receive ether function. /// Warns if the contract has a payable fallback, but no receive ether function.
void checkPayableFallbackWithoutReceive(ContractDefinition const& _contract); void checkPayableFallbackWithoutReceive(ContractDefinition const& _contract);
/// Error if the contract requires too much storage
void checkStorageSize(ContractDefinition const& _contract);
OverrideChecker m_overrideChecker; OverrideChecker m_overrideChecker;
langutil::ErrorReporter& m_errorReporter; langutil::ErrorReporter& m_errorReporter;

View File

@ -92,19 +92,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
for (auto const& n: _contract.subNodes()) for (auto const& n: _contract.subNodes())
n->accept(*this); n->accept(*this);
bigint size = 0; m_currentContract = nullptr;
vector<VariableDeclaration const*> 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;
}
}
return false; return false;
} }
@ -566,7 +554,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
" in small quantities per transaction."; " in small quantities per transaction.";
}; };
if (storageSizeUpperBound(*varType) >= bigint(1) << 64) if (varType->storageSizeUpperBound() >= bigint(1) << 64)
{ {
if (_variable.isStateVariable()) if (_variable.isStateVariable())
m_errorReporter.warning(3408_error, _variable.location(), collisionMessage(_variable.name(), true)); m_errorReporter.warning(3408_error, _variable.location(), collisionMessage(_variable.name(), true));

View File

@ -63,37 +63,6 @@ struct TypeComp
}; };
using TypeSet = std::set<Type const*, TypeComp>; using TypeSet = std::set<Type const*, TypeComp>;
bigint storageSizeUpperBoundInner(
Type const& _type,
set<StructDefinition const*>& _structsSeen
)
{
switch (_type.category())
{
case Type::Category::Array:
{
auto const& t = dynamic_cast<ArrayType const&>(_type);
if (!t.isDynamicallySized())
return storageSizeUpperBoundInner(*t.baseType(), _structsSeen) * t.length();
break;
}
case Type::Category::Struct:
{
auto const& t = dynamic_cast<StructType const&>(_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( void oversizedSubtypesInner(
Type const& _type, Type const& _type,
bool _includeType, bool _includeType,
@ -106,7 +75,7 @@ void oversizedSubtypesInner(
case Type::Category::Array: case Type::Category::Array:
{ {
auto const& t = dynamic_cast<ArrayType const&>(_type); auto const& t = dynamic_cast<ArrayType const&>(_type);
if (_includeType && storageSizeUpperBound(t) >= bigint(1) << 64) if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64)
_oversizedSubtypes.insert(&t); _oversizedSubtypes.insert(&t);
oversizedSubtypesInner(*t.baseType(), t.isDynamicallySized(), _structsSeen, _oversizedSubtypes); oversizedSubtypesInner(*t.baseType(), t.isDynamicallySized(), _structsSeen, _oversizedSubtypes);
break; break;
@ -116,7 +85,7 @@ void oversizedSubtypesInner(
auto const& t = dynamic_cast<StructType const&>(_type); auto const& t = dynamic_cast<StructType const&>(_type);
if (_structsSeen.count(&t.structDefinition())) if (_structsSeen.count(&t.structDefinition()))
return; return;
if (_includeType && storageSizeUpperBound(t) >= bigint(1) << 64) if (_includeType && t.storageSizeUpperBound() >= bigint(1) << 64)
_oversizedSubtypes.insert(&t); _oversizedSubtypes.insert(&t);
_structsSeen.insert(&t.structDefinition()); _structsSeen.insert(&t.structDefinition());
for (auto const& m: t.members(nullptr)) for (auto const& m: t.members(nullptr))
@ -231,12 +200,6 @@ util::Result<TypePointers> transformParametersToExternal(TypePointers const& _pa
} }
bigint solidity::frontend::storageSizeUpperBound(frontend::Type const& _type)
{
set<StructDefinition const*> structsSeen;
return storageSizeUpperBoundInner(_type, structsSeen);
}
vector<frontend::Type const*> solidity::frontend::oversizedSubtypes(frontend::Type const& _type) vector<frontend::Type const*> solidity::frontend::oversizedSubtypes(frontend::Type const& _type)
{ {
set<StructDefinition const*> structsSeen; set<StructDefinition const*> structsSeen;
@ -1847,7 +1810,7 @@ BoolResult ArrayType::validForLocation(DataLocation _loc) const
break; break;
} }
case DataLocation::Storage: case DataLocation::Storage:
if (storageSizeUpperBound(*this) >= bigint(1) << 256) if (storageSizeUpperBound() >= bigint(1) << 256)
return BoolResult::err("Type too large for storage."); return BoolResult::err("Type too large for storage.");
break; break;
} }
@ -1888,6 +1851,14 @@ bool ArrayType::isDynamicallyEncoded() const
return isDynamicallySized() || baseType()->isDynamicallyEncoded(); return isDynamicallySized() || baseType()->isDynamicallyEncoded();
} }
bigint ArrayType::storageSizeUpperBound() const
{
if (isDynamicallySized())
return 1;
else
return length() * baseType()->storageSizeUpperBound();
}
u256 ArrayType::storageSize() const u256 ArrayType::storageSize() const
{ {
if (isDynamicallySized()) if (isDynamicallySized())
@ -2347,6 +2318,14 @@ u256 StructType::memoryDataSize() const
return size; 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 u256 StructType::storageSize() const
{ {
return max<u256>(1, members(nullptr).storageSize()); return max<u256>(1, members(nullptr).storageSize());
@ -2491,7 +2470,7 @@ BoolResult StructType::validForLocation(DataLocation _loc) const
if ( if (
_loc == DataLocation::Storage && _loc == DataLocation::Storage &&
storageSizeUpperBound(*this) >= bigint(1) << 256 storageSizeUpperBound() >= bigint(1) << 256
) )
return BoolResult::err("Type too large for storage."); return BoolResult::err("Type too large for storage.");

View File

@ -59,7 +59,6 @@ using BoolResult = util::Result<bool>;
namespace solidity::frontend namespace solidity::frontend
{ {
bigint storageSizeUpperBound(frontend::Type const& _type);
std::vector<frontend::Type const*> oversizedSubtypes(frontend::Type const& _type); std::vector<frontend::Type const*> oversizedSubtypes(frontend::Type const& _type);
inline rational makeRational(bigint const& _numerator, bigint const& _denominator) 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. /// @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, /// For dynamically "allocated" types, it returns the size of the statically allocated head,
virtual u256 storageSize() const { return 1; } 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 /// 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 /// this function @returns the size in bytes smaller than 32. Data is moved to the next slot if
/// it does not fit. /// it does not fit.
@ -788,6 +791,7 @@ public:
unsigned calldataEncodedTailSize() const override; unsigned calldataEncodedTailSize() const override;
bool isDynamicallySized() const override { return m_hasDynamicLength; } bool isDynamicallySized() const override { return m_hasDynamicLength; }
bool isDynamicallyEncoded() const override; bool isDynamicallyEncoded() const override;
bigint storageSizeUpperBound() const override;
u256 storageSize() const override; u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
bool nameable() const override { return true; } bool nameable() const override { return true; }
@ -952,6 +956,7 @@ public:
unsigned calldataEncodedTailSize() const override; unsigned calldataEncodedTailSize() const override;
bool isDynamicallyEncoded() const override; bool isDynamicallyEncoded() const override;
u256 memoryDataSize() const override; u256 memoryDataSize() const override;
bigint storageSizeUpperBound() const override;
u256 storageSize() const override; u256 storageSize() const override;
bool canLiveOutsideStorage() const override { return true; } bool canLiveOutsideStorage() const override { return true; }
bool nameable() const override { return true; } bool nameable() const override { return true; }

View File

@ -4,5 +4,5 @@ contract C {
uint[2**255][2] a; uint[2**255][2] a;
} }
// ---- // ----
// TypeError 1534: (77-94): Type too large for storage.
// TypeError 7676: (60-97): Contract too large for storage. // TypeError 7676: (60-97): Contract too large for storage.
// TypeError 1534: (77-94): Type too large for storage.

View File

@ -5,6 +5,6 @@ contract C {
uint[2**255] b; 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: (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. // 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.

View File

@ -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.

View File

@ -8,5 +8,5 @@ contract C {
S s; S s;
} }
// ---- // ----
// TypeError 1534: (146-149): Type too large for storage.
// TypeError 7676: (60-152): Contract too large for storage. // TypeError 7676: (60-152): Contract too large for storage.
// TypeError 1534: (146-149): Type too large for storage.