Solidity

Solidity Layout

Solidity Layout Main Tips

  • Layout in Solidity is important when it comes to storing various data in storagememory, call data.
  • Knowing the layout is required to use different types of memory in Solidity efficiently!

Solidity Layout In Storage

Static size variables (that would include everything but the dynamically-sized array and mapping types) are laid out adjacently in storage beginning with position 0. Items needing less than 32 bytes get packed into one storage slot if it can be done, according to the following rules:

  • First item inside the storage slot will be stored aligned lower-order.
  • Types that are elementary and only use only so many bytes as is necessary to store them.
  • In case elementary types do not fit the remainder of the storage slot, they are moved to the following slot.
  • Array data and struct would always initiate a new slot, occupying it all (however, they are packed tightly as dictated by these rules)

Using reduced-size arguments is beneficial only when dealing with storage values since the compiler is going to pack multiple elements into a single storage slot, thus combining multiple writes or reads into one operation.

On the other hand, in case of dealing with memory values or function arguments, no inherent benefit is there as the compiler will not pack those values.

Finally, to allow this to be optimized by the EVM, make sure that you have ordered your struct members and storage variables in a way that allows them to be packed tightly. As an example, if you declare your storage variables in this order:

uint128, uint128, uint256

Instead of ordering them like this:

uint128, uint256, uint128

The first example will end up taking up two storage slots meanwhile the latter will take up three.

The struct and array elements get stored one after another as if provided explicitly.

Because of their unpredictable size, dynamic size array and mapping types utilize a computation of the Keccak-256 hash for finding the start position of the array data or the value. The starting positions are explicitly full storage slots.

The dynamic array or mapping themselves occupy a slot that is unfilled in storage at a certain position p depending on the rule above rule (alternatively, by recursive application of this rule for arrays of arrays or mappings to mappings). For an array that is dynamic, this slot would store the amount of element inside the array (strings and byte arrays here would be an exception, read further). For mappings, the slot remains unused (however, it is requied so that two similar mappings after each other would use not use the same hash distribution). Data of the array is located at keccak256(p) and corresponding value to the mapping key k‘s location is keccak256(k . p) where the . symbol signifies concatenation. In case the value again is a type that is not elementary, search for the positions is done by adding an offset of keccak256(k . p) .

bytes and strings data is stored in one slot along with the length, if they are short, that is. Particularly: when the data 31 bytes in length at most, it gets stored inside bytes that are higher-order (left aligned) while the lowest-order byte is mean for string length * 2 . In case it no longer is, the main slot would then store length * 2 + 1 with the data stored the way it normally is – inside keccak256(slot) .

Because of these reasons, the position that data[4][9].b is at keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1 :

Example

pragma solidity ^0.4.1;

contract Cont {
  struct structType { uint var1; uint var2; }
  uint var3;
  mapping(uint => mapping(uint => structType)) data;
}

 

Try on Remix Try live on Hosting

Warning: When elements that take up less than 32 bytes are used, the gas usage of your contract may become higher. That is because of the fact that the Ethereum Virtual Machine operates on 32 bytes at a time. For this reason, when the element is no bigger than that, the EVM has to utilize more operations so the size of that element is reduced from 32 bytes to the desired size.


Solidity Layout In Memory

Solidity normally reserves three slots of 256-bit:

  • 0 – 64: scratch space used for hashing methods
  • 64 – 96: size of the currently allocared memory (aka. the free memory pointer)

Scratch space viable for use between statements (when using inline assembly, for example).

Solidity will always place new objects at the free memory pointer so memory never becomes free (this feature will possibly change later).

Warning: Solidity provides some operations which need a temporary area of memory, which is larger than 64 bytes, subsequently not fitting into the scratch space. These are meant to for placing where the free memory points to, however, given how short their lifecycle is, the pointer does not get updated. The memory may potentially be zeroed out. For this reason, it shouldn’t expected that the free memory will be zeroed out.


Solidity Layout Of Call Data

Whenever contracts in Solidity are deployed and when they are called from an account, it is assumed that the input data is in the ABI specification format. The ABI specification would require arguments to be padded to 32 bytes multiples. Internal function calls utilize an alternative convention.

Read previous post:
Solidity Assembly Opcode Stream Generation

Solidity Assembly Opcode Stream Generation Main Tips In Solidity standalone assembly, the process of opcode stream generation is the point...

Close