Skip to content
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

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from

Conversation

matheusaaguiar
Copy link
Collaborator

@matheusaaguiar matheusaaguiar commented Feb 26, 2025

Part of #597/#15727.

@matheusaaguiar matheusaaguiar added this to the 0.8.29 milestone Feb 26, 2025
@matheusaaguiar matheusaaguiar marked this pull request as draft February 26, 2025 15:52
@matheusaaguiar matheusaaguiar force-pushed the storageLayoutSpecifierDocs branch from 50ba958 to 88792ee Compare February 26, 2025 17:14
@matheusaaguiar matheusaaguiar self-assigned this Feb 26, 2025
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

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.

Comment on lines 9 to 22
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.
Copy link
Member

@cameel cameel Feb 27, 2025

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.

Copy link
Member

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.

Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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.

Comment on lines 40 to 61
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;
}
Copy link
Member

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.

Copy link
Member

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.

@cameel
Copy link
Member

cameel commented Feb 27, 2025

Please link to the issue in PR description.

Comment on lines 9 to 22
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.
Copy link
Collaborator

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``.
Copy link
Collaborator

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.

Copy link
Member

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.

matheusaaguiar and others added 2 commits March 4, 2025 18:49
@matheusaaguiar matheusaaguiar force-pushed the storageLayoutSpecifierDocs branch from 803a4b2 to 74f84d3 Compare March 4, 2025 21:49
Copy link
Member

@cameel cameel Mar 5, 2025

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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``.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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::
Copy link
Collaborator

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.

Comment on lines +42 to +67
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;
}
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants