События и логи

События — это способ контракта «крикнуть наружу»: дёшево записать факт в лог, который услышат фронтенд и индексаторы.
Контракт не может позвонить вашему серверу. Но он может оставить событие в блокчейне — и весь мир узнает, что произошёл перевод.

Событие (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-параметры дают быстрый поиск, логи читают фронтенд и индексаторы. Это завершает основы; дальше — структуры данных: маппинги, структуры и массивы.

Проверьте себя
1. Можно ли прочитать ранее излучённое событие изнутри контракта?
AДа, через emit
BДа, если оно indexed
CНет, логи недоступны коду EVM
DТолько в той же транзакции
2. Зачем помечать параметр события как indexed?
AЧтобы скрыть его значение
BЧтобы по нему можно было быстро фильтровать события
CЧтобы сэкономить весь газ на событии
DЧтобы записать его в storage