Bit packing in Solidity is something I’ve been wanting to try for a while. My hypothesis was that using packing techniques would result in gas savings (and look cool). The result of the exercise was the packed-voting repo. The write-up and repo can be found here:
https://github.com/lokithe5th/packed-voting
I shared my repo with some colleagues, one of which asked a question I had taken for granted: “Is the gain in gas-efficiency worth the cost in terms of readability?” and “What about using a struct with appropriately sized variables?” - which is a generally accepted pattern for gas savings when using structs. (Thanks @Ben!)
The question made me realize that I had simply assumed gas savings will follow because I was using bit packing. But I hadn’t tested this out to prove my assumption. And so the gas-comparison branch was born.
And the results…surprised me.


There are three versions of the Voting contract in the gas-comparison branch: PackedVoting, NormalVoting and BadVoting
PackedVoting is the original voting contract which uses bit packing to store proposals.
NormalVoting implements the same IVoting interface as packed voting, but it stores proposals as a struct with the variables tightly packed to fit a 256 bit storage slot.
BadVoting does the same as the previous two contracts BUT it does not implement any thoughtful arrangements of the proposal struct nor does it have any gas optimizations. I would go so far to say it is purposefully gas inefficient.
To set the stage:
H0: the packed voting will be the cheapest in terms of gas usage for voting
H1: the packed voting will not be the cheapest in terms of gas usage for voting
For these gas tests I also allowed it to loop for 5000 propose and 10000 vote calls. This did not significantly alter the gas results.
In contract to my expectations, the hardhat gas reporter showed that NormalVoting has the cheapest vote functions, by around 300 units per call (not much) when compared to PackedVoting. The BadVoting contract came last with a whopping 17 000 units more than the other two methods. We would not expect to see a contract such as BadVoting to be used in production, with such an obvious problem, but it can and does happen.
And so we must reject the null hypothesis. And a valuable lesson was learnt: Using bit packing without understanding why it should be used can lead to increasing gas costs.
But the gas reporter showed something else: the cost to create a proposal was cheaper in PackedVoting compared to NormalVoting (by about 100 units). This is not a significant difference and is likely to be reflective of the cost in creating the storage pointer to the appropriate proposal in the _proposals mapping.
The primary limitation of this test run is that this was a straightforward comparison with low contract complexity.
Although the results do not support the use of bit packing as a gas-efficiency method for these simple contracts, it does not preclude gas-optimizations through bit packing in more complex contacts. Tightly packing *structs (on the other hand) can yield significant gains in efficiency without sacrificing readability. For beginner developers, or low-complexity contracts, it makes sense to grab such low-hanging fruit first, before upping the complexity of their smart contract solutions.
Next steps: 1) I’m interested to see if bit packing yields better results for more complex contracts; 2) I would love to dig down into the bytecode to get a comprehensive understanding of where and when bit packing might save some gas - besides when storing 8 bools in an 8-bit space 😉
Interested in following my learning journey? Hit subscribe below 🤓

