Solidity / Event ба emit

Event ба emit

event нь Solidity-ийн мэдэгдлийн механизм — transaction хийгдэх үед blockchain-ийн log-д бичигддэг мэдээлэл. Фронтенд програм (ethers.js, viem) эдгээр event-г сонсож, шинэ үйл явдлыг бодит цагт ажиглах боломжтой.

Event тодорхойлох ба emit хийх

solidity
// Тодорхойлох
event EventName(Type1 param1, Type2 param2);

// Гаргах
emit EventName(value1, value2);
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract SimpleToken {
    mapping(address => uint256) public balances;

    // event тодорхойлно
    event Transfer(address from, address to, uint256 amount);
    event Mint(address to, uint256 amount);

    function mint(address to, uint256 amount) public {
        balances[to] += amount;
        emit Mint(to, amount);   // log-д бичнэ
    }

    function transfer(address to, uint256 amount) public {
        require(balances[msg.sender] >= amount, "Үлдэгдэл хүрэлцэхгүй");
        balances[msg.sender] -= amount;
        balances[to]         += amount;
        emit Transfer(msg.sender, to, amount);   // log-д бичнэ
    }
}

Event log хаана хадгалагддаг вэ?

Event нь storage-д биш, transaction log-д хадгалагддаг — storage-ийн ~1/5-ийн gas зарцуулдаг, гэхдээ Solidity-д уншиж чадахгүй. Зөвхөн гадны програм (фронтенд, indexer) унших боломжтой.

код
Transaction
├── from:     0xАбВГ...
├── to:       contract
├── logs:
│   └── event Transfer(from=0xАбВГ, to=0хДЕЖ, amount=100)
│       ← gas хямд, contract дотроос уншигдахгүй
└── ...

Event ашиглах хэрэгцээ:

  • Фронтенд шинэ үйл явдлыг бодит цагт харуулах
  • DApp-ийн үйл ажиллагааг хянах (аудит)
  • Дэд бүтцийн системүүд (The Graph, Etherscan) өгөгдөл цуглуулах

indexed параметр

indexed тэмдэглэгдсэн параметрийг хайх, шүүх боломжтой. Event-д дээд тал нь 3 indexed параметр тавьж болно.

solidity
event Transfer(
    address indexed from,    // хайж болно
    address indexed to,      // хайж болно
    uint256 amount           // indexed биш — хайж болохгүй
);

indexed параметр нь Ethereum-д topic болгон хадгалагддаг. Фронтенд эдгээр topic-р шүүдэг:

javascript
// ethers.js дээр: зөвхөн myAddress руу ирсэн Transfer-г сонсоно
contract.queryFilter(contract.filters.Transfer(null, myAddress));

indexed биш параметр нь data хэсэгт хадгалагддаг — шүүж болохгүй ч утгыг бүрэн харах боломжтой.

Бодит жишээ: NFT Marketplace

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

contract NFTMarketplace {
    struct Listing {
        address seller;
        uint256 price;
        bool    active;
    }

    mapping(uint256 => Listing) public listings;

    // indexed: tokenId, seller — шүүж хайх боломжтой
    event Listed(
        uint256 indexed tokenId,
        address indexed seller,
        uint256 price
    );

    event Sold(
        uint256 indexed tokenId,
        address indexed seller,
        address indexed buyer,
        uint256 price
    );

    event Cancelled(
        uint256 indexed tokenId,
        address indexed seller
    );

    function listToken(uint256 tokenId, uint256 price) public {
        require(price > 0, "Үнэ тэгээс их байх ёстой");

        listings[tokenId] = Listing({
            seller: msg.sender,
            price:  price,
            active: true
        });

        emit Listed(tokenId, msg.sender, price);
    }

    function buyToken(uint256 tokenId) public payable {
        Listing storage listing = listings[tokenId];

        require(listing.active,             "Зарагдахгүй байна");
        require(msg.value == listing.price, "Үнэ таарахгүй байна");
        require(listing.seller != msg.sender, "Өөрийн зүйлсээ худалдаж авах боломжгүй");

        address seller = listing.seller;
        listing.active = false;

        payable(seller).transfer(msg.value);

        emit Sold(tokenId, seller, msg.sender, msg.value);
    }

    function cancelListing(uint256 tokenId) public {
        require(listings[tokenId].seller == msg.sender, "Зөвхөн эзэмшигч");
        require(listings[tokenId].active, "Зарагдаагүй байна");

        listings[tokenId].active = false;
        emit Cancelled(tokenId, msg.sender);
    }
}

Фронтенд дэх event сонсох (ethers.js)

javascript
import { ethers } from "ethers";

const provider = new ethers.BrowserProvider(window.ethereum);
const contract = new ethers.Contract(ADDRESS, ABI, provider);

// Шинэ Transfer бүрийг бодит цагт сонсох
contract.on("Transfer", (from, to, amount) => {
  console.log(`${from}${to}: ${ethers.formatEther(amount)} ETH`);
});

// Өнгөрсөн event-г хайх
const filter = contract.filters.Transfer(null, myAddress);
const events = await contract.queryFilter(filter, fromBlock, toBlock);

events.forEach((e) => {
  console.log("Хүлээн авсан:", e.args.amount.toString());
});

Event vs Storage харьцуулалт

| | Event (log) | Storage | | ------------------ | -------------- | -------------------- | | Gas | ~375+ (хямд) | ~20,000 (шинэ утга) | | Contract-аас унших | ❌ боломжгүй | ✅ болно | | Фронтендээс унших | ✅ болно | ✅ болно | | Байнга хадгалах | ✅ блокчейнд | ✅ блокчейнд | | Хайх, шүүх | indexed-ийг ✅ | mapping-ийн key-г ✅ |

Дүрэм: Contract дотроос уншиж хэрэггүй түүхийн мэдээлэл (үйл явдлын бүртгэл) → event. Contract дотроос уншиж ашиглах мэдээлэл (үлдэгдэл, эзэмшигч) → storage.

Дараагийн хичээлд:

modifier — кодын давталтгүй нөхцөл шалгалтын механизм, onlyOwner загварыг дэлгэрэнгүй судална.