Noxx - EVM Deep Dives

Part 1

This article should be a review for you. It explains function selector step and step and gives an instance in EVM Playground so you can follow along:

Click "Run" and then "Step into" to execute one instruction at a time:

Part 2

This article explains memory layout and free memory pointer.

Memory layout

Solidity memory layout, quoted from Solidity doc:

Solidity’s memory layout reserves four 32-byte slots:

  • 0x00 - 0x3f (64 bytes): scratch space

  • 0x40 - 0x5f (32 bytes): free memory pointer

  • 0x60 - 0x7f (32 bytes): zero slot

I made a diagram for easier memorization:

Memory expansion

While walking through the EVM playground above you may have noticed a few strange occurrences. First, when we wrote a single byte 0x22 using MSTORE8 to memory location 32 (0x20) the memory changed from

to

You may ask the question, what’s with all the additional zeros we only added 1 byte?

When your contract writes to memory, you have to pay for the number of bytes written. If you are writing to an area of memory that hasn't been written to before, there is an additional memory expansion cost for using it for the first time.

Memory is expanded in 32 bytes (256-bit) increments when writing to previously untouched memory space.

Memory expansion costs scale linearly for the first 724 bytes and quadratically after that.

Above our memory was 32 bytes before we wrote 1 byte at location 32. At this point we began writing into untouched memory, as a result, the memory was expanded by another 32-byte increment to 64 bytes.

Note that all locations in memory are well-defined initially as zero which is why we see 2200000000000000000000000000000000000000000000000000000000000000 added to our memory.

Part 3

This part covers storage.

Data Structure

Contract storage is abstracted as a 32-byte to 32-byte mapping. That means there is 2256 2^{256}storage slots and each slot is a 256-bit wide chunk. The number of slots is a huge number, therefore if the slot contains 0 then it is not stored by the nodes of the network. This is why setting a state variable back to 0 would grant you gas refund.

Diagram of storage:

Contract variables that are declared as storage variables can be split into 2 camps fixed-size and dynamic-size.

If you have worked through Ethernaut, fixed-size variables storage layout should be a piece of cake -> slot packing.

To learn more about dynamic size variables refer to the “Program the Blockchain” post.

EVM Storage Opcodes

SSTORE

We’ll start with SSTORE it takes in a 32-byte key and a 32-byte value from the call stack and stores that 32-byte value at that 32-byte key location. Check out this EVM playground to see how it works.

SLOAD

Next, we have SLOAD which takes in a 32-byte key from the call stack and pushes the 32-byte value stored at that 32-byte key location onto the call stack. Check out this EVM playground to see how it works.

The question you should be asking yourself at this stage is if SSTORE and SLOAD only deal in 32-byte values how can you extract a variable that has been packed into a 32-byte slot. We will be answering this question soon.

Storing & Retrieving Packed Variables

This part is pretty diffcult to follow. Focus, focus, focus.

Sample code:

Remember that hexadecimal numbers are ultimately seen as binary numbers by the machine. This is important as a number of bitwise operations are used in slot packing:

Next noxx covered AND, OR, NOT. You should be very familiar with these.

Storing value1 = 1 is trivial. Let's focus on value2 = 22. The logic for value3 and value4 is the same as value2.

The computation will be using the following values:

The first thing the EVM does is use the EXP opcode which takes in a base integer and an exponent and returns the value. Here we use 0x0100 as the base integer which represents a 1-byte offset and raise it to exponent 0x04 which is the start position for value2. The image below shows why the returned value is useful:

We can’t use this value however as it would overwrite value1 which has already been stored. This is where bitmasks are utilized:

Here’s another example to crystalise what is happening. This is the same process but looking at what would happen if all 4 values had already been stored and we wanted to update value2 from 22 to 99. Look out for the existing 0x016 value being zeroed out:

Bitwise OR can combine the values we have:

Part 4

Part 5

Part 6

Last updated