Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
LABS
Glossary

Storage Packing

Storage packing is a gas optimization technique for Ethereum smart contracts that combines multiple smaller data variables into a single 256-bit storage slot to minimize on-chain storage costs.
Chainscore © 2026
definition
BLOCKCHAIN OPTIMIZATION

What is Storage Packing?

Storage packing is a gas optimization technique that efficiently combines multiple small data elements into a single storage slot on the Ethereum Virtual Machine (EVM) to reduce transaction costs.

Storage packing is a smart contract development technique that minimizes gas costs by strategically arranging multiple, smaller state variables to occupy a single 32-byte storage slot. On the Ethereum Virtual Machine (EVM), reading from or writing to a storage slot has a fixed, high gas cost, regardless of how much of the 32-byte word is used. By packing related data—such as multiple uint8 booleans, small integers, or packed structs—into one slot, developers can drastically reduce the number of expensive SSTORE and SLOAD operations. This is a fundamental optimization for contracts where gas efficiency is critical, such as in DeFi protocols or NFT collections.

The technique relies on understanding EVM storage layout and bitwise operations. Variables are declared contiguously in the contract, and the Solidity compiler will automatically pack them into a single slot if they fit within 32 bytes. For example, a uint128, a uint64, and two uint32 variables can be packed together. Developers often use explicit bitmasking with operators like & (AND) and | (OR) to encode and decode packed data within a slot. This manual control is essential for non-standard packing or when interacting with already-deployed contracts using inline assembly via yul.

Consider a practical example: an NFT contract tracking staking status. Instead of using a full bool (which uses an entire slot) for each token's staked state, a developer can pack 256 boolean flags into a single uint256 variable, using each bit to represent a token ID. Reading or updating the status for any token then only requires accessing that one storage slot and performing a bitwise operation, rather than performing a storage operation for each individual token. This pattern is common in highly optimized contracts like Uniswap v3, which uses bit-packed Slot0 to store multiple fee and tick parameters.

While powerful, storage packing introduces trade-offs. It increases code complexity and can make contracts harder to audit and maintain. Reading a packed value requires extra computation (bit shifting and masking) off-chain, though this cost is negligible compared to on-chain storage operations. It's also incompatible with some data types; dynamic arrays and mappings cannot be packed, and elements within structs only pack if they are stored in storage, not memory. Effective packing requires careful planning during the contract design phase, as the storage layout is immutable after deployment.

The optimization is closely related to other gas-saving strategies like using immutable and constant variables, minimizing storage writes, and employing transient storage (EIP-1153). As a core technique in the Ethereum gas golfing playbook, mastery of storage packing is a key differentiator for developers building high-performance, cost-effective smart contracts. Its principles also apply to other EVM-compatible chains like Arbitrum, Polygon, and Base, where reducing L1 data publication costs or L2 execution fees is equally vital.

how-it-works
EVM OPTIMIZATION

How Storage Packing Works

Storage packing is a critical gas optimization technique for smart contract developers, allowing multiple variables to be stored within a single 256-bit storage slot on the Ethereum Virtual Machine (EVM).

Storage packing is the process of efficiently storing multiple, smaller state variables within a single 256-bit storage slot on the Ethereum blockchain to minimize gas costs. Each storage operation on the EVM, whether reading or writing, incurs a gas fee based on the slot accessed. By packing related data—such as multiple uint8, bool, or address types—into one slot, developers drastically reduce the number of expensive SSTORE and SLOAD operations. This optimization is essential for contracts where gas efficiency is paramount, directly lowering transaction costs for users.

The mechanism relies on the EVM's fixed 256-bit (32-byte) slot size. For example, a uint8 uses only 8 bits of data but would occupy an entire slot if declared alone, wasting 248 bits. Through careful struct design and variable ordering, a developer can declare variables like uint64 a, uint64 b, uint64 c, and uint64 d sequentially; the Solidity compiler will automatically pack them into one slot because their combined size (256 bits) fits perfectly. Manual bitwise operations using & (AND), | (OR), and bit-shifting are required for more complex packing or when accessing packed data within assembly blocks.

