I have a Solidity function like sampleFunction(uint[]calldata) which takes an array of uints which contains 6 numbers [1900000000000000,1,990,990,2,1]. Far as I know these input variables will consume a byte32 by each which leads to a lot of zeros in the call message data, therefore it is inefficient in terms of gas usage. My question is what can I do to save gas on the unwanted zeros? How can I create a shorter call message? My first idea was creating a string from the numbers but Solidity can't access string objects by index like it can be done in Python for example. What I want to achieve is something like the below example:

From this:


To this:


I am not familiar with the bytes representation of transaction data, but this is something what I would like send then unpack it in Solidity without using Assembly if it's possible.

  • 1
    It is difficult to recommend something without more details about what represent each number. In the worst case you will be trading "calldata cost" against the cost of decoding the numbers. Parsing an array of bytes is not free.
    – Ismael
    Commented Mar 16 at 5:38

1 Answer 1


To clarify, your array example appears to use numbers in decimal format, and your packing example also shows decimals. However, it is hexadecimal, so you can pack significantly larger numbers.

You have two ways to effectively pack the numbers into a single uint256/bytes32.

Decimal Packing

  1. Packing: Choose a base (a power of 10) for each number. For example, if your number could be a maximum of 99, the base would be 100; for a maximum of 999, it would be 1000. The maximum for a single uint256 number is:

This allows for 77 digits to be arranged for your numbers. For example, with 3 numbers:

If p = x1 * x2Base * x3Base + x2 * x3Base + x3 with x1=1900000000000000, x2=1, x3=990, and the bases x2Base=10, x3Base=1000, the maximums are x2Max=9, x3Max=999. However, you could choose other maximums depending on your case.

The packed number would then be: 1900000000000000 * 10 * 1000 + 1 * 1000 + 990 = 19000000000000001990.

  1. Decimal Unpacking: Use the % and / operators to extract the digits before and after the target number:
  • Divide to remove the trailing digits.
  • Use the modulo operation to extract the number.
x1 = p / (x2Base * x3Base)
x2 = (p % (x2Base * x3Base)) / x3Base
x3 = p % x3Base
  • We don't apply the modulo operation for x1 because, after dividing, it is already our target (no numbers before it).
  • We don't shift for x3 because the number is already at the end.

Bitwise Packing

The uint256 number has 32 bytes, and you can represent the numbers from the example above as uint64, uint4, uint10, etc.

  1. Packing:
p = (x1 << (x2Size + x3Size)) | (x2 << x3Size) | x3

With x2Size=4, x3Size=10, the packed number is (1900000000000000 << (4 + 10)) | (1 << 10) | 990 = 31129600000000002014.

  1. Unpacking:
  • Shift right to remove the trailing digits.
  • Perform a bitwise AND to extract the number.
x1 = p >> (x2Size + x3Size) = 31129600000000002014 >> (4 + 10) = 1900000000000000
x2 = (p >> x3Size) & (2 ** x2Size - 1) = (31129600000000002014 >> 10) & (2**4 - 1) = 1
x3 = p & (2**x3Size - 1) = 31129600000000002014 & (2**10 - 1) = 990
  • We don't apply AND for x1 as after shifting, it is already our target (no numbers before it).
  • We don't shift for x3 because the number is already at the end.

Made some tests on gas consumption: calldata.spec.ts, and with bitwise packing you would save 1968 GAS compared to unit[] calldata, if we assume 50gwei for the gas price, then it would cost 0.0000984ETH, that by 3.3K$ makes 0.32$ saving.

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.