Solidity – Efficient Bit Packing Techniques

memorysolidity

I have a struct with the following variables:

struct contract {
        address customer;
        uint8 tokentype; // 5 token types
        uint8 size;  // 5 sizes
        bool  gender; // 2 types
        bool  active;  // 2 types
    }

I'm not a computer scientist, but I think bools are 2 bits, and when I have 5 cases, I suppose that's 5 bits.

To save on gas and memory, I could store them as a uint instead of separate variables, so that each digit slot would reflect one item: {tokentype, size, gender, active} –> {digit4, digit3, digit2, digit1}. Thus 4411, would be the largest possible number for my set of cases, while 0 the smallest. I would then access them via pulling out some particular contract "con", so that I could apply logic on the cases using

for size, it's digit 3, and thus

con.digit3 / 1e3 % 10 = case of digit 3

which I could use in my contract via the generalized function, for various x and j:

if ((con.digitx / 1ex) % 10 == j) 

I would replace values the following way. For example, if the existing value of digit3 is not 4, and I wanted it to be 4, I would apply the function

  con.digit4 = con.digit4 + 4e3 - ((con.digit4 / 1e3) % 10) * 1e4;

In the context of Solidity's gas considerations, is there a more efficient way? I figure there is using a shift operation.

Best Answer

In Solidity a bool is stored in 1 byte (8 bits). However, only a single bit is actually used. Under the hood, The hex value of true is 0x01, false is 0x00. Converted to binary these are 00000001 and 00000000 - 7 of the 8 bits are not used.

All reads and writes to storage are handled in 32 byte increments. To reduce gas costs, Solidity tightly packs variables where possible so that they are stored within the same 32 bytes. In the case of your struct you have an address (20 bytes) followed by 2 uint8s (1 byte each) and 2 bools (1 byte each). This is a total of 24 bytes, so Solidity is able to pack the entire struct into a single 32 byte slot. There is no need to optimize further on your end.

The concept you are considering (packing more than one bool within a single byte) is known as a bit field. Bit fields are more efficient in cases where you wish to store a sequence of greater than 32 booleans. Mudit Gupta has written an excellent blog post that explains how to accomplish this via bitshifting:

The easiest way is to take a uint256 variable and use all 256 bits of it to represent individual booleans. To get an individual boolean from a uint256 , use this function:

function getBoolean(
    uint256 _packedBools,
    uint256 _boolNumber
)
    public
    view
    returns (bool)
{
    uint256 flag = (_packedBools >> _boolNumber) & uint256(1);
    return (flag == 1 ? true : false);
}

To set or clear a bool, use:

function setBoolean(
    uint256 _packedBools,
    uint256 _boolNumber,
    bool _value
)
    public
    view
    returns (uint256)
{
    if (_value)
        return _packedBools | uint256(1) << _boolNumber;
    else
        return _packedBools & ~(uint256(1) << _boolNumber);
}

With this technique, you can store 256 booleans in one storage slot. If you try to pack bool normally (like in a struct) then you will only be able to fit 32 bools in one slot. Use this only when you want to store more than 32 booleans.

Related Topic