By Fran Palacios
In this article, we’re going to delve a bit deeper into persistent storage on EVM-compatible blockchains1. In other languages, it’s not necessary to know what happens at a low level in order to write efficient code, but in the case of Solidity (which is not quite a high-level language), it is important – though not essential – to know this, because operations cost gas.
Gas is the unit that measures the amount of computational effort required to execute operations on the blockchain. In Ethereum, we have a transactional model, and each transaction requires computation to be executed and validated, so a fee (gas) must be paid. In this table fragment, we can see the cost associated with each operation (opcode); we can see that accesses to Memory (MLOAD & MSTORE) are much cheaper than accesses to Storage (SLOAD & STORE).
Opcodes table: https://github.com/crytic/evm-opcodes
In Solidity, there are two types of storage: volatile (Memory), which can be thought of as RAM, and persistent (Storage), which can be thought of as a hard drive.
|
|
|
|
All Smart Contracts running on the EVM have a permanent Storage associated with them, which is part of the blockchain’s global state. This Storage can be seen as a list of key-value pairs ordered by their declaration in the Smart Contract, with each element being 32 bytes in size, and a total size of 2^256. While the Storage is large enough to store anything, it initially costs nothing since it’s all set to 0. In Ethereum, you pay for everything that’s not 0 and for memory expansions.
The order in which variables are declared is relevant in Solidity, and they’re usually not grouped by domain or other criteria, but by size and type for the EVM’s convenience.
Let’s take a look at this contract.
|
|
To inspect the state of the Storage, we can use the getStorageAt function provided by the provider (in this case, we’ll use ethers). We pass it the address of a contract and the memory slot of the data we want to retrieve. This function allows us to examine the contents of the Storage at a specific slot, which can be useful for debugging and analyzing Smart Contracts on the blockchain.
|
|
This piece of code prints out to the console the first 5 slots in the contract storage.
|
|
As expected, the number 5 was stored in position 0 and True in position 1.
If we change the data type of favNumber
from uint256
to uint8
we can see how they share a memory slot:
|
|
We can see that the size and type of variables must be taken into account. The rules for declaring variables efficiently are defined in the Solidity documentation.
Now let’s see some examples using arrays (fixed-size and dynamic) and mappings2.
Fixed-size arrays are stored in consecutive positions. We can verify this using the same method as before:
|
|
We see that the data stored at slots 2, 3 and 4 are the hexadecimal values of 10, 20 and 30 (0x00..a, 0x00...14 and 0x00...1e)
|
|
And what about dynamic arrays? How does the EVM know how many slots to reserve between variables?
Well, it doesn’t know 😅, and to solve this it uses a hash function keccak256, to find a memory position and not overwrite other slots. Let’s change the code a bit to add a dynamic array. Reemplazamos uint256[3] myArray
con uint256[] myArray
. The rest of the code remains the same with the same 3 elements.
|
|
We can see that they’re gone,but in position 2 we have stored the number 3, which represents the length of the array and gets updated as elements are added. However, the slot where it is stored remains the same, which is slot 2. To calculate the position from which the array data is stored, the function keccak256(slot-where-the-array-length-is-stored)
is used. Ethers provides us with this function:
We know that it is in slot 2 (0x2 in hexadecimal). With hexZeroPad
, we fill in 0s to the left until we reach 32 bytes (64 zeros).
|
|
Now, if we call getStorageAt
with that slot, we will get the first item stored in the array:
|
|
We see that the hexadecimal value of 10, our first element! Now the next elements are stored in a sequentiall order. So if we add 1 to the position of the first element we get the second.
|
|
Which is 20 in hex. And the third element is in the next one:
|
|
Finally, we will see an example with mappings.
|
|
In this output, we see that slot 2 is completely empty, unlike the dynamic array, here the length of the mapping is not stored because it is not iterable, and its keys do not have to be consecutive.
|
|
However, to calculate the memory position to store the data, it does something similar to arrays. In this case, the formula to apply is this: keccak256(h(k) . p)
. Where h
is a function based on the type of the key, k
is the dictionary key, p
is the memory slot of the mapping, and .
is the concatenation of both.
|
|
As a curiosity about this function, it allows us to access the contents of a memory slot in a contract’s storage. If you think about it, you can see everything, as long as you have access to the contract, you can see in which slot something is stored.
As seen in the last example, there are public variables (by default) and private ones (those that have the private
3121prefix), so using this function, you can see them all if you have access to the contract code and simply count in which slot the variable is defined. So remember, never store sensitive data in the blockchain.
Ethereum Virtual Machine. It is an entity supported by the nodes that make up the network. We can see it as a distributed state machine. It does not share resources in terms of computing power with other nodes, but it does share global state. It is capable of executing code. ↩︎
It is a key-value dictionary, it is not iterable and its size is dynamic. Its syntax is (key-type ⇒ key-value-type) variable-name
. Mappings can be nested. ↩︎
Do you want more? We invite you to subscribe to our newsletter to get the most relevan articles.
If you enjoy reading our blog, could you imagine how much fun it would be to work with us? let's do it!
But wait a second 🖐 we've got a conflict here. Newsletters are often 💩👎👹 to us. That's why we've created the LEAN LIST, the first zen, enjoyable, rocker, and reggaetoner list of the IT industry. We've all subscribed to newsletters beyond our limits 😅 so we are serious about this.