Effective packing requires understanding storage layout rules. In Solidity, variables are packed contiguously within a slot starting from the least significant bits, and a new slot is started only when the remaining space is insufficient. However, packing is disabled for variables within structs and arrays that are stored in memory or calldata, and it does not cross word boundaries (256-bit slots). A common practice is to group smaller, frequently accessed variables together in a single struct to ensure they are packed, while isolating larger types like uint256 or string into their own slots.

Consider a contract managing user flags: storing a bool isActive, uint8 tier, and uint64 lastUpdated separately would consume three slots and over 60,000 gas for initial writes. Packed into a struct {uint64 lastUpdated; uint8 tier; bool isActive;}, they occupy one slot, as the total size is less than 256 bits. This single-slot write costs 20,000 gas, saving roughly 66%.* This demonstrates why storage packing is a fundamental skill for writing cost-effective smart contracts, especially in high-throughput applications like DeFi protocols and NFT minting.

key-features
STORAGE PACKING

Key Features & Benefits

Storage packing is a data optimization technique that consolidates multiple small data entries into a single storage slot on a blockchain to reduce gas costs and improve state efficiency.

01

Gas Cost Reduction

The primary benefit of storage packing is significant gas savings. On EVM chains, writing to a new storage slot costs ~20,000 gas, while updating an existing slot costs ~5,000 gas. By packing multiple uint8 or bool values into a single 32-byte slot, a contract can perform multiple state updates for the cost of one. This is critical for functions called frequently by users.

02

Efficient State Management

Packing optimizes the use of the blockchain's state trie. Each unique storage slot is a node in the Merkle Patricia Trie. Fewer slots mean a smaller, more efficient state tree, which reduces the computational load for network nodes during state synchronization and verification. This contributes to overall network scalability.

03

Bitmasking & Bitwise Operations

Implementation relies on bitwise operations (&, |, <<, >>). Developers use a bitmask to isolate specific bits within a packed slot.

  • Example: Storing three 10-bit numbers in one uint256. To read the second value: (packedSlot >> 10) & 0x3FF.
  • This requires careful design to avoid overflows and ensure data integrity.
04

Common Use Cases

Packing is ideal for storing multiple boolean flags, enums, small integers, or packed structs.

  • NFT Attributes: Rarity, generation, and status flags in a single slot.
  • User Roles: Multiple permission bits for access control.
  • Game States: Packed data for player position, health, and inventory.
  • Time-Locking: Combining a timestamp with a flag in one storage word.
05

Trade-offs & Considerations

While efficient, packing introduces complexity.

  • Read/Write Overhead: Extra computation is needed to pack/unpack data, slightly increasing on-chain execution gas (offset by massive storage savings).
  • Design Rigidity: The bit layout is fixed; changing it later requires a migration.
  • Less Readable: Packed storage variables are not individually accessible via public getters without helper functions.
06

Solidity Implementation

In Solidity, use uint256 as the base type and explicitly define bit positions. Libraries like OpenZeppelin's BitMaps and BitMaps for uint256 provide standardized utilities.

solidity
// Example: Packing two uint128s into one uint256
uint256 private _packedData;
function setValues(uint128 a, uint128 b) internal {
    _packedData = (uint256(b) << 128) | uint256(a);
}
code-example
SOLIDITY OPTIMIZATION

Storage Packing

Storage packing is a gas optimization technique in Solidity that minimizes the number of storage slots used by grouping multiple, smaller state variables into a single 256-bit slot.

In the Ethereum Virtual Machine (EVM), a storage slot is a 256-bit (32-byte) unit of persistent data. Reading from or writing to storage is one of the most expensive operations in terms of gas. Storage packing works by declaring multiple, smaller contiguous state variables (like uint8, uint16, bool) so the EVM can pack them into a single slot, provided their combined size does not exceed 256 bits. For example, two uint128 variables can be packed into one slot, but a uint256 will always occupy an entire slot by itself. This technique directly reduces the gas costs for contract deployment and state-modifying transactions.

The packing order is determined by the declaration order of variables within a contract. The EVM packs variables from right to left within a slot, starting with the first variable in the slot. It's crucial to group variables that are read or updated together, as unpacking a value from a packed slot still incurs a computational cost. A common pitfall is leaving storage gaps—unused bits within a slot—by not ordering variables efficiently. Developers must also be aware that elements of arrays and mappings always occupy their own full storage slots and cannot be packed with other variables.

Consider this optimized struct: struct Packed { uint64 a; uint64 b; uint128 c; }. All three members fit into one 256-bit slot (64+64+128 bits). In contrast, struct Inefficient { uint128 a; uint256 b; uint64 c; } would use three slots because the uint256 b forces a new slot, and uint64 c is placed into a new slot as it cannot fit into the remainder of the previous one. Using the pragma abicoder v2 and uint types with explicit bit sizes is essential for effective packing. This optimization is a fundamental skill for writing gas-efficient smart contracts.

common-use-cases
STORAGE PACKING

Common Use Cases & Examples

Storage packing is a critical optimization technique for reducing on-chain storage costs by efficiently encoding and storing data within smart contracts. These examples illustrate its practical applications across different blockchain ecosystems.

03

Game State Compression

On-chain games and GameFi projects pack numerous player state variables—health, mana, inventory item IDs, cooldown timestamps—into minimal storage. For instance, a player's state might be packed into two 256-bit slots instead of twenty separate variables. This is essential for making frequent state updates economically viable, as seen in games like Dark Forest, where efficient ZK-SNARK-friendly state management is required.

05

Solana's Account Data Layout

On Solana, where account storage is explicitly allocated and paid for, packing is mandatory for cost-effective program design. Developers meticulously define data structures to fit within allocated bytes. For example, a token account packs the mint address, owner address, amount, and delegate information into a fixed layout. This maximizes data density per account, directly lowering rent-exempt SOL requirements for users.

SOLIDITY STORAGE LAYOUT

Packing Compatible Data Types

Data types that can be packed into a single 256-bit (32-byte) storage slot to minimize gas costs.

Data TypeSize (Bytes)Packing RuleExample Slot Composition

uint8, uint16, uint32, uint64

1, 2, 4, 8

Sequential packing within slot

uint64 (8) + uint32 (4) + uint16 (2) + uint8 (1) = 15/32 used

bool

1

Treated as uint8

bool + uint8 + uint16 = 4/32 used

address

20

Treated as uint160

address (20) + uint16 (2) + bool (1) = 23/32 used

bytes1 to bytes31

1-31

Packed left-to-right

bytes4 (4) + bytes8 (8) = 12/32 used

enums (based on uint8)

1

Same as underlying integer

enum (1) + uint8 (1) = 2/32 used

structs

Sum of members

Members packed recursively

struct {uint8 a; uint16 b;} uses 3 bytes

Fixed-size arrays

Element size * length

Elements packed sequentially

uint8[4] uses 4 bytes

Incompatible: mapping, dynamic arrays, string

N/A

Use separate storage slot

security-considerations
STORAGE PACKING

Security & Implementation Considerations

Storage packing is an optimization technique that reduces gas costs by efficiently storing multiple variables within a single storage slot. While powerful, it introduces specific security and design tradeoffs.

01

Slot Collision & Data Corruption

Packing variables into a single 32-byte slot requires careful bit-level management. A primary risk is slot collision, where write operations to one packed variable inadvertently overwrite adjacent data due to incorrect bit masking or shifting. This can lead to silent data corruption. For example, writing a uint8 into bits 16-23 without clearing the entire slot first could corrupt data in bits 0-15 and 24-31.

  • Mitigation: Always use explicit bitwise operations (&, |, <<, >>) with proper masks.
  • Best Practice: Implement dedicated getter and setter functions to abstract the packing logic and prevent direct slot manipulation.
02

Gas Optimization vs. Execution Cost

The primary benefit of storage packing is reducing SSTORE operations, which are extremely gas-intensive. However, the computational overhead for packing and unpacking data can increase execution gas. This creates a trade-off:

  • Beneficial: When variables are frequently written together in the same transaction, the single SSTORE saves significant gas.
  • Detrimental: If variables are read/written independently, the extra bitwise operations may outweigh the storage savings.

Developers must profile gas usage for their specific access patterns. Packing is most effective for state variables that are updated in a batch, like a user's packed flags (bool isActive, bool isWhitelisted, uint8 tier).

03

Upgradeability & Storage Layout

Storage packing creates a rigid storage layout that is incompatible with naive upgrade patterns. Adding, removing, or reordering packed variables in a proxy-based upgradeable contract will corrupt the stored data.

  • EIP-1967/Transparent Proxy: The storage layout must be append-only. You cannot insert a new variable between existing packed ones.
  • Unstructured Storage (EIP-7201): This pattern mitigates the issue by using pseudorandom slot positions, but packing within a custom struct still requires careful versioning.

Any contract upgrade must preserve the exact memory offset and bit positioning of all previously packed variables.

04

Front-Running & Write Order Dependencies

When multiple packed variables within a slot are updated in separate transactions, the order of those transactions becomes critical due to front-running. A malicious actor can exploit this by inserting their transaction between two legitimate ones.

Example Scenario:

  1. A user submits TX A to set bool flagA = true.
  2. A front-runner sees this and submits TX B that sets uint16 valueB = 0xFFFF in the same slot.
  3. If TX B executes first, it overwrites the slot, and TX A then resets valueB to 0 when it writes flagA.

This can be mitigated by updating all variables in the slot atomically within a single transaction or using access control to prevent unauthorized writes.

05

Verification & Debugging Complexity

Debugging and formally verifying contracts with packed storage is more complex. Standard tools may not intuitively display the packed bitfield.

  • Block Explorers: May show the raw 32-byte hex value of a storage slot, requiring manual decoding.
  • Testing: Requires extensive unit tests for getter/setter functions to ensure bitwise logic is correct across all edge cases.
  • Formal Verification: Tools must reason about bit-level operations, which is more challenging than simple integer arithmetic.

Developers should document the exact bit layout (e.g., bits 0-7: uint8 id, bits 8-15: uint8 status, bits 16-31: uint16 balance) in NatSpec comments.

06

Best Practice: Using `struct` for Explicit Packing

The safest implementation pattern is to use a Solidity struct with explicitly sized types and the assembly block for manual packing/unpacking, or to rely on the compiler's automatic packing for structs stored in a single slot.

Example of Manual Assembly:

solidity
struct PackedData {
    uint64 a;
    uint64 b;
    uint128 c;
} // Automatically packed by compiler into one slot

Key Guidelines:

  • Let the compiler handle packing within structs when possible; it's less error-prone.
  • For manual assembly, isolate the logic in a dedicated library.
  • Always write comprehensive fuzz tests (e.g., with Foundry) to validate all possible bit combinations.
STORAGE PACKING

Frequently Asked Questions (FAQ)

Storage packing is a critical gas optimization technique in Ethereum Virtual Machine (EVM) development that reduces transaction costs by efficiently storing multiple data points within a single 256-bit storage slot.

Storage packing is a gas optimization technique that stores multiple smaller state variables within a single 256-bit storage slot to minimize Ethereum gas costs. On the EVM, writing to a storage slot costs 20,000 gas for a cold slot and 5,000 gas for a warm slot, regardless of how much data is stored. By packing multiple uint8, uint16, bool, or address variables into one slot, developers pay for one storage operation instead of many. For example, instead of storing a uint8 (8 bits) and a uint16 (16 bits) in separate slots, they can be packed into a single slot, using only 24 of the available 256 bits and saving up to 20,000 gas on the initial write. The Solidity compiler automatically packs contiguous variables that fit within a single 32-byte word, but developers must manually order their state variable declarations to enable this optimization.

ENQUIRY

Get In Touch
today.

Our experts will offer a free quote and a 30min call to discuss your project.

NDA Protected
24h Response
Directly to Engineering Team
10+
Protocols Shipped
$20M+
TVL Overall
NDA Protected direct pipeline
Storage Packing: Ethereum Gas Optimization Technique | ChainScore Glossary