События и логи
События — это способ контракта «крикнуть наружу»: дёшево записать факт в лог, который услышат фронтенд и индексаторы.
Контракт не может позвонить вашему серверу. Но он может оставить событие в блокчейне — и весь мир узнает, что произошёл перевод.
Событие (event) — это запись в специальном журнале логов транзакции. Контракт объявляет событие и «излучает» его через emit. Логи не хранятся в storage и не читаются самим контрактом, зато их легко и дёшево слушать снаружи: кошельки, дашборды, The Graph и другие индексаторы подписываются на события.
Параметры можно пометить indexed (до трёх на событие) — по ним работает быстрый поиск/фильтрация. Например, «покажи все Transfer, где from = мой адрес».
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Token {
mapping(address => uint256) public balances;
event Transfer(address indexed from, address indexed to, uint256 value);
function transfer(address to, uint256 value) external {
require(balances[msg.sender] >= value, "low balance");
balances[msg.sender] -= value;
balances[to] += value;
emit Transfer(msg.sender, to, value); // факт записан в лог
}
}
КОНТРАКТ ВНЕШНИЙ МИР
======== ===========
transfer() --emit Transfer--> ЛОГ блока
|
indexed from/to ------+--> фронтенд фильтрует
+--> индексатор (The Graph)
+--> эксплорер показывает
Как работает под капотом (EVM/газ)
Под событиями лежат опкоды LOG0..LOG4. indexed-параметры становятся «топиками» — по ним строится bloom-фильтр блока, что и даёт быстрый поиск. Не-indexed параметры пишутся в «data» лога. Логи существенно дешевле storage (примерно 375 газа за событие плюс по 375 за топик и 8 за байт данных), потому что узлы не обязаны хранить их вечно в дереве состояния. Но из самого контракта прочитать свои логи нельзя — они односторонний канал «контракт → наружу».
# Та же логика на Python: лог событий + фильтрация по indexed
log = [] # журнал событий блока
def emit_transfer(frm, to, value):
log.append({"event": "Transfer", "from": frm, "to": to, "value": value})
emit_transfer("alice", "bob", 40)
emit_transfer("bob", "carol", 10)
emit_transfer("alice", "carol", 5)
# фронтенд фильтрует по indexed-параметру from == "alice"
mine = [e for e in log if e["from"] == "alice"]
print("Мои переводы:", mine)
print("Всего событий в блоке:", len(log))
«Та же логика на Python ▶». indexed-поля — это ключи для фильтрации; так фронтенд показывает «мои транзакции», не перебирая всё состояние контракта.
Частые ошибки
- Пытаться прочитать собственное событие внутри контракта — это невозможно, логи не доступны коду EVM.
- Складывать в события приватные данные, думая что «это же лог» — логи публичны, как и всё в блокчейне.
- Забывать
emitна ключевых действиях — тогда фронтенд и индексаторы «слепнут», UX ломается.
Best practices
- Излучайте событие на каждое значимое изменение состояния (переводы, минт, смена владельца) — это стандарт и для UX, и для аудита.
- Помечайте
indexedте поля, по которым будет фильтрация (адреса, id), но помните про лимит в 3. - Следуйте именам событий из стандартов (например
Transfer,Approvalу ERC-20), чтобы кошельки понимали их «из коробки».
Как события превращаются в данные DApp
На практике события — это «событийный поток» приложения. Современные DApp почти никогда не читают состояние контракта пользователь-за-пользователем: это медленно и дорого. Вместо этого индексатор (например, The Graph через так называемый subgraph) подписывается на события контракта, разбирает их по мере появления новых блоков и складывает в обычную базу данных с быстрым поиском. Фронтенд затем запрашивает уже эту базу обычным GraphQL-запросом — «покажи последние 20 переводов адреса X» — и получает ответ за миллисекунды. Получается удобное разделение: блокчейн хранит истину и излучает события, индексатор превращает их в удобную для запросов проекцию, а пользователь видит мгновенный отклик. Поэтому продуманный набор событий — это не «логирование для отладки», а полноценный публичный API вашего контракта.
Итоги
События — дешёвый односторонний канал из контракта во внешний мир. indexed-параметры дают быстрый поиск, логи читают фронтенд и индексаторы. Это завершает основы; дальше — структуры данных: маппинги, структуры и массивы.