Skip to content

Commit 45a910c

Browse files
authored
Merge pull request #11750 from ethereum/immutables-10463
Allow reading of immutables during construction time
2 parents 065a303 + 121fd40 commit 45a910c

40 files changed

+232
-54
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Language Features:
44

55

66
Compiler Features:
7+
* Immutable variables can be read at construction time once they are initialized.
78

89

910
Bugfixes:

docs/contracts/constant-state-variables.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,23 @@ Immutable
7676
Variables declared as ``immutable`` are a bit less restricted than those
7777
declared as ``constant``: Immutable variables can be assigned an arbitrary
7878
value in the constructor of the contract or at the point of their declaration.
79-
They cannot be read during construction time and can only be assigned once.
79+
They can be assigned only once and can, from that point on, be read even during
80+
construction time.
8081

8182
The contract creation code generated by the compiler will modify the
8283
contract's runtime code before it is returned by replacing all references
8384
to immutables by the values assigned to the them. This is important if
8485
you are comparing the
8586
runtime code generated by the compiler with the one actually stored in the
8687
blockchain.
88+
89+
.. note::
90+
Immutables that are assigned at their declaration are only considered
91+
initialized once the constructor of the contract is executing.
92+
This means you cannot initialize immutables inline with a value
93+
that depends on another immutable. You can do this, however,
94+
inside the constructor of the contract.
95+
96+
This is a safeguard against different interpretations about the order
97+
of state variable initialization and constructor execution, especially
98+
with regards to inheritance.

libsolidity/analysis/ImmutableValidator.cpp

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,31 @@ using namespace solidity::langutil;
2727

2828
void ImmutableValidator::analyze()
2929
{
30-
m_inConstructionContext = true;
30+
m_inCreationContext = true;
3131

3232
auto linearizedContracts = m_currentContract.annotation().linearizedBaseContracts | ranges::views::reverse;
3333

3434
for (ContractDefinition const* contract: linearizedContracts)
3535
for (VariableDeclaration const* stateVar: contract->stateVariables())
3636
if (stateVar->value())
37-
m_initializedStateVariables.emplace(stateVar);
37+
stateVar->value()->accept(*this);
3838

3939
for (ContractDefinition const* contract: linearizedContracts)
40+
for (std::shared_ptr<InheritanceSpecifier> const& inheritSpec: contract->baseContracts())
41+
if (auto args = inheritSpec->arguments())
42+
ASTNode::listAccept(*args, *this);
43+
44+
for (ContractDefinition const* contract: linearizedContracts)
45+
{
4046
for (VariableDeclaration const* stateVar: contract->stateVariables())
4147
if (stateVar->value())
42-
stateVar->value()->accept(*this);
48+
m_initializedStateVariables.emplace(stateVar);
4349

44-
for (ContractDefinition const* contract: linearizedContracts)
4550
if (contract->constructor())
4651
visitCallableIfNew(*contract->constructor());
52+
}
4753

48-
for (ContractDefinition const* contract: linearizedContracts)
49-
for (std::shared_ptr<InheritanceSpecifier> const& inheritSpec: contract->baseContracts())
50-
if (auto args = inheritSpec->arguments())
51-
ASTNode::listAccept(*args, *this);
52-
53-
m_inConstructionContext = false;
54+
m_inCreationContext = false;
5455

5556
for (ContractDefinition const* contract: linearizedContracts)
5657
{
@@ -64,6 +65,15 @@ void ImmutableValidator::analyze()
6465
checkAllVariablesInitialized(m_currentContract.location());
6566
}
6667

68+
bool ImmutableValidator::visit(Assignment const& _assignment)
69+
{
70+
// Need to visit values first (rhs) as they might access other immutables.
71+
_assignment.rightHandSide().accept(*this);
72+
_assignment.leftHandSide().accept(*this);
73+
return false;
74+
}
75+
76+
6777
bool ImmutableValidator::visit(FunctionDefinition const& _functionDefinition)
6878
{
6979
return analyseCallable(_functionDefinition);
@@ -207,19 +217,37 @@ void ImmutableValidator::analyseVariableReference(VariableDeclaration const& _va
207217
"Cannot write to immutable here: Immutable variables cannot be initialized inside an if statement."
208218
);
209219
else if (m_initializedStateVariables.count(&_variableReference))
220+
{
221+
if (!read)
222+
m_errorReporter.typeError(
223+
1574_error,
224+
_expression.location(),
225+
"Immutable state variable already initialized."
226+
);
227+
else
228+
m_errorReporter.typeError(
229+
2718_error,
230+
_expression.location(),
231+
"Immutable variables cannot be modified after initialization."
232+
);
233+
}
234+
else if (read)
210235
m_errorReporter.typeError(
211-
1574_error,
236+
3969_error,
212237
_expression.location(),
213-
"Immutable state variable already initialized."
238+
"Immutable variables must be initialized using an assignment."
214239
);
215240
m_initializedStateVariables.emplace(&_variableReference);
216241
}
217-
if (read && m_inConstructionContext)
242+
if (
243+
read &&
244+
m_inCreationContext &&
245+
!m_initializedStateVariables.count(&_variableReference)
246+
)
218247
m_errorReporter.typeError(
219248
7733_error,
220249
_expression.location(),
221-
"Immutable variables cannot be read during contract creation time, which means "
222-
"they cannot be read in the constructor or any function or modifier called from it."
250+
"Immutable variables cannot be read before they are initialized."
223251
);
224252
}
225253

