китаев.tech

Events и логи — как хранятся данные

Как смарт-контракты записывают events в logs, где они лежат, как читать их через ethers.js и чем logs отличаются от State.

Events и логи

Event — это запись, которую смарт-контракт оставляет во время выполнения транзакции.

Такие записи называются logs. Они попадают в receipt транзакции и помогают приложениям отслеживать, что произошло: перевод токена, создание пула, mint NFT, изменение параметра.


Events не меняют State

State хранит текущее состояние:

text
balanceOf(IVAN) = 100 WETH
balanceOf(ANNA) = 25 WETH

Logs хранят историю событий:

text
Transfer(from: IVAN, to: ANNA, value: 5 WETH)
Transfer(from: ANNA, to: PETR, value: 1 WETH)

Контракт не может прочитать старые logs из своего кода. Logs нужны внешним приложениям: frontend, backend, индексаторам, аналитике и block explorer.


Читаем Transfer events у WETH

typescript
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://ethereum.publicnode.com");

const wethAddress = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";

const wethAbi = [
  "event Transfer(address indexed from, address indexed to, uint256 value)",
  "function decimals() view returns (uint8)",
  "function symbol() view returns (string)",
];

const weth = new ethers.Contract(wethAddress, wethAbi, provider);

const latestBlock = await provider.getBlockNumber();
const fromBlock = latestBlock - 100;

const decimals = await weth.decimals();
const symbol = await weth.symbol();
const events = await weth.queryFilter(
  weth.filters.Transfer(),
  fromBlock,
  latestBlock,
);

for (const event of events.slice(0, 5)) {
  const { from, to, value } = event.args;

  console.log("Tx:", event.transactionHash);
  console.log("From:", from);
  console.log("To:", to);
  console.log("Value:", ethers.formatUnits(value, decimals), symbol);
  console.log("Block:", event.blockNumber);
  console.log("---");
}

Этот код читает реальные Transfer events WETH за последние 100 блоков Ethereum mainnet и выводит первые пять переводов.


Как это работает

event Transfer(address indexed from, address indexed to, uint256 value) — описание event в ABI.

indexed — помечает поля, по которым RPC может быстро фильтровать logs.

weth.filters.Transfer() — создаёт фильтр для всех Transfer events WETH.

weth.queryFilter(filter, fromBlock, latestBlock) — ищет logs в диапазоне блоков.

event.args — декодированные аргументы event. Без ABI вы бы получили только raw topics и data.


Topics и data

На низком уровне log состоит из адреса контракта, topics и data.

text
address: 0xC02a...56Cc2
topics:
  [0] hash("Transfer(address,address,uint256)")
  [1] indexed from
  [2] indexed to
data:
  value

topics[0] определяет тип event. Остальные topics хранят indexed аргументы.

Неиндексированные аргументы лежат в data. В Transfer это value.


Фильтруем по адресу получателя

typescript
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://ethereum.publicnode.com");

const wethAddress = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
const vitalikAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

const abi = [
  "event Transfer(address indexed from, address indexed to, uint256 value)",
];

const weth = new ethers.Contract(wethAddress, abi, provider);

const latestBlock = await provider.getBlockNumber();
const fromBlock = latestBlock - 10_000;

const filter = weth.filters.Transfer(null, vitalikAddress);
const events = await weth.queryFilter(filter, fromBlock, latestBlock);

console.log("Incoming WETH transfers:", events.length);

Первый аргумент null значит: любой from. Второй аргумент фильтрует to, потому что to помечен как indexed.


Где events используются

ЗадачаКак помогают logs
История переводовчитать Transfer events
UI после транзакциипоказать результат из receipt
Индексаторпостроить базу данных по events
Аналитикасчитать объёмы, пользователей, действия

Events дешевле, чем хранить те же данные в State, но они не заменяют State. Контракт не может использовать logs как источник истины для своей логики.


Частые ошибки

Считать event изменением State → event только записывает log. State меняют операции записи в storage, балансы и вызовы контрактов.

Пытаться читать logs из смарт-контракта → logs доступны через RPC и receipt, но не доступны контракту во время выполнения.

Запрашивать слишком большой диапазон блоков → публичные RPC часто ограничивают eth_getLogs. Делите историю на небольшие диапазоны.


Что дальше

Материалы китаev.tech публикуются в образовательных целях и не являются инвестиционной рекомендацией. Примеры кода и описания протоколов — для обучения; использование в продакшне на ваш собственный риск. Дисклеймер и политика конфиденциальности.