<aside> 📘 Relates:
Solidity layout and access of storage state variables simply explained
Solidity delegatecall usage and pitfalls
Understanding Smart Contract Vulnerabilities
</aside>
The Storage is like a key-value data structure that holds the state variables values of a Solidity smart contract.
Thinking of the storage as an array will help us understand it better. Each space in this storage “array” is called a slot and holds 32 bytes of data (256 bits). The maximum length of this storage “array” is 2²⁵⁶-1, so we can fit a lot of elements into it.
Each state variable declared in the smart contract will occupy a slot depending on its declaration position and its type.
For example, the following StorageLayout
contract has only 1 state variable, id
, since it’s the first one, it will be stored in slot index 0 of the storage.
contract StorageLayout {
uint256 public id = 543;
}
Take a look at the following example:
contract StorageLayout {
uint256 public id = 543;
uint256 public count;
address public owner;
}
The state variable id
is located at storage slot index 0 because it’s the first declared. count
is at slot index 1 because is the second. owner
is at slot index 2 because it’s the third declared. And so on.
We could represent it as:
storage[0] = 543; // 0x000000000000000000000000000000000000000000000000000000000000021F
storage[1] = 0;
storage[2] = 0x0;
We see that we are assigning the number 543
to the index 0 of the storage. All the data in the storage is saved as bytes, so, converting the decimal value 543
to hexadecimal byte format would be 021f
, left padded to 32 bytes, that’s why I’m showing:
0x000000000000000000000000000000000000000000000000000000000000021f
Which is how the 543
number would be represented in storage.
We also see that not assigning a value to a state variable is equivalent to assigning its default or zero value based on the type; for a string
type, the zero value would be represented as an empty string. For an address
type the value would be represented as the zero address 0x0000000000000000000000000000000000000000
, 40 zeros or 20 empty bytes. For a uint256
it would be a 0, or 64 zeros if represented as bytes. For a bool
type the zero value would be represented as false, which is 0x00
, one byte long for this type. And so on.
All the values in the storage are saved ABI-Encoded and when retrieving the value using its variable they are decoded automatically.
We could use web3.js
to access the storage for any contract directly, doing something like this:
For example, let’s call this contract that is deployed in the testnet Rinkeby at: 0x8Aa5C5B74F35a1cB01631bCA24D995d369670E60