4

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.

3
  • 1
    bools are 2 bits - no, each one of them is 8 bits (so 16 altogether). You can optimize them into 2 bits by using a single uint and packing several flags into it, but it might ultimately "de-optimize" the gas-cost of your function or functions (and more generally, reducing "memory size" on the blockchain is not necessarily an optimization). Commented Oct 31, 2019 at 14:29
  • 1
    So in short - more efficient way is to use as much "memory space" as needed, and perform as little number of operations as possible. Your attempt to use less memory ultimately increases the number of operations and subsequently the gas-cost. Commented Oct 31, 2019 at 14:31
  • but by using only 1 variable as opposed to 4, and considering I might wish to change 2 or 3 items at a time, wouldn't I save much more by not having to change the state on 2 or 3 variables, and instead only 1? Commented Oct 31, 2019 at 14:38

1 Answer 1

8

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.

4
  • Using the Gupta suggestion mentioned below, it works, but only until slot 6. For example, getBoolean(10101101, n) works for n<6, but after that there's no obvious correspondence (to me). Any thoughts as to why? Given the way solidity writes, it seems you are saying that, when saving bools, anything less than 32 bytes doesn't benefit from creating a bit field. Correct? Commented Oct 31, 2019 at 18:47
  • meant to say, Gupta suggestion above :) Commented Oct 31, 2019 at 18:56
  • I modified my answer slightly to be more clear. The benefit of a bit field is that you are storing boolean values in bits (binary) not bytes (hexadecimal). So 1 byte can hold 8 bools. Commented Nov 1, 2019 at 7:50
  • And yes, given Solidity's tight packing, if you have 32 booleans or less they can already be stored within a single storage slot, so this technique will not reduce your storage writes. Commented Nov 1, 2019 at 7:52

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.