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; }

Paul Razvan Berg
  • 17,902
  • 6
  • 73
  • 143

2 Answers2

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.

Rohan Nero
  • 1,562
  • 2
  • 7
  • 27
  • 1
    So it doesn't look like tight variable packing applies in this case? – Paul Razvan Berg Sep 29 '23 at 08: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 '23 at 18:05
  • 1
    hmm to me it does look like tight variable packing does apply... did I miss something? – eth Sep 30 '23 at 09:09
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?

eth
  • 85,679
  • 53
  • 285
  • 406
  • 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 01 '23 at 02:05
  • 1
    @RohanNero yes (same essence as your tip-toeing :)) – eth Oct 03 '23 at 05:51