3

Tight variable packing is a known coding pattern in Solidity for saving gas by placing struct fields that are lower than 256 bits closer to one another.

Does this logic apply to storage variables? That is, it it more gas-efficient to pack small types together in a Solidity contract storage?

E.g. is contract B more gas efficient than A?

pragma solidity >=0.8.19;

contract A {
    uint128 public x;
    uint256 public y;
    uint128 public z;
}

contract B {
    uint128 public x;
    uint128 public y;
    uint256 public z;
}

2 Answers 2

2

Yes, "State variables of contracts are stored in storage in a compact way such that multiple values sometimes use the same storage slot."

Solidity docs explain:

... data is stored contiguously item after item starting with the first state variable, which is stored in slot 0. For each variable, a size in bytes is determined according to its type. Multiple, contiguous items that need less than 32 bytes are packed into a single storage slot if possible...

@Rohan's answer does indicate contract B is more gas efficient than A, suggesting that contract B setValues is only writing to 2 storage slots instead of 3. But setValue of a single uint128 does cost more like Why does uint8 cost more gas than uint256?

2
  • So setValue() costs more gas in contract B because the EVM has to downscale twice to set the data as opposed to only once in contract A?
    – Rohan Nero
    Oct 1, 2023 at 2:05
  • 1
    @RohanNero yes (same essence as your tip-toeing :))
    – eth
    Oct 3, 2023 at 5:51
2

I made two sample contracts using the provided code in Remix:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;


/* Deployment: 
gas 291003 gas
transaction cost    253106 gas 
execution cost  185426 gas

 First `setValues()`:
 gas    102048 gas
transaction cost    88737 gas 
execution cost  67253 gas

 Second `setValues()`:
 gas    33393 gas
transaction cost    29037 gas 
execution cost  7553 gas

 `readValues()`: 7015 gas
*/
contract A {
    uint128 public x;
    uint256 public y;
    uint128 public z;

    function setValues(uint128 _x, uint _y, uint128 _z) public {
        x = _x;
        y = _y;
        z = _z;
    }

    function readValues() public view returns(uint128, uint256, uint128) {
        return (x,y,z);
    }
}

/* Deployment: 
gas 291003 gas
transaction cost    253106 gas 
execution cost  185426 gas

  First `setValues()`:
gas 76870 gas
transaction cost    66843 gas 
execution cost  45359 gas

  Second `setValues()`:
gas 31100 gas
transaction cost    27043 gas 
execution cost  5559 gas
  `readValues()`: 5065 gas

*/
contract B {
    uint128 public x;
    uint128 public y;
    uint256 public z;

     function setValues(uint128 _x, uint128 _y, uint _z) public {
        x = _x;
        y = _y;
        z = _z;
    }

    function readValues() public view returns(uint128, uint128, uint256) {
        return (x,y,z);
    }
}

The deployment cost between the two contracts was identical, however, once I started making function calls, the differences became apparent.

contract A costs more when writing to hot and cold storage, as well as making a view call to the values.

Taking it a little further, I added an function to each contract that only sets the value for the first uint x.

function setValue(uint128 _x) public {
        x = _x;
    }

Surprisingly, contract B's function call cost more gas:

Contract A:

gas 50351 gas
transaction cost    43783 gas 
execution cost  22579 gas

Contract B:

gas 50376 gas
transaction cost    43805 gas 
execution cost  22601 gas

I'm not sure why, but I'm assuming it's caused by having to set a specific part of the zero storage slot's value as opposed to simply setting the entire value for storage slot zero's variable.

Kind of like tip-toeing around uint y as to not wake it up.

3
  • 1
    So it doesn't look like tight variable packing applies in this case? Sep 29, 2023 at 8:30
  • I suppose not, the only time it saved the user gas was when the function interacted with all three state variables. However, even changing the order of contract A's setValues() input parameters and state variable assignments doesn't lower the gas enough to be less expensive than contract B.
    – Rohan Nero
    Sep 29, 2023 at 18:05
  • 1
    hmm to me it does look like tight variable packing does apply... did I miss something?
    – eth
    Sep 30, 2023 at 9:09

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.