Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Language Features:
* General: Add a builtin that computes the base slot of a storage namespace using the `erc7201` formula from ERC-7201.
* General: Allow ``constant`` keyword for composite types (structs, arrays, function pointers) and as a data location for internal function parameters.

Compiler Features:
* Commandline Interface: Disallow selecting the deprecated assembly input mode that was only accessible via `--assemble` instead of treating it as equivalent to `--strict-assembly`.
Expand Down
8 changes: 8 additions & 0 deletions docs/contracts/constant-state-variables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ can sometimes be cheaper than immutable values.
Not all types for constants and immutables are implemented at this time. The only supported types are
:ref:`strings <strings>` (only for constants) and :ref:`value types <value-types>`.

When using the IR pipeline (``--via-ir``), ``constant`` also supports composite types:
structs, fixed-size arrays (including nested), arrays of strings, structs with string fields,
enum arrays, and arrays of internal function pointers.
Dynamic arrays (``uint256[] constant``) are supported with slicing (``arr[s:e]``).

Additionally, ``constant`` can be used as a data location specifier for parameters of
``internal`` and ``private`` functions, enabling zero-copy passing of constant references.

.. code-block:: solidity

// SPDX-License-Identifier: GPL-3.0
Expand Down
2 changes: 1 addition & 1 deletion docs/grammar/SolidityParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ locals [boolean visibilitySet = false, boolean mutabilitySet = false]
* The declaration of a single variable.
*/
variableDeclaration: type=typeName location=dataLocation? name=identifier;
dataLocation: Memory | Storage | Calldata;
dataLocation: Memory | Storage | Calldata | Constant;

