-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Storage layout specifier docs #15892
base: develop
Are you sure you want to change the base?
Conversation
50ba958
to
88792ee
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Layout of State Variables in Storage and Transient Storage should mention the specifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should also say what happens when the base slot is close to the end of storage:
- The compiler can detect collisions for things of static size and that dynamically sized stuff gets placed at random locations (so collisions are no more likely than at zero base slot)
- Risks for upgradeability and inline assembly access beyond allocated space when things are placed at the very end.
Contracts can define an arbitrary base slot for its own storage. | ||
The contract's state variables will be stored from the specified slot | ||
instead of the default slot zero. | ||
This can be done by using ``layout at <base-slot-expression>`` in the header of | ||
a contract definition, either after or before the inheritance specifier. | ||
It can be given at most once. | ||
The ``base-slot-expression`` must be an expression consisting of number literals | ||
that can be evaluated at compile time and yield in a rational value in the range of ``uint256``. | ||
As a general rule, any expression that can be used to specify the size of an array | ||
may also be used to indicate a slot. | ||
The layout of a contract without an explicit specification is identical to one with ``layout at 0``. | ||
The storage layout cannot be specified for abstract contracts, interfaces and libraries. | ||
The identifiers ``layout`` and ``at`` are not reserved keywords of the Solidity language, but | ||
it is strongly recommended to avoid using them since that may change in the future. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please split this into paragraphs. Currently this will all render as one big clump of text, which will only grow as we keep adding more info (remember that the current implementation is very limited and will be extended).
I'd also start with a paragraph that gives as quick gist of the feature and an example. Only below that I'd add layers of detail: exact syntax, where it can be used, what expressions are allowed, behavior in corner cases.
Finally, minor details, like whether the identifiers are reserved should go into some note at the very end, because it's almost at the level of implementation detail and irrelevant to most users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also comment in #14770 that this bit will have to be updated once we make layout
and at
keywords.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally, minor details, like whether the identifiers are reserved should go into some note at the very end, because it's almost at the level of implementation detail and irrelevant to most users.
Given that we are planning to make them reserved keywords, I'd even say we should put it in a warning, and not a note.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've used a note, but no problem changing to a warning.
Not sure what should be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess a warning is better, actually.
In the next example, contract ``C`` inherits from contracts ``A`` and ``B`` | ||
and also specifies a custom storage base slot. So, inherited state variables, | ||
starting from the top level contract ``A`` will be stored from slot ``42``. | ||
Thus, state variable ``y``, for instance, will be at slot ``43``, while | ||
state variable ``flag`` will be at slot ``45``. | ||
|
||
.. code-block:: solidity | ||
|
||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity >=0.8.29 <0.9.0; | ||
contract A { | ||
uint x; | ||
} | ||
|
||
contract B { | ||
address payable y; | ||
mapping (address => bool) public map; | ||
} | ||
|
||
contract C is A, B layout at 0x1234 { | ||
bool flag; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The details of how variables are laid out in presence of inheritance and how the layout specifier affects them should be explained in Layout of State Variables in Storage and Transient Storage. Here we should just generally say how it works and link to those details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you moved it but did not add a link.
Please link to the issue in PR description. |
Contracts can define an arbitrary base slot for its own storage. | ||
The contract's state variables will be stored from the specified slot | ||
instead of the default slot zero. | ||
This can be done by using ``layout at <base-slot-expression>`` in the header of | ||
a contract definition, either after or before the inheritance specifier. | ||
It can be given at most once. | ||
The ``base-slot-expression`` must be an expression consisting of number literals | ||
that can be evaluated at compile time and yield in a rational value in the range of ``uint256``. | ||
As a general rule, any expression that can be used to specify the size of an array | ||
may also be used to indicate a slot. | ||
The layout of a contract without an explicit specification is identical to one with ``layout at 0``. | ||
The storage layout cannot be specified for abstract contracts, interfaces and libraries. | ||
The identifiers ``layout`` and ``at`` are not reserved keywords of the Solidity language, but | ||
it is strongly recommended to avoid using them since that may change in the future. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally, minor details, like whether the identifiers are reserved should go into some note at the very end, because it's almost at the level of implementation detail and irrelevant to most users.
Given that we are planning to make them reserved keywords, I'd even say we should put it in a warning, and not a note.
a contract definition, either after or before the inheritance specifier. | ||
It can be given at most once. | ||
The ``base-slot-expression`` must be an expression consisting of number literals | ||
that can be evaluated at compile time and yield in a rational value in the range of ``uint256``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps another section describing that the max number of available storage slots will be (uint256::max - user defined base slot address + 1), and that a revert will be triggered in case an attempt is made to exceed the maximum number of available slots.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a revert. A compilation error.
Co-authored-by: Kamil Śliwak <[email protected]> Co-authored-by: Nikola Matić <[email protected]>
803a4b2
to
74f84d3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this page still says that the storage always starts at zero:
Except for dynamically-sized arrays and mappings (see below), data is stored contiguously item after item starting with the first state variable, which is stored in slot ``0``. For each variable,
Please also check if there's anything in the rest of the text that needs updating too.
As the previous example shows, this can be done by using ``layout at <base-slot-expression>`` | ||
in the header of a contract definition. | ||
|
||
The layout specifier can given at most once and can be either after or before the inheritance specifier. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The layout specifier can given at most once and can be either after or before the inheritance specifier. | |
The layout specifier can be placed either before or after the inheritance specifier, and at most once. |
|
||
The layout specifier can given at most once and can be either after or before the inheritance specifier. | ||
The ``base-slot-expression`` must be an :ref:`integer literal<rational_literals>` expression | ||
that can be evaluated at compile time and yield in a value in the range of ``uint256``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that can be evaluated at compile time and yield in a value in the range of ``uint256``. | |
that can be evaluated at compile time and yield a value in the range of ``uint256``. |
Could even change to must yield a value in the range of uint256, otherwise a compilation error will occur
.
The ``base-slot-expression`` must be an :ref:`integer literal<rational_literals>` expression | ||
that can be evaluated at compile time and yield in a value in the range of ``uint256``. | ||
|
||
In the case of a a custom storage layout specification which places the contract near the storage end, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the case of a a custom storage layout specification which places the contract near the storage end, | |
In the case of a custom storage layout specification which places the contract near the storage end, |
The storage layout cannot be specified for abstract contracts, interfaces and libraries. | ||
Also, it is important to note that it does **not** affect transient state variables. | ||
|
||
.. note:: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Promote to warning? We're almost certainly going to reserve these for 0.9.0.
In the following example, contract ``C`` inherits from contracts ``A`` and ``B`` and also | ||
specifies a custom storage base slot. | ||
The result is that all variable storage slots of the inherent tree will be adjusted according to | ||
the value specified by ``C``. | ||
So, the inherited state variable ``x`` will be stored at the base slot ``0x1234``, followed by | ||
``y`` and ``map`` and terminating with ``flag``. | ||
Notice that, although their storage slots changes, their position in relation to each other | ||
remains the same, following the C3-linearized order. | ||
|
||
.. code-block:: solidity | ||
|
||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.29; | ||
|
||
contract A { | ||
uint x; | ||
} | ||
|
||
contract B { | ||
address payable y; | ||
mapping (address => bool) public map; | ||
} | ||
|
||
contract C is A, B layout at 0x1234 { | ||
bool flag; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit odd to add an example that addresses just the storage layout and nothing else. I think it would be better to have one that shows most the elements described above. I'd add more variables to it and after just show what the resulting storage slots, offsets and sizes are.
The example should incorporate:
- storage, transient storage, constants and immutables live in separate spaces
- packing
- arrays and structs start a new slot and items after them as well
- elements of structs/arrays
- inherited variables are packed too
- multiple inheritance
- custom layout
Part of #597/#15727.