libsolidity/analysis/ImmutableValidator.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ namespace solidity::frontend
2828

2929
/**
3030
* Validates access and initialization of immutable variables:
31-
* must be directly initialized in their respective c'tor
32-
* can not be read by any function/modifier called by the c'tor (or the c'tor itself)
31+
* must be directly initialized in their respective c'tor or inline
32+
* cannot be read before being initialized
33+
* cannot be read when initializing state variables inline
3334
* must be initialized outside loops (only one initialization)
3435
* must be initialized outside ifs (must be initialized unconditionally)
3536
* must be initialized exactly once (no multiple statements)
@@ -48,6 +49,7 @@ class ImmutableValidator: private ASTConstVisitor
4849
void analyze();
4950

5051
private:
52+
bool visit(Assignment const& _assignment);
5153
bool visit(FunctionDefinition const& _functionDefinition);
5254
bool visit(ModifierDefinition const& _modifierDefinition);
5355
bool visit(MemberAccess const& _memberAccess);
@@ -74,7 +76,7 @@ class ImmutableValidator: private ASTConstVisitor
7476
FunctionDefinition const* m_currentConstructor = nullptr;
7577
bool m_inLoop = false;
7678
bool m_inBranch = false;
77-
bool m_inConstructionContext = false;
79+
bool m_inCreationContext = true;
7880
};
7981

8082
}

libsolidity/codegen/LValue.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,15 @@ ImmutableItem::ImmutableItem(CompilerContext& _compilerContext, VariableDeclarat
155155
void ImmutableItem::retrieveValue(SourceLocation const&, bool) const
156156
{
157157
solUnimplementedAssert(m_dataType->isValueType(), "");
158-
solAssert(!m_context.runtimeContext(), "Tried to read immutable at construction time.");
159-
for (auto&& slotName: m_context.immutableVariableSlotNames(m_variable))
160-
m_context.appendImmutable(slotName);
158+
159+
if (m_context.runtimeContext())
160+
CompilerUtils(m_context).loadFromMemory(
161+
static_cast<unsigned>(m_context.immutableMemoryOffset(m_variable)),
162+
*m_dataType
163+
);
164+
else
165+
for (auto&& slotName: m_context.immutableVariableSlotNames(m_variable))
166+
m_context.appendImmutable(slotName);
161167
}
162168

163169
void ImmutableItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const

libsolidity/codegen/ir/IRGenerationContext.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,17 @@ using InternalDispatchMap = std::map<YulArity, DispatchSet>;
6565
class IRGenerationContext
6666
{
6767
public:
68+
enum class ExecutionContext { Creation, Deployed };
69+
6870
IRGenerationContext(
6971
langutil::EVMVersion _evmVersion,
72+
ExecutionContext _executionContext,
7073
RevertStrings _revertStrings,
7174
OptimiserSettings _optimiserSettings,
7275
std::map<std::string, unsigned> _sourceIndices
7376
):
7477
m_evmVersion(_evmVersion),
78+
m_executionContext(_executionContext),
7579
m_revertStrings(_revertStrings),
7680
m_optimiserSettings(std::move(_optimiserSettings)),
7781
m_sourceIndices(std::move(_sourceIndices))
@@ -139,6 +143,7 @@ class IRGenerationContext
139143
YulUtilFunctions utils();
140144

141145
langutil::EVMVersion evmVersion() const { return m_evmVersion; }
146+
ExecutionContext executionContext() const { return m_executionContext; }
142147

143148
void setArithmetic(Arithmetic _value) { m_arithmetic = _value; }
144149
Arithmetic arithmetic() const { return m_arithmetic; }
@@ -162,8 +167,11 @@ class IRGenerationContext
162167

163168
std::map<std::string, unsigned> const& sourceIndices() const { return m_sourceIndices; }
164169

170+
bool immutableRegistered(VariableDeclaration const& _varDecl) const { return m_immutableVariables.count(&_varDecl); }
171+
165172
private:
166173
langutil::EVMVersion m_evmVersion;
174+
ExecutionContext m_executionContext;
167175
RevertStrings m_revertStrings;
168176
OptimiserSettings m_optimiserSettings;
169177
std::map<std::string, unsigned> m_sourceIndices;

libsolidity/codegen/ir/IRGenerator.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ string IRGenerator::generate(
164164
}
165165
)");
166166

167-
resetContext(_contract);
167+
resetContext(_contract, ExecutionContext::Creation);
168168
for (VariableDeclaration const* var: ContractType(_contract).immutableVariables())
169169
m_context.registerImmutableVariable(*var);
170170

@@ -211,7 +211,7 @@ string IRGenerator::generate(
211211
bool creationInvolvesAssembly = m_context.inlineAssemblySeen();
212212
t("memoryInitCreation", memoryInit(!creationInvolvesAssembly));
213213

214-
resetContext(_contract);
214+
resetContext(_contract, ExecutionContext::Deployed);
215215

216216
// NOTE: Function pointers can be passed from creation code via storage variables. We need to
217217
// get all the functions they could point to into the dispatch functions even if they're never
@@ -1049,7 +1049,7 @@ string IRGenerator::memoryInit(bool _useMemoryGuard)
10491049
).render();
10501050
}
10511051

1052-
void IRGenerator::resetContext(ContractDefinition const& _contract)
1052+
void IRGenerator::resetContext(ContractDefinition const& _contract, ExecutionContext _context)
10531053
{
10541054
solAssert(
10551055
m_context.functionGenerationQueueEmpty(),
@@ -1063,7 +1063,7 @@ void IRGenerator::resetContext(ContractDefinition const& _contract)
10631063
m_context.internalDispatchClean(),
10641064
"Reset internal dispatch map without consuming it."
10651065
);
1066-
IRGenerationContext newContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings, m_context.sourceIndices());
1066+
IRGenerationContext newContext(m_evmVersion, _context, m_context.revertStrings(), m_optimiserSettings, m_context.sourceIndices());
10671067
newContext.copyFunctionIDsFrom(m_context);
10681068
m_context = move(newContext);
10691069

libsolidity/codegen/ir/IRGenerator.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class SourceUnit;
3939
class IRGenerator
4040
{
4141
public:
42+
using ExecutionContext = IRGenerationContext::ExecutionContext;
43+
4244
IRGenerator(
4345
langutil::EVMVersion _evmVersion,
4446
RevertStrings _revertStrings,
@@ -47,7 +49,7 @@ class IRGenerator
4749
):
4850
m_evmVersion(_evmVersion),
4951
m_optimiserSettings(_optimiserSettings),
50-
m_context(_evmVersion, _revertStrings, std::move(_optimiserSettings), std::move(_sourceIndices)),
52+
m_context(_evmVersion, ExecutionContext::Creation, _revertStrings, std::move(_optimiserSettings), std::move(_sourceIndices)),
5153
m_utils(_evmVersion, m_context.revertStrings(), m_context.functionCollector())
5254
{}
5355

@@ -115,7 +117,7 @@ class IRGenerator
115117
/// to perform memory optimizations.
116118
std::string memoryInit(bool _useMemoryGuard);
117119

118-
void resetContext(ContractDefinition const& _contract);
120+
void resetContext(ContractDefinition const& _contract, ExecutionContext _context);
119121

120122
langutil::EVMVersion const m_evmVersion;
121123
OptimiserSettings const m_optimiserSettings;

libsolidity/codegen/ir/IRGeneratorForStatements.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2934,7 +2934,14 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue)
29342934
solUnimplementedAssert(_lvalue.type.isValueType(), "");
29352935
solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1, "");
29362936
solAssert(_lvalue.type == *_immutable.variable->type(), "");
2937-
define(result) << "loadimmutable(\"" << to_string(_immutable.variable->id()) << "\")\n";
2937+
if (m_context.executionContext() == IRGenerationContext::ExecutionContext::Creation)
2938+
define(result) <<
2939+
m_utils.readFromMemory(*_immutable.variable->type()) <<
2940+
"(" <<
2941+
to_string(m_context.immutableMemoryOffset(*_immutable.variable)) <<
2942+
")\n";
2943+
else
2944+
define(result) << "loadimmutable(\"" << to_string(_immutable.variable->id()) << "\")\n";
29382945
},
29392946
[&](IRLValue::Tuple const&) {
29402947
solAssert(false, "Attempted to read from tuple lvalue.");
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
contract C {
2+
uint immutable public a;
3+
uint immutable public b;
4+
uint immutable public c;
5+
uint immutable public d;
6+
7+
constructor() {
8+
a = 1;
9+
b = a;
10+
c = b;
11+
d = c;
12+
}
13+
}
14+
// ====
15+
// compileViaYul: also
16+
// ----
17+
// a() -> 1
18+
// b() -> 1
19+
// c() -> 1
20+
// d() -> 1

0 commit comments

Comments
 (0)