bytes1, bytes16, bytes32


bytes[n] are fixed length byte arrays, as opposed to bytes that are variable length byte arrays. The storage of bytes[n] in storage is such that it takes up a slot when it is of bytes32 and is packed in cases of bytes below 32. It is very similar to the uint type.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Yul {
    bytes1 public a = 0xaa;
    bytes1 public b = 0xbb;
    bytes1 public c = 0xcc;
    
    function getBytes1() public view returns (bytes32) {
        assembly {
            let val := sload(0x00)
            mstore(0x80, val)
            return(0x80, 0x20)
        }
    }
}

Returns 0x0000000000000000000000000000000000000000000000000000000000ccbbaa, just like in uint's case.

However, there is some bit of caution to be proceeded with when dealing with bytes[n]. Take a look at these three pieces of code written using bytes4.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Yul {
    bytes4 public a = 0xaabbccdd;
    
    function getBytes4() public view returns (bytes32) {
        assembly {
            let val := sload(0x00)
            mstore(0x80, val)
            return(0x80, 0x20)
        }
    }
}

A call to the getBytes4 function would return 0x00000000000000000000000000000000000000000000000000000000aabbccdd, which is what we expect, according to what we have learnt.

Take a look at the second one.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Yul {
    bytes4 public a;
        
    function setAndReturnBytes4() public returns (bytes32) {
        bytes4 f = 0xaabbccdd;

        assembly {
            sstore(0x00, f)
            mstore(0x80, sload(0x00))
            return(0x80, 0x20)
        }
    }
}

This will return 0xaabbccdd00000000000000000000000000000000000000000000000000000000. You can find this by checking the Remix Console, expanding the transaction and checking the decoded output object. This is wrong, and will set the value of a to 0x00000000.

And a look at the final one.

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Yul {
    bytes4 public a;
        
    function setAndReturnBytes4() public returns (bytes32) {
        assembly {
            sstore(0x00, 0xaabbccdd)
            mstore(0x80, sload(0x00))
            return(0x80, 0x20)
        }
    }
}

This will return 0x00000000000000000000000000000000000000000000000000000000aabbccdd.

Setting a bytes[n] variable in a function will right-pad it to 32 bytes. Whereas, directly assigning the variable in a Yul block or in storage will handle it normally by left padding it.

To get a knowledge of which type of bytes[n] does what, refer to this part of the book.

bytes16

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Yul {
    bytes16 public a;
        
    function setAndReturnBytes16() public returns (bytes32) {
        assembly {
            sstore(0x00, 0x0011223344556677889900aabbccddeeff)
            mstore(0x80, sload(0x00))
            return(0x80, 0x20)
        }
    }
}

Returns 0x0000000000000000000000000000000011223344556677889900aabbccddeeff.

bytes32

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

contract Yul {
    bytes32 public a;
        
    function setAndReturnBytes32() public returns (bytes32) {
        assembly {
            sstore(0x00, 0x003344556677889900aabbccddeeff0011223344556677889900aabbccddeeff)
            mstore(0x80, sload(0x00))
            return(0x80, 0x20)
        }
    }
}

Returns 0x003344556677889900aabbccddeeff0011223344556677889900aabbccddeeff.