3

Given the following function:

function getArrayItems(bytes calldata) external returns (
    bytes4 _signature,
    bytes32 _wutIsThis,
    bytes32 _length,
    bytes32 _firstItem
) {
    assembly {
        _signature := calldataload(0x00) // trim the first 4 bytes
        _wutIsThis := calldataload(0x04)
        _length := calldataload(0x24)
        _firstItem := calldataload(0x44)
    }
}

I get the following output when I run the code in remix with the input 0xcafe:

{   
    "0": "bytes4: _signature 0x38626302",
    "1": "bytes32: _wutIsThis 0x0000000000000000000000000000000000000000000000000000000000000020",
    "2": "bytes32: _length 0x0000000000000000000000000000000000000000000000000000000000000002",
    "3": "bytes32: _firstItem 0xcafe000000000000000000000000000000000000000000000000000000000000"
}

I fully understand the 1st, 3rd and 4th items. _length was tricky, but according to the docs:

The length of a dynamic array is stored at the first slot of the array and followed by the array elements.

It's the 2nd item, _wutIsThis, that is causing me trouble. My hunch is that it's about the data location of the array. That is, data is offset by 0x0000000000000000000000000000000000000000000000000000000000000020, which means that the first item of the array is located 32 bytes after the said location's position in calldata (I know, this is so meta).

Is that correct? If yes, why do we do this and where can I find some further documentation? The solidity docs on type data location are a bit vague and only explain that reference types must specify a keyword (memory, storage or keyword).

1 Answer 1

3

Is that correct? If yes, why do we do this and where can I find some further documentation?

Yes, you are correct.

Transactions are encoded based on the Contract ABI Specification. It is hard to get through, but these docs have all the answers to your question.


The transaction in question is passing in a dynamic parameter (bytes) as opposed to a static one (uint, address, etc.). When encoding the parameters, the EVM looks to see if the parameters are static or dynamic.

Static parameters are encoded in a fairly straightforward manner--they are converted to their hex representation and then concatenated into the input data hex string.

Dynamic values are more interesting. Use this section of the docs to fully understand, but the idea is that the encoded data is the location of the data. The encoded data itself is then concatenated to the end of the input data hex string, after all of the dynamic types. For dynamic types, the length of the parameter is then included, followed by the data itself.

Breaking down the transaction you posted:

0x3862630200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002cafe000000000000000000000000000000000000000000000000000000000000

0x38626302

This is the method ID of the function called (in this case, getArrayItems(bytes))

0x0000000000000000000000000000000000000000000000000000000000000020

This is the location of the first (dynamic) parameter. This is the location where calldata lives, but is not the data itself.

0x0000000000000000000000000000000000000000000000000000000000000002

This is the length of the dynamic calldata. In this case, the length is 2 because of the way bytes works. The EVM sees your 0xcafe as [0xca],[0xfe], which is why the length is 2 even though there is only one parameter passed in.

cafe000000000000000000000000000000000000000000000000000000000000

The data passed into calldata.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.