Tutorial·8 min का पठन·Solingo द्वारा

Solidity Mappings को समझें — बेसिक से एडवांस पैटर्न तक

Mappings की पूरी जानकारी — nested mappings, iterable patterns, gas optimization और production-ready code examples के साथ।

# Solidity Mappings को समझें — बेसिक से एडवांस पैटर्न तक

Mappings Solidity का सबसे fundamental data structure है, लेकिन इसकी limitations और best practices को समझना जरूरी है। इस tutorial में हम basic से advanced patterns तक सब कुछ cover करेंगे।

Mappings क्या हैं?

Mapping एक key-value store है जो hash table की तरह काम करता है। यह किसी भी built-in या custom type को दूसरे type से map कर सकता है।

// Basic syntax

mapping(KeyType => ValueType) public myMapping;

// Examples

mapping(address => uint256) public balances;

mapping(uint256 => address) public tokenOwners;

mapping(bytes32 => bool) public isValid;

Mappings की Key Features

1. किसी भी key का default value zero होता है:

contract MappingDefaults {

mapping(address => uint256) public balances;

function checkDefault() public view returns (uint256) {

// यह 0 return करेगा, भले ही कभी set न किया गया हो

return balances[address(0x123)];

}

}

2. Mappings iterable नहीं हैं:

आप mapping के सभी keys या values को loop नहीं कर सकते। इसके लिए separate array maintain करना पड़ता है (नीचे देखें)।

3. Storage-only data location:

Mappings सिर्फ state variables के रूप में exist कर सकते हैं, memory या calldata में नहीं।

Nested Mappings

Complex data structures के लिए nested mappings बेहद useful हैं:

contract TokenAllowances {

// owner => spender => amount

mapping(address => mapping(address => uint256)) public allowances;

function approve(address spender, uint256 amount) public {

allowances[msg.sender][spender] = amount;

}

function transferFrom(

address owner,

address recipient,

uint256 amount

) public {

require(allowances[owner][msg.sender] >= amount, "Insufficient allowance");

allowances[owner][msg.sender] -= amount;

// transfer logic...

}

}

Triple Nested Example

contract MultiSigWallet {

// owner => txId => hasConfirmed

mapping(address => mapping(uint256 => bool)) public confirmations;

// या और complex:

// chainId => contractAddress => selector => isAllowed

mapping(uint256 => mapping(address => mapping(bytes4 => bool))) public permissions;

}

Iterable Mapping Pattern

यह सबसे common advanced pattern है। जब आपको mapping iterate करनी हो, तो एक array maintain करें:

contract IterableMapping {

struct User {

uint256 balance;

bool exists;

}

mapping(address => User) public users;

address[] public userList;

function addUser(address user, uint256 balance) public {

// Duplicate prevention

if (!users[user].exists) {

users[user] = User(balance, true);

userList.push(user);

} else {

users[user].balance = balance;

}

}

function removeUser(address user) public {

require(users[user].exists, "User doesn't exist");

// Remove from mapping

delete users[user];

// Remove from array (swap with last and pop)

for (uint256 i = 0; i < userList.length; i++) {

if (userList[i] == user) {

userList[i] = userList[userList.length - 1];

userList.pop();

break;

}

}

}

function getAllUsers() public view returns (address[] memory) {

return userList;

}

function getTotalBalance() public view returns (uint256 total) {

for (uint256 i = 0; i < userList.length; i++) {

total += users[userList[i]].balance;

}

}

}

Gas Optimization Patterns

Pattern 1: Storage Caching

// ❌ Expensive (3 SLOAD operations)

function badExample(address user) public view returns (uint256) {

return balances[user] + balances[user] + balances[user];

}

// ✅ Optimized (1 SLOAD)

function goodExample(address user) public view returns (uint256) {

uint256 balance = balances[user];

return balance + balance + balance;

}

Pattern 2: Packed Structs in Mappings

contract PackedMapping {

struct UserData {

uint128 balance; // 16 bytes

uint64 lastUpdate; // 8 bytes

uint32 reputation; // 4 bytes

uint16 level; // 2 bytes

bool isActive; // 1 byte

// Total: 31 bytes = 1 storage slot!

}

mapping(address => UserData) public users;

// Single SSTORE updates all fields in one slot

function updateUser(address user, uint128 bal, uint32 rep) public {

UserData storage data = users[user];

data.balance = bal;

data.reputation = rep;

data.lastUpdate = uint64(block.timestamp);

}

}

Pattern 3: Mapping vs Array

// Mapping: O(1) access, but can't iterate

mapping(uint256 => address) public owners;

// Array: O(n) search, but can iterate

address[] public ownerList;

// Best of both worlds:

mapping(uint256 => address) public tokenOwner;

mapping(address => uint256[]) public ownerTokens;

Advanced Pattern: EnumerableMap

OpenZeppelin जैसे pattern जो mapping + array combine करते हैं:

library EnumerableMap {

struct Map {

bytes32[] keys;

mapping(bytes32 => uint256) values;

mapping(bytes32 => uint256) indexes; // 1-based index

}

function set(Map storage map, bytes32 key, uint256 value) internal {

if (map.indexes[key] == 0) {

map.keys.push(key);

map.indexes[key] = map.keys.length;

}

map.values[key] = value;

}

function remove(Map storage map, bytes32 key) internal {

uint256 index = map.indexes[key];

require(index > 0, "Key doesn't exist");

uint256 lastIndex = map.keys.length;

bytes32 lastKey = map.keys[lastIndex - 1];

map.keys[index - 1] = lastKey;

map.indexes[lastKey] = index;

map.keys.pop();

delete map.values[key];

delete map.indexes[key];

}

function length(Map storage map) internal view returns (uint256) {

return map.keys.length;

}

}

Real-World Example: DEX Order Book

contract SimpleOrderBook {

struct Order {

address trader;

uint256 amount;

uint256 price;

bool isBuy;

uint256 timestamp;

}

mapping(uint256 => Order) public orders;

mapping(address => uint256[]) public userOrders;

uint256 public nextOrderId;

uint256[] public buyOrders;

uint256[] public sellOrders;

function placeOrder(uint256 amount, uint256 price, bool isBuy) public {

uint256 orderId = nextOrderId++;

orders[orderId] = Order({

trader: msg.sender,

amount: amount,

price: price,

isBuy: isBuy,

timestamp: block.timestamp

});

userOrders[msg.sender].push(orderId);

if (isBuy) {

buyOrders.push(orderId);

} else {

sellOrders.push(orderId);

}

}

function getUserOrders(address user)

public

view

returns (Order[] memory)

{

uint256[] memory orderIds = userOrders[user];

Order[] memory result = new Order[](orderIds.length);

for (uint256 i = 0; i < orderIds.length; i++) {

result[i] = orders[orderIds[i]];

}

return result;

}

}

Common Pitfalls

1. Delete की Limitation

struct Data {

uint256 value;

uint256[] items;

}

mapping(address => Data) public userData;

// ❌ यह nested array को delete नहीं करता

delete userData[msg.sender];

// ✅ Manual cleanup needed

delete userData[msg.sender].items;

delete userData[msg.sender];

2. Existence Check

// ❌ यह काम नहीं करता (0 valid value हो सकता है)

if (balances[user] != 0) { ... }

// ✅ Explicit exists flag

mapping(address => bool) public exists;

mapping(address => uint256) public balances;

Gas Cost Comparison

| Operation | Gas Cost | Notes |

|-----------|----------|-------|

| First SSTORE (zero to non-zero) | ~20,000 | Expensive |

| SSTORE (non-zero to non-zero) | ~5,000 | Update |

| SLOAD | ~2,100 | Read |

| Mapping access | 0 | सिर्फ offset calculation |

Conclusion

Mappings Solidity में data management का backbone हैं। Key takeaways:

  • Default values और iteration limitations समझें
  • Iterable pattern के लिए array + mapping combine करें
  • Gas optimization के लिए storage packing use करें
  • Complex use cases में EnumerableMap जैसे libraries consider करें

अगली बार जब mapping use करें, तो सोचें: क्या मुझे iteration चाहिए? क्या struct packing से gas बचा सकते हैं? यही difference है basic और production-ready code में।

Practice में लगाने के लिए तैयार हैं?

Solingo पर interactive exercises के साथ इन concepts को apply करें।

मुफ्त में शुरू करें