китаев.tech

Events — объявление и emit

Как объявлять events в Solidity, вызывать emit и выбирать indexed-поля для фильтрации logs.

Event записывает факт транзакции

event — это описание записи, которую контракт может оставить во время выполнения транзакции. Такая запись попадает в logs и потом читается через RPC, block explorer, frontend или индексатор.

emit — это команда, которая создаёт конкретный log. Контракт меняет state отдельно, а event только сообщает внешнему миру, что произошло.


Пишем контракт с событиями

Создайте файл contracts/DonationBox.sol:

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

contract DonationBox {
    address public immutable owner;
    uint256 public totalDonated;

    mapping(address => uint256) public donatedBy;

    event DonationReceived(
        address indexed donor,
        uint256 amount,
        uint256 totalDonated
    );

    event Withdrawn(
        address indexed owner,
        address indexed to,
        uint256 amount
    );

    error OnlyOwner();
    error ZeroAmount();
    error ZeroAddress();
    error NothingToWithdraw();
    error EthTransferFailed();

    constructor() {
        owner = msg.sender;
    }

    function donate() external payable {
        if (msg.value == 0) {
            revert ZeroAmount();
        }

        donatedBy[msg.sender] += msg.value;
        totalDonated += msg.value;

        emit DonationReceived(msg.sender, msg.value, totalDonated);
    }

    function withdraw(address payable to) external {
        if (msg.sender != owner) {
            revert OnlyOwner();
        }

        if (to == address(0)) {
            revert ZeroAddress();
        }

        uint256 amount = address(this).balance;

        if (amount == 0) {
            revert NothingToWithdraw();
        }

        (bool success, ) = to.call{value: amount}("");

        if (!success) {
            revert EthTransferFailed();
        }

        emit Withdrawn(msg.sender, to, amount);
    }
}

Этот контракт принимает пожертвования, хранит сумму по каждому адресу и записывает события, которые приложение может показать в истории действий.


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

event DonationReceived(...) — объявляет формат события. Это описание попадёт в ABI контракта.

address indexed donor — делает адрес донора фильтруемым. Frontend сможет быстро найти все пожертвования конкретного адреса.

uint256 amount — обычный аргумент события. Он попадёт в data, но по нему нельзя эффективно фильтровать logs через topics.

emit DonationReceived(msg.sender, msg.value, totalDonated) — создаёт log после изменения storage. В событие попадает уже обновлённый totalDonated.

event Withdrawn(...) — отдельное событие для другого действия. Лучше делать разные events для разных бизнес-событий, а не один универсальный Action.

emit Withdrawn(msg.sender, to, amount) — записывает, кто вывел ETH, куда и сколько.


Indexed-поля

indexed нужен для фильтрации logs. Внешнее приложение может запросить события только по конкретному donor, owner или to, если эти поля помечены как indexed.

В одном событии можно индексировать до трёх аргументов. Не стоит ставить indexed на всё подряд: индексированные динамические типы вроде string и bytes хранятся в topics как hash, а не как исходное значение.

Хорошие кандидаты для indexed: address, uint256 id, bytes32 role, bytes32 orderHash.


Events не заменяют State

Events нужны для истории, интерфейсов и индексаторов. Они дешевле записи в storage, но контракт не может прочитать старые logs из своего Solidity-кода.

Если значение нужно для будущей логики контракта, храните его в state: mapping, массиве или переменной. Если значение нужно только внешнему приложению, часто достаточно event.

В DonationBox баланс пожертвований хранится в donatedBy и totalDonated, а события помогают восстановить историю пожертвований снаружи.


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

Думать, что emit меняет состояние → event только создаёт log. State меняют присваивания, изменения storage и переводы ETH.

Не индексировать адреса → без indexed frontend не сможет быстро фильтровать events по пользователю через topics.

Эмитить событие до проверки результата → сначала выполните действие и убедитесь, что оно не упало, затем вызывайте emit.


Что дальше

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