EVM – Why Does mload(0x40) Result in 0x20?

assemblyevmopcodeyul

I ran the code below and had a question.

function mloadTest1() public pure returns(bytes memory) {
    bytes memory a;

    assembly {
        a :=mload(0x40)
    }

    return a;
}

mload(x) is a Yul code that reads 32 bytes from offset x in memory. And Solidity defaults to free memory points from 0x00 to 0x60, with 0x80 stored at 0x40.

So I think the value of the last return a in the above code should be 0x80, but the result was 0x20, which I don't understand.

Can anyone explain these results?

Best Answer

Unlike value types like bytesN or uintN reference types (denoted by their memory, calldata or storage annotation) are merely pointers to their underlying data.

When you use inline-assembly to set a what you are setting is actually the pointer of that variable not the value of the byte-string itself. As you've stated correctly the free memory pointer at 0x40 initially holds 0x80, meaning when you do a := mload(0x40) you're essentially saying "the bytes variable a now points to the memory offset 0x80.

Considering that the memory is empty and the offset that a bytes object points to holds its length, Solidity interprets this as a bytes of length 0 because the word (32-byte segment) at 0x80 is 0.

When you then return the bytes Solidity proceeds to ABI encode it. Return values are encoded as a tuple of values, in this case a tuple with a single bytes: (bytes,). For variable length values such as bytes ABI encoding first encodes the offset in the return data, before encoding the length and actual data at that offset.

So the data you're getting should be interpreted as follows:

0x

offset of the first `bytes` value (32 bytes in the return data):
0000000000000000000000000000000000000000000000000000000000000020
at the encoded offset (32) the actual length of the `bytes` (0):
0000000000000000000000000000000000000000000000000000000000000000
Related Topic