This article will show some sample code for how to use the “shift right” and “shift left” (`shr` and `shl`, respectively) opcodes in Solidity’s assembly language in the context of manipulating bytes.
Our example case will be extracting a function’s arguments from a contract’s calldata. This functionality is both common and practical; in fact, the source code we will look at is a core piece of Optimism’s Canonical Transaction Chain contract.
The calldata that we will be using for reference looks like this(full data available on chain):
The first thing you will notice is that
shr with an integer and a
calldataload function passed into it.
calldataload accepts a location in the calldata as an argument (4) and returns the subsequent 32 bytes. So we have this section selected:
Which yields this bytes32 object:
One byte is 2 characters, so the length as a string would be 64.
To interpret a data type smaller than 32 bytes (like uint40, which is 5 bytes) the compiler simply reads from the right:
Without shifting, the operation would yield a return value equaling the integer value of the hex object
0x0000030000, or 196608.
The integer argument that
shr accepts is the number of **bits **to *shift *the 32 byte block to the *right *by. Because there are 8 bits in a byte, it equals a 27 byte shift.
Visualizing a shift of 3 bytes
But because the object still needs to be 32 total bytes, the side that it is shifting from will be padded with zeroes. The uint40 interpretation of
shr(216, calldataload(4)) would then be:
0x00000301d6c (which you will recognize as the first 10 bytes of the bytes32 object loaded from
calldataload(4)) or, as an integer, 3153260.
shl is the same thing, except the bytes32 object gets shifted left in the window instead of right.
Applying the same calculations to the rest of the variables yields the following results:
shouldStartAtElement = 3153260 totalElementsToAppend = 113 numContexts = 10
These values are verifiable using the Optimism block explorer. Batch 23718 indeed consists of 113 elements and links to the L1 block in question. Its first transaction is exactly one higher than the
shouldStartAtElement, which means it is the next to be recorded.