🎓 A1Learner
🔍
0 of 45 lessons completed

Solidity Packing Mastery

Packing is storing several small values together in one unit of storage. Small values declared next to each other share that unit instead of each taking one alone.

Small values declared together read back correctly

Four small values are declared next to each other. Each one reads back exactly as set, with no neighbour disturbed.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract PackedStats {
    uint128 public first;
    uint64 public second;
    uint64 public third;

    function setAll(uint128 a, uint64 b, uint64 c) public {
        first = a;
        second = b;
        third = c;
    }
}

Shifts and masks pack and unpack a value exactly

Packing shifts each piece into its own bit position. Unpacking shifts it back and masks away the other pieces.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract BitPacker {
    function pack(uint8 a, uint8 b, uint16 c) public pure returns (uint256) {
        return (uint256(a) << 24) | (uint256(b) << 16) | uint256(c);
    }

    function unpackA(uint256 packed) public pure returns (uint8) {
        return uint8((packed >> 24) & 0xff);
    }

    function unpackB(uint256 packed) public pure returns (uint8) {
        return uint8((packed >> 16) & 0xff);
    }

    function unpackC(uint256 packed) public pure returns (uint16) {
        return uint16(packed & 0xffff);
    }
}

Changing one packed value leaves its neighbours alone

A small value, a yes-or-no flag, and two larger small values sit together. Changing only the first leaves the rest unchanged.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract PackedFlags {
    uint8 public level;
    bool public active;
    uint16 public score;
    uint32 public wins;

    function setAll(uint8 l, bool a, uint16 s, uint32 w) public {
        level = l;
        active = a;
        score = s;
        wins = w;
    }

    function setLevel(uint8 l) public {
        level = l;
    }
}

Example

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

contract PackedPlayer {
    uint256 public packed;

    function setAttributes(uint8 level, uint16 rank, uint32 score) public {
        packed = (uint256(level) << 48) | (uint256(rank) << 32) | uint256(score);
    }

    function level() public view returns (uint8) {
        return uint8((packed >> 48) & 0xff);
    }

    function rank() public view returns (uint16) {
        return uint16((packed >> 32) & 0xffff);
    }

    function score() public view returns (uint32) {
        return uint32(packed & 0xffffffff);
    }
}

Key points

  • Packing: Packing is storing several small values together in one unit of storage.
  • Declaration order: Declaration order is the order variables are listed in the contract.
  • Bit packing: Bit packing is placing values at chosen bit positions with shifts and masks.
  • Shift: A shift moves a value left or right by a chosen number of bits.
  • Mask: A mask is a value that keeps only chosen bits of another value.
  • Restatement: Packing fits several small values together so they share storage instead of each taking a whole unit alone.

In this example, setAttributes shifts level, rank, and score into their own bit positions and joins them into one packed value. Level, rank, and score each shift back and mask away the other pieces. Setting new attributes replaces all three readings at once.

Note

Packing is proved here by values reading back correctly, never by claiming a storage position in words.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

How does unpacking recover one piece from a packed value?