Ethereum's contract data storage model differs significantly from conventional approaches. Even experienced programmers may find its design novel. Smart contracts executed by the Ethereum Virtual Machine (EVM) read/write from a key-value (KV) database, contrasting sharply with standard programming language memory models. This architecture ensures efficient storage while maintaining pointer-like data access capabilities.
Core Principles of Solidity Storage
Solidity employs a storage model where each data item is assigned a computable location within a massive 2²⁵⁶-sized array (initialized with zeros). Key features:
- Sparse Storage: Only non-zero values occupy physical space in the KV database
- 32-byte Slots: Each slot holds 32 bytes; larger data spans consecutive slots
- Deterministic Positioning: Fixed-size types have compile-time assigned locations
👉 Explore Ethereum storage optimization techniques
Fixed-Size Data Storage
Value types with predetermined sizes follow strict sequential allocation:
pragma solidity >0.5.0;
contract StorageExample {
uint8 public a = 11; // Slot 0
uint256 b = 12; // Slot 1
uint[2] c = [13,14]; // Slots 2-3
struct Entry {
uint id; // Slot 4
uint value; // Slot 5
}
Entry d;
}Storage Verification
Retrieve data using Ethereum's eth_getStorageAt API:
web3.eth.getStorageAt(contractAddr,0) // Returns 0x000...00b (11 in decimal)Compact Storage Optimization
The compiler packs smaller values (≤32 bytes) into single slots:
contract StorageExample2 {
uint256 a = 11; // Slot 0
uint8 b = 12; // Slot 1 (1 byte)
uint128 c = 13; // Slot 1 (16 bytes)
bool d = true; // Slot 1 (1 byte)
uint128 e = 14; // Slot 2 (requires new slot)
}Reading Packed Data
let data = web3.eth.getStorageAt(contractAddr,1);
let c = parseInt(data.substr(34,32),16); // Extracts 0x0d (13)Dynamic Data Storage
Strings and Bytes
Short strings (≤31 bytes) store length+data in one slot:
string short = "Under 31 chars"; // Stored in single slot
string long = "Exceeds 31 byte limit..."; // Length in slot, data at keccak256(slot)Arrays
Dynamic arrays store length in declared slot, elements starting at keccak256(slot):
uint256[] public array = [401,402,403];Element positions calculated as:keccak256(slot) + (index * elementSize/32)
Mappings
Keys map to storage via keccak256(key.slot):
mapping(address => UserInfo) users;Location calculation:
keccak256(abi.encodePacked(key, slot))Composite Types
Complex types follow recursive storage rules:
struct UserInfo {
string name; // Packed or hashed storage
uint8 age; // Compact storage
uint256[] orders; // Dynamic array rules
}FAQ
Why can't mappings be iterated?
Mappings don't store key lists, only key-value pairs at hashed locations, making enumeration impossible without external tracking.
Can storage positions collide?
The 2²⁵⁶ space makes collisions statistically impossible. Each key-derivation produces unique positions.
How to optimize storage costs?
- Group smaller types together
- Place frequently accessed data in separate slots
- Minimize dynamic storage operations
👉 Master Solidity storage patterns
Storage Helper Contract
contract SlotHelp {
function getArraySlot(uint256 slot, uint256 index) public pure returns (bytes32) {
return bytes32(uint256(keccak256(abi.encodePacked(slot))) + index);
}
function getMapSlot(uint256 slot, string memory key) public pure returns (bytes32) {
return keccak256(abi.encodePacked(key, slot));
}
}Key Takeaways
- Solidity's storage model combines deterministic positioning with hashing for dynamic types
- Storage is sparse—only non-zero values consume gas
- Field ordering significantly impacts storage efficiency
- Complex types follow composition rules of basic storage patterns
Understanding these principles enables developers to optimize gas costs and ensure secure data handling in smart contracts.