The following a code snippet from a Clone.sol. In this code, the function _getArgAddress returns the address value from storage.

function _getArgAddress(
        uint256 argOffset
    ) internal pure returns (address arg) {
        uint256 offset = _getImmutableArgsOffset();
        // solhint-disable-next-line no-inline-assembly
        assembly {
            arg := shr(0x60, calldataload(add(offset, argOffset)))

What I don't understand, is how does shr read from storage? AFAIK, shr is just a logical right shift. SHR

1 Answer 1


You're right,shr is only a logical right shift operator in Solidity's inline assembly. The shr operation in the code provided is not reading the address argument from storage, but rather the code line where it's utilized is loading the address argument from calldata and aligning it to the right-most part of the 32-byte word as follows:

  1. add(offset, argOffset): This operation adds the offset, which is the starting position of the packed immutable arguments in calldata, and the argOffset, which is the position of the desired address argument within the packed immutable arguments.

  2. calldataload(add(offset, argOffset)): This operation reads the data from the calldata at the calculated position. Calldata is the input data for the current function call, and calldataload reads a 32-byte word from it at the specified position. Since Ethereum addresses are 20 bytes long, the returned value will have the address in the last 20 bytes of the 32-byte word.

  3. shr(0x60, calldataload(add(offset, argOffset))): The shr operation is used to shift the 32-byte word to the right by 0x60 (96) bits, aligning the 20-byte address to the rightmost part of the word. This is necessary because Ethereum addresses are 20 bytes long, and they should be right-aligned in a 32 bytes word.

Regarding how Clone.sol works, it is adapted from ClonesWithImmutableArgs which replaces storage loads with calldata reading which is far cheaper. Immutable arguments are stored in the code region of the created proxy contract, and whenever the proxy is called, it reads the arguments into memory, and then appends them to the calldata of the delegate call to the implementation contract.

Clone.sol contains a collection of helper functions to be able to read different types of immutable arguments.

The storage loads are replaced with calldata reading in the following ways:

  1. Instead of using sload to read data from storage, the functions use calldataload to read data directly from calldata. This opcode reads 32 bytes from the calldata at the specified offset.

  2. Instead of computing the storage slot, an offset in calldata is used to locate the desired argument. The offset is computed using the _getImmutableArgsOffset() function, which calculates the offset of the packed immutable arguments in calldata.

  3. Since calldata stores the arguments in a packed format, you may need to apply bit-shifting operations to extract the desired data. For example, in _getArgAddress, shr(0x60, calldataload(add(offset, argOffset))) is used to shift the data right by 0x60 (96) bits to extract the 20-byte address. Similarly, in _getArgUint64, shr(0xc0, calldataload(add(offset, argOffset))) is used to shift the data right by 0xc0 (192) bits to extract the 8-byte uint64 value.

  • thanks for this, but so you're saying that the calldata already has the address in it? ie: The proxy contract appends the calldata before calling the _getArgAddress function? Commented Mar 16, 2023 at 19:24
  • 1
    Yes, the calldata already has the address argument in it. The proxy contract appends the immutable arguments into the calldata that is passed to the implementation. I think _getArgAddress is meant to be used to decode the calldata received on the implementation side to extract those passed arguments. Commented Mar 16, 2023 at 22:02

Your Answer

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

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