/**
* Complex expression.
Expand Down
33 changes: 31 additions & 2 deletions libsolidity/analysis/DeclarationTypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
case Location::Storage: return "\"storage\"";
case Location::Transient: return "\"transient\"";
case Location::CallData: return "\"calldata\"";
case Location::Constant: return "\"constant\"";
case Location::Unspecified: return "none";
}
return {};
Expand Down Expand Up @@ -514,6 +515,9 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
case Location::Transient:
solUnimplemented("Transient data location cannot be used in this kind of variable or parameter declaration.");
break;
case Location::Constant:
typeLoc = DataLocation::Constant;
break;
case Location::Unspecified:
solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
}
Expand All @@ -528,10 +532,35 @@ void DeclarationTypeChecker::endVisit(VariableDeclaration const& _variable)
if (_variable.isConstant() && !type->isValueType())
{
bool allowed = false;
bool isByteArrayOrString = false;
if (auto arrayType = dynamic_cast<ArrayType const*>(type))
allowed = arrayType->isByteArrayOrString();
{
isByteArrayOrString = arrayType->isByteArrayOrString();
if (isByteArrayOrString)
allowed = true;
else
allowed = !arrayType->containsNestedMapping();
}
else if (auto structType = dynamic_cast<StructType const*>(type))
{
bool membersResolved = true;
for (auto const& member: structType->structDefinition().members())
if (!member->annotation().type)
{
membersResolved = false;
break;
}
allowed = !membersResolved || !structType->containsNestedMapping();
}
else if (dynamic_cast<MappingType const*>(type))
allowed = false;
if (!allowed)
m_errorReporter.fatalTypeError(9259_error, _variable.location(), "Only constants of value type and byte array type are implemented.");
m_errorReporter.fatalTypeError(9259_error, _variable.location(), "Constants of this type are not supported. Only value types, arrays, structs, and byte/string types without mappings are allowed.");
if (!isByteArrayOrString)
{
if (auto ref = dynamic_cast<ReferenceType const*>(type))
type = TypeProvider::withLocation(ref, DataLocation::Constant, false);
}
}

if (!type->isValueType())
Expand Down
37 changes: 32 additions & 5 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,30 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
return false;
}

namespace
{
bool isCompileTimeConstantExpression(Expression const& _expr)
{
if (*_expr.annotation().isPure)
return true;
if (auto const* ident = dynamic_cast<Identifier const*>(&_expr))
{
if (dynamic_cast<FunctionDefinition const*>(ident->annotation().referencedDeclaration))
if (auto const* fType = dynamic_cast<FunctionType const*>(ident->annotation().type))
if (fType->kind() == FunctionType::Kind::Internal)
return true;
}
if (auto const* tuple = dynamic_cast<TupleExpression const*>(&_expr))
{
for (auto const& comp: tuple->components())
if (comp && !isCompileTimeConstantExpression(*comp))
return false;
return true;
}
return false;
}
}

bool TypeChecker::visit(VariableDeclaration const& _variable)
{
_variable.typeName().accept(*this);
Expand Down Expand Up @@ -499,7 +523,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
{
if (!_variable.value())
m_errorReporter.typeError(4266_error, _variable.location(), "Uninitialized \"constant\" variable.");
else if (!*_variable.value()->annotation().isPure)
else if (!*_variable.value()->annotation().isPure && !isCompileTimeConstantExpression(*_variable.value()))
m_errorReporter.typeError(
8349_error,
_variable.value()->location(),
Expand Down Expand Up @@ -3458,8 +3482,8 @@ bool TypeChecker::visit(IndexAccess const& _access)
case Type::Category::ArraySlice:
{
auto const& arrayType = dynamic_cast<ArraySliceType const&>(*baseType).arrayType();
if (arrayType.location() != DataLocation::CallData || !arrayType.isDynamicallySized())
m_errorReporter.typeError(4802_error, _access.location(), "Index access is only implemented for slices of dynamic calldata arrays.");
if ((arrayType.location() != DataLocation::CallData && arrayType.location() != DataLocation::Constant) || !arrayType.isDynamicallySized())
m_errorReporter.typeError(4802_error, _access.location(), "Index access is only implemented for slices of dynamic calldata or constant arrays.");
baseType = &arrayType;
[[fallthrough]];
}
Expand Down Expand Up @@ -3599,8 +3623,11 @@ bool TypeChecker::visit(IndexRangeAccess const& _access)
else if (!(arrayType = dynamic_cast<ArrayType const*>(exprType)))
m_errorReporter.fatalTypeError(4781_error, _access.location(), "Index range access is only possible for arrays and array slices.");

if (arrayType->location() != DataLocation::CallData || !arrayType->isDynamicallySized())
m_errorReporter.typeError(1227_error, _access.location(), "Index range access is only supported for dynamic calldata arrays.");
if (
(arrayType->location() != DataLocation::CallData && arrayType->location() != DataLocation::Constant) ||
!arrayType->isDynamicallySized()
)
m_errorReporter.typeError(1227_error, _access.location(), "Index range access is only supported for dynamic calldata or constant arrays.");
else if (arrayType->baseType()->isDynamicallyEncoded())
m_errorReporter.typeError(2148_error, _access.location(), "Index range access is not supported for arrays with dynamically encoded base types.");
_access.annotation().type = TypeProvider::arraySlice(*arrayType);
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/ast/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,8 @@ std::set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocation
locations.insert(Location::Storage);
if (!isTryCatchParameter() && !isConstructorParameter())
locations.insert(Location::CallData);
if (!isConstructorParameter() && isInternalCallableParameter())
locations.insert(Location::Constant);

return locations;
}
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ class FunctionDefinition: public CallableDeclaration, public StructurallyDocumen
class VariableDeclaration: public Declaration, public StructurallyDocumented
{
public:
enum Location { Unspecified, Storage, Transient, Memory, CallData };
enum Location { Unspecified, Storage, Transient, Memory, CallData, Constant };
enum class Mutability { Mutable, Immutable, Constant };
static std::string mutabilityToString(Mutability _mutability)
{
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/ast/ASTJsonExporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,8 @@ std::string ASTJsonExporter::location(VariableDeclaration::Location _location)
return "calldata";
case VariableDeclaration::Location::Transient:
return "transient";
case VariableDeclaration::Location::Constant:
return "constant";
}
// To make the compiler happy
return {};
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/ast/ASTJsonImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,8 @@ VariableDeclaration::Location ASTJsonImporter::location(Json const& _node)
return VariableDeclaration::Location::CallData;
else if (storageLocStr == "transient")
return VariableDeclaration::Location::Transient;
else if (storageLocStr == "constant")
return VariableDeclaration::Location::Constant;
else
astAssert(false, "Unknown location declaration");

Expand Down
46 changes: 45 additions & 1 deletion libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,8 @@ TypeResult ReferenceType::unaryOperatorResult(Token _operator) const
return isPointer() ? nullptr : TypeProvider::emptyTuple();
case DataLocation::Transient:
solUnimplemented("Transient data location is only supported for value types.");
case DataLocation::Constant:
return nullptr;
}
return nullptr;
}
Expand Down Expand Up @@ -1582,6 +1584,8 @@ std::string ReferenceType::stringForReferencePart() const
case DataLocation::Transient:
solUnimplemented("Transient data location is only supported for value types.");
break;
case DataLocation::Constant:
return "constant";
}
solAssert(false, "");
return "";
Expand All @@ -1604,6 +1608,9 @@ std::string ReferenceType::identifierLocationSuffix() const
case DataLocation::CallData:
id += "_calldata";
break;
case DataLocation::Constant:
id += "_constant";
break;
}
if (isPointer())
id += "_ptr";
Expand Down Expand Up @@ -1646,6 +1653,14 @@ BoolResult ArrayType::isImplicitlyConvertibleTo(Type const& _convertTo) const
return true;
return !isDynamicallySized() && convertTo.length() >= length();
}
else if (convertTo.location() == DataLocation::Constant)
{
if (!baseType()->isImplicitlyConvertibleTo(*convertTo.baseType()))
return false;
if (convertTo.isDynamicallySized())
return true;
return !isDynamicallySized() && convertTo.length() >= length();
}
else
{
// Conversion to storage pointer or to memory, we do not copy element-for-element here, so
Expand Down Expand Up @@ -1769,6 +1784,28 @@ BoolResult ArrayType::validForLocation(DataLocation _loc) const
case DataLocation::Transient:
solUnimplemented("Transient data location is only supported for value types.");
break;
case DataLocation::Constant:
{
bigint size = bigint(length());
auto type = m_baseType;
while (auto arrayType = dynamic_cast<ArrayType const*>(type))
{
if (arrayType->isDynamicallySized())
break;
else
{
size *= arrayType->length();
type = arrayType->baseType();
}
}
if (type->isDynamicallySized())
size *= type->memoryHeadSize();
else
size *= type->memoryDataSize();
if (size >= std::numeric_limits<unsigned>::max())
return BoolResult::err("Type too large for constant data.");
break;
}
}
return true;
}
Expand Down Expand Up @@ -1852,6 +1889,11 @@ std::vector<std::tuple<std::string, Type const*>> ArrayType::makeStackItems() co
case DataLocation::Transient:
solUnimplemented("Transient data location is only supported for value types.");
break;
case DataLocation::Constant:
if (isDynamicallySized() && !isByteArrayOrString())
return {std::make_tuple("codeOffset", TypeProvider::uint256()), std::make_tuple("length", TypeProvider::uint256())};
else
return {std::make_tuple("codeOffset", TypeProvider::uint256())};
}
solAssert(false, "");
}
Expand Down Expand Up @@ -2050,7 +2092,7 @@ BoolResult ArraySliceType::isImplicitlyConvertibleTo(Type const& _other) const
return
(*this) == _other ||
(
m_arrayType.dataStoredIn(DataLocation::CallData) &&
(m_arrayType.dataStoredIn(DataLocation::CallData) || m_arrayType.dataStoredIn(DataLocation::Constant)) &&
m_arrayType.isDynamicallySized() &&
m_arrayType.isImplicitlyConvertibleTo(_other)
);
Expand Down Expand Up @@ -2609,6 +2651,8 @@ std::vector<std::tuple<std::string, Type const*>> StructType::makeStackItems() c
case DataLocation::Transient:
solUnimplemented("Transient data location is only supported for value types.");
break;
case DataLocation::Constant:
return {std::make_tuple("codeOffset", TypeProvider::uint256())};
}
solAssert(false, "");
}
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/ast/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ inline rational makeRational(bigint const& _numerator, bigint const& _denominato
return rational(_numerator, _denominator);
}

enum class DataLocation { Storage, Transient, CallData, Memory };
enum class DataLocation { Storage, Transient, CallData, Memory, Constant };


/**
Expand Down
15 changes: 9 additions & 6 deletions libsolidity/codegen/ABIFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,13 @@ std::string ABIFunctions::abiEncodingFunction(
return abiEncodingFunctionCalldataArrayWithoutCleanup(*fromArray, *toArray, _options);
else
return abiEncodingFunctionSimpleArray(*fromArray, *toArray, _options);
case DataLocation::Memory:
if (fromArray->isByteArrayOrString())
return abiEncodingFunctionMemoryByteArray(*fromArray, *toArray, _options);
else
return abiEncodingFunctionSimpleArray(*fromArray, *toArray, _options);
case DataLocation::Storage:
case DataLocation::Constant:
case DataLocation::Memory:
if (fromArray->isByteArrayOrString())
return abiEncodingFunctionMemoryByteArray(*fromArray, *toArray, _options);
else
return abiEncodingFunctionSimpleArray(*fromArray, *toArray, _options);
case DataLocation::Storage:
if (fromArray->baseType()->storageBytes() <= 16)
return abiEncodingFunctionCompactStorageArray(*fromArray, *toArray, _options);
else
Expand Down Expand Up @@ -611,6 +612,7 @@ std::string ABIFunctions::abiEncodingFunctionSimpleArray(
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
switch (_from.location())
{
case DataLocation::Constant:
case DataLocation::Memory:
templ("arrayElementAccess", "mload(srcPtr)");
break;
Expand Down Expand Up @@ -907,6 +909,7 @@ std::string ABIFunctions::abiEncodingFunctionStruct(
}
break;
}
case DataLocation::Constant:
case DataLocation::Memory:
{
std::string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name));
Expand Down
6 changes: 6 additions & 0 deletions libsolidity/codegen/ArrayUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,9 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDept
case DataLocation::Transient:
solUnimplemented("Transient data location is only supported for value types.");
break;
case DataLocation::Constant:
solUnimplemented("Constant composite types require compilation via the IR pipeline (use --via-ir).");
break;
}
}
}
Expand Down Expand Up @@ -783,6 +786,9 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b
case DataLocation::Transient:
solUnimplemented("Transient data location is only supported for value types.");
break;
case DataLocation::Constant:
solUnimplemented("Constant composite types require compilation via the IR pipeline (use --via-ir).");
break;
}
}

Expand Down
9 changes: 9 additions & 0 deletions libsolidity/codegen/CompilerUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,9 @@ void CompilerUtils::convertType(
case DataLocation::Transient:
solUnimplemented("Transient data location is only supported for value types.");
break;
case DataLocation::Constant:
solUnimplemented("Constant composite types require compilation via the IR pipeline (use --via-ir).");
break;
case DataLocation::Memory:
{
// Copy the array to a free position in memory, unless it is already in memory.
Expand Down Expand Up @@ -1174,6 +1177,9 @@ void CompilerUtils::convertType(
case DataLocation::Transient:
solUnimplemented("Transient data location is only supported for value types.");
break;
case DataLocation::Constant:
solUnimplemented("Constant composite types require compilation via the IR pipeline (use --via-ir).");
break;
case DataLocation::Memory:
// Copy the array to a free position in memory, unless it is already in memory.
switch (typeOnStack.location())
Expand Down Expand Up @@ -1216,6 +1222,9 @@ void CompilerUtils::convertType(
case DataLocation::Transient:
solUnimplemented("Transient data location is only supported for value types.");
break;
case DataLocation::Constant:
solUnimplemented("Constant composite types require compilation via the IR pipeline (use --via-ir).");
break;
case DataLocation::CallData:
{
if (typeOnStack.isDynamicallyEncoded())
Expand Down
Loading