Mapping
mapping бол Solidity-ийн хамгийн чухал өгөгдлийн бүтэц — түлхүүр (key) → утга (value) хосоор хязгааргүй тооны өгөгдөл хадгалдаг. ERC-20 token, NFT өмчлөл, хэрэглэгчийн үлдэгдэл зэрэг бараг бүх contract-д mapping ашиглагддаг.
Синтакс
mapping(KeyType => ValueType) visibility variableName;
mapping(address => uint256) public balances;
mapping(uint256 => address) public tokenOwner;
mapping(address => bool) public isWhitelisted;
mapping(bytes32 => string) public messages;
KeyType байж болох төрлүүд: uint, int, address, bytes, string, bool — бараг бүх value type.
ValueType нь дурын төрөл байж болно — тэр дотроо өөр mapping байж болно.
Хамгийн энгийн жишээ
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Wallet {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function getBalance(address user) public view returns (uint256) {
return balances[user];
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Үлдэгдэл хүрэлцэхгүй");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
balances[msg.sender] нь "msg.sender хаягийн үлдэгдэл" гэсэн утга. JavaScript-ийн object[key]-тай синтакс адил.
Default утга
Mapping-д хадгалаагүй key-г уншихад алдаа гарахгүй — өгөгдлийн төрлийн default утгыг буцаана:
mapping(address => uint256) public scores;
mapping(address => bool) public hasVoted;
mapping(address => string) public names;
// Хадгалагдаагүй хаягийг унших:
scores[0x1234...] // → 0 (uint256-ийн default)
hasVoted[0x1234...] // → false (bool-ийн default)
names[0x1234...] // → "" (string-ийн default)
Энэ нь маш ашигтай — require(balances[user] > 0) шалгалтыг шинэ хэрэглэгчид ч аюулгүй ажиллуулна.
Nested mapping — mapping дотор mapping
ValueType-д өөр mapping тавьж хоёр түлхүүртэй хадгалах боломжтой.
// ERC-20 стандартын allowance: A нь B-д хэдий зарцуулахыг зөвшөөрсөн
mapping(address => mapping(address => uint256)) public allowance;
// Тохируулах:
function approve(address spender, uint256 amount) public {
allowance[msg.sender][spender] = amount;
}
// Унших:
function getAllowance(address owner, address spender) public view returns (uint256) {
return allowance[owner][spender];
}
Гурван түвшний nested mapping ч бичиж болно, гэхдээ уншихад хэцүү болдог тул хоёр түвшин хангалттай:
// Тоглоомын мэдээлэл: тоглогч → орц → оноо
mapping(address => mapping(uint256 => uint256)) public levelScore;
levelScore[player][1] = 500; // тоглогч 1-р орцноос 500 оноо авсан
levelScore[player][2] = 800;
Mapping-г iterate хийж болохгүй
Solidity-д mapping-г давтаж (loop) уншиж чадахгүй — энэ нь хамгийн чухал ялгаа:
mapping(address => uint256) public balances;
// ❌ БОЛОМЖГҮЙ — compile error
for (address key in balances) { }
// ❌ БОЛОМЖГҮЙ
balances.length;
Яагаад? Mapping нь Ethereum storage-д hash table хэлбэрээр хадгалагддаг — key бүрийн хаяг keccak256(key, slot) hash-аар тодорхойлогддог. Бүх key-ийн жагсаалт blockchain-д огт хадгалагддаггүй тул давтах боломжгүй.
Давтах шаардлагатай бол тусдаа array хадгална:
contract Registry {
mapping(address => uint256) public scores;
address[] public players; // хэн бүртгүүлсэн — жагсаалт
mapping(address => bool) public registered;
function register() public {
require(!registered[msg.sender], "Аль хэдийн бүртгэлтэй");
players.push(msg.sender); // array-д нэмнэ
registered[msg.sender] = true; // mapping-д тэмдэглэнэ
scores[msg.sender] = 0;
}
function getAllPlayers() public view returns (address[] memory) {
return players;
}
// одоо давтаж болно
function getTopScore() public view returns (uint256 top) {
for (uint256 i = 0; i < players.length; i++) {
if (scores[players[i]] > top) {
top = scores[players[i]];
}
}
}
}
Mapping устгах
delete ашиглан утгыг default болгож болно:
mapping(address => uint256) public balances;
function removeUser(address user) public {
delete balances[user]; // balances[user] = 0 болно
}
delete нь утгыг default-д тавих ч array-аас арилгадаггүй — array-г тусдаа удирдах хэрэгтэй.
Практик жишээ: санал өгөх систем
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract Voting {
address public owner;
mapping(address => bool) public hasVoted;
mapping(string => uint256) public voteCount;
string[] public candidates;
constructor() {
owner = msg.sender;
}
function addCandidate(string memory name) public {
require(msg.sender == owner, "Зөвхөн эзэмшигч");
candidates.push(name);
}
function vote(string memory candidate) public {
require(!hasVoted[msg.sender], "Аль хэдийн санал өгсөн");
hasVoted[msg.sender] = true;
voteCount[candidate] += 1;
}
function getVotes(string memory candidate) public view returns (uint256) {
return voteCount[candidate];
}
function getCandidateCount() public view returns (uint256) {
return candidates.length;
}
}
Mapping vs Array
| | Mapping | Array |
| ---------------------- | --------------------------- | ---------------------------- |
| Хайх хурд | O(1) — тэр даруй | O(n) — давтана |
| Iterate хийх | ❌ боломжгүй | ✅ болно |
| Дурын key | ✅ address, uint, ... | ❌ зөвхөн index (0, 1, 2...) |
| Давхар хадгалалт мэдэх | ❌ | ✅ .length |
| Gas | Key-ийн тоогоос үл хамаарна | Element нэмэх бүр нэмэгдэнэ |
Хоёрыг хослуулах нь ихэвчлэн хамгийн сайн шийдэл — mapping хурдан хайхад, array давтахад.
Дараагийн хичээлд:
Array — динамик ба тогтмол урттай жагсаалт, push, pop, delete ажиллагааг судална.