-
Notifications
You must be signed in to change notification settings - Fork 6.3k
Clarify storage layout. #10860
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
Clarify storage layout. #10860
Conversation
6f88691 to
e4e3434
Compare
| by the above rules, state variables from different contracts do share the same storage slot. | ||
|
|
||
| The elements of structs and arrays are stored after each other, just as if they were given explicitly. | ||
| The elements of structs and arrays are stored after each other, just as if they were given |
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.
Does stored after each other mean stored in separate storage slots or packed tightly as per rules above?
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 hoped that "just as if they were given..." would clarify that question...
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.
Hmm... it is still a bit vague at least for me. Perhaps just as if they were declared outside the struct?
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.
Actually, I also still find this sentence a bit confusing... isn't this covered by the "rules above" - or respectively wouldn't it be covered, if the contiguous items that need less than 32 bytes in front of those rules was dropped?
| Due to their unpredictable size, mapping and dynamically-sized array types use a Keccak-256 hash | ||
| computation to find the starting position of the value or the array data. | ||
| These starting positions are always full stack slots. | ||
| Due to their unpredictable size, mappings and dynamically-sized array types cannot be stored |
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.
| Due to their unpredictable size, mappings and dynamically-sized array types cannot be stored | |
| Due to their unknown size, mappings and dynamically-sized array types cannot be stored |
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.
Hm, not sure about this. If you don't know the size of something but can put a reasonable upper bound on it, you might still store it in-place.
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 mappings and dynamically-sized arrays isn't the reason they are not stored in between because their size is unknown (because this value set at runtime)? I agree that in the hypothetical case where some data structure is bounded but exact size unknown, one could still store "in between" but that isn't the case here iiuc
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.
What about "Since their size is not fixed, mappings..."?
docs/internals/layout_in_storage.rst
Outdated
| So for the following contract snippet | ||
| the position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``:: | ||
|
|
||
| For mappings, the slot stays empty, but it is needed so that two mappings after each other will use a |
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.
Stays empty meaning zero initialized?
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.
Yes, but this is undefined. It's just not used for anything.
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.
Should we include this info in the docs? Something like reading from this slot is undefined?
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 think this paragraph is enough documentation.
docs/internals/layout_in_storage.rst
Outdated
| rule recursively. The location of ``x[i][j]`` for ``x`` being ``uint32[][]`` is | ||
| computed as follows (again, assuming ``x`` itself is stored at slot ``p``): | ||
| The slot is ``keccak256(keccak2569(p) + i) + floor(j / 8)`` (8 items per slot) and | ||
| the element can be obtained from the slot data ``v`` using ``(v >> ((j % 8) * 2)) & 0xffff``. |
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.
Let's say the uint32 values are 1, 2, 3. Each value is stored in 4 byte groups (32-bit) so the last 12 bytes look like 0x000100020003. So, isn't the formula to obtain data (v >> ((j % 8) * 8)) & 0xffffffff?
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.
Right! Is the forumla useful?
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.
Depends. If an "advanced" user wants to exploit this to reduce gas usage, maybe?
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 useful, I'm trying to look at internal contract state /nonpublic storage on chain so understanding that formula is necessary if you want to introspect values smaller than 32bytes
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.
Hi I have a question.
what does v in (v >> ((j % 8) * 2)) & 0xffff refer to?
Also, why are we &ing with 0xffff? Is not anding with 0xffff same as the value that we are anding?
docs/internals/layout_in_storage.rst
Outdated
|
|
||
| The value of a mapping key | ||
| ``k`` is located at ``keccak256(k . p)`` where ``.`` is concatenation. The hash is always a hash of | ||
| 64 bytes. If the key type is shorter, it is padded with zeros according to its alignment. |
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.
| 64 bytes. If the key type is shorter, it is padded with zeros according to its alignment. | |
| 64 input (`k.p`) bytes. If the key type is shorter, it is padded with zeros according to its alignment. |
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.
Maybe the whole sentence should be rephrased - we should not use k in keccak256(k . p) if it is modified in some cases.
docs/internals/layout_in_storage.rst
Outdated
| computed to compress it to 32 bytes. | ||
|
|
||
| If the mapping value is a | ||
| non-value type, the positions are found by adding an offset to ``keccak256(k . p)``. |
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.
might be worth putting a ref to https://docs.soliditylang.org/en/v0.8.1/types.html#reference-types for convenience here, I had to google to make sure I knew what a non-value type was (though I was definitely assuming it was non-primitives e.g. uint etc)
|
This is much much better now, you addressed pretty much all of the concerns I had in the issue / confusing bits. This is much easier to follow, especially the part about |
|
I'm still not sure / a bit confused around "adding an offset" for non value types. Maybe the example should add nested structs as well, e.g. for a sample contract like so am I right in my understanding that the location for
|
0c06f01 to
a9dfc53
Compare
|
Made some modifications, please re-review. |
|
@relyt29 yes, your computation is correct. |
bshastry
left a comment
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.
Took a quick look at the text part and it looks good to me (tiny change suggested). Would be nice to have an ack from @relyt29 as well.
4a25027 to
a89322b
Compare
ekpyron
left a comment
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.
Some minor comments, but generally it reads good and is definitely an improvement! (So I'll approve regardless of those minor comments)
| 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, | ||
| a size in bytes is determined according to its type. |
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.
| a size in bytes is determined according to its type. | |
| its size in bytes is determined according to its type. |
?
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.
is 32 the size of a mapping?
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.
Fair enough... the required size? Just "a size" sounds a bit weird to me. But also fine ;-).
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.
Yeah, I even wanted to suggest the size myself but I saw that @ekpyron already made a suggestion so I removed it.
It could say something like the size of the space taken in the contiguous block but that's way too long.
| by the above rules, state variables from different contracts do share the same storage slot. | ||
|
|
||
| The elements of structs and arrays are stored after each other, just as if they were given explicitly. | ||
| The elements of structs and arrays are stored after each other, just as if they were given |
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.
Actually, I also still find this sentence a bit confusing... isn't this covered by the "rules above" - or respectively wouldn't it be covered, if the contiguous items that need less than 32 bytes in front of those rules was dropped?
docs/internals/layout_in_storage.rst
Outdated
| non-value type, the location is the starting slot of the data. If the value is of struct type, | ||
| for example, you have to add an offset corresponding the the struct member to reach the member. | ||
|
|
||
| Let us compute the storage location of ``data[4][9].c`` in the following contract. |
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.
Maybe it's nicer to say
As an example, consider the following contract:
<Contract>
Let us compute the storage location of...
otherwise one needs to look/scroll down and then move back to understand this... but maybe fine...
e8a9a3f to
77c7afd
Compare
|
Rebased. |
docs/internals/layout_in_storage.rst
Outdated
| The value corresponding to a mapping key ``k`` is located at ``keccak256(h(k) . p)`` | ||
| where ``.`` is concatenation and ``h`` is a function that is applied to the key depending on its type: | ||
|
|
||
| - for value types, ``h`` pads the value to 32 bytes according to its alignment |
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.
We could clarify this a bit more:
| - for value types, ``h`` pads the value to 32 bytes according to its alignment | |
| - for value types, ``h`` pads the value to 32 bytes according to its alignment (value types are generally right aligned except for fixed bytes types). |
BTW, is there anything other than bytesXX that is left-aligned?
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.
function types, I think.
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.
Is that documented anywhere? Or is it considered an implementation detail?
Actually this document does not mention function types at all and I don't think we ever explain how much space they take (at least not Types > Function Types). They don't take a full slot, right?
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.
We discussed this on Discord. We do not have the size of function type documented anywhere (and maybe some other value types too, we need to check) but that's a separate issue.
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.
Reapproving. (There's still https://github.com/ethereum/solidity/pull/10860/files#r569580367, but I'd be fine with merging as is.)
EDIT: Oh, wow, a few of @cameel's points just now are worth fixing, though!
26eb07b to
1db87bd
Compare
|
These two are still open: |
827035f to
514340f
Compare
514340f to
61b5e8e
Compare
Fixes #10859