Skip to content

Conversation

@chriseth
Copy link
Contributor

Fixes #10859

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
Copy link
Contributor

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?

Copy link
Contributor Author

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...

Copy link
Contributor

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?

Copy link
Collaborator

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
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor Author

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..."?

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
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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.

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

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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?

Copy link

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

Copy link

@sssubik sssubik Mar 25, 2022

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?


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

Choose a reason for hiding this comment

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

Suggested change
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.

Copy link
Contributor Author

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.

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

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)

@relyt29
Copy link

relyt29 commented Jan 28, 2021

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 p. Thanks

@relyt29
Copy link

relyt29 commented Jan 28, 2021

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. foo[4].bar[3].baz

for a sample contract like so

pragma solidity ^0.4.0;

contract C {
  struct Disco { 
          uint a; 
          uint b; 
          mapping(uint => Techno) bar;
  }
  struct Techno {
         uint g;
         uint baz; 
 }
  uint x;
  mapping(uint => Disco) foo;
}

am I right in my understanding that the location for foo[4].bar[3].baz should be (for all integer numbers below assume uint256(integer) so 31 bytes of leading zeros, . is concatenation + is arithmetic addition)

keccak ( 0x03 . ( keccak( 0x04 . 0x01 ) + 0x02) ) ) + 0x01

@chriseth chriseth force-pushed the clarifyStorageLayout branch from 0c06f01 to a9dfc53 Compare February 2, 2021 10:02
@chriseth
Copy link
Contributor Author

chriseth commented Feb 2, 2021

Made some modifications, please re-review.

@chriseth
Copy link
Contributor Author

chriseth commented Feb 2, 2021

@relyt29 yes, your computation is correct.

bshastry
bshastry previously approved these changes Feb 2, 2021
Copy link
Contributor

@bshastry bshastry left a 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.

ekpyron
ekpyron previously approved these changes Feb 3, 2021
Copy link
Collaborator

@ekpyron ekpyron left a 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.
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
a size in bytes is determined according to its type.
its size in bytes is determined according to its type.

?

Copy link
Contributor Author

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?

Copy link
Collaborator

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 ;-).

Copy link
Collaborator

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

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?

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

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...

@chriseth
Copy link
Contributor Author

chriseth commented Feb 3, 2021

Rebased.

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

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:

Suggested change
- 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

function types, I think.

Copy link
Collaborator

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?

Copy link
Collaborator

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.

ekpyron
ekpyron previously approved these changes Feb 3, 2021
Copy link
Collaborator

@ekpyron ekpyron left a 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!

@chriseth
Copy link
Contributor Author

chriseth commented Feb 4, 2021

cameel
cameel previously approved these changes Feb 4, 2021
@chriseth chriseth force-pushed the clarifyStorageLayout branch from 827035f to 514340f Compare February 4, 2021 15:12
cameel
cameel previously approved these changes Feb 4, 2021
@chriseth chriseth force-pushed the clarifyStorageLayout branch from 514340f to 61b5e8e Compare February 4, 2021 15:26
@chriseth chriseth merged commit 2fb2788 into develop Feb 4, 2021
@chriseth chriseth deleted the clarifyStorageLayout branch February 4, 2021 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docs: "Layout of State Variables in Storage" "Mappings and Dynamic Arrays" paragraph incredibly confusing and poorly written

7 participants