mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9407 from ethereum/refactorStorageSizeUpperBound
Refactor storageSizeUpperBound.
This commit is contained in:
commit
874f4d5447
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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));
|
||||||
|
@ -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.");
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
|
||||||
|
@ -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.
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user