Обработка ошибок смарт-контрактов Solidity в Golang: как упростить код

    Разработка на Golang с использованием смарт-контрактов Solidity часто сталкивается с проблемой обработки пользовательских ошибок. Стандартный подход через abigen и CallContract приводит к громоздкому коду, особенно при десятках функций контракта. В этой статье разберём, почему так происходит и какие существуют решения.

    Почему abigen не обрабатывает ошибки из коробки?

    Инструмент abigen генерирует Go-биндинги для вызова функций смарт-контракта, но не предоставляет встроенного механизма для парсинга revert-ошибок. Это связано с тем, что Solidity возвращает ошибки в виде ABI-закодированных данных, которые необходимо декодировать вручную. Разработчику приходится самостоятельно формировать CallMsg, вызывать CallContract и парсить результат.

    Основные проблемы при обработке ошибок

    Избыточность кода

    Для каждой функции контракта требуется повторять шаги: парсинг ABI, упаковка calldata, создание CallMsg, вызов CallContract и разбор ошибки. Это приводит к дублированию и усложняет поддержку.

    Сложность парсинга revert-строк

    Стандартная ошибка execution reverted не содержит информации о причине. Необходимо анализировать возвращаемые данные, извлекать methodID (0x08c379a0 для строковых ошибок) и декодировать их через ABI.

    Альтернативные подходы и готовые инструменты

    1. Использование библиотеки go-ethereum с утилитами

    Библиотека go-ethereum предоставляет функцию UnpackRevertReason в пакете accounts/abi/bind, которая автоматически парсит стандартные revert-строки. Пример использования:

    reason, err := bind.UnpackRevertReason(res)
    if err != nil {
        // обработка ошибки
    }
    fmt.Println(reason)

    2. Создание обёртки над CallContract

    Разработайте универсальную функцию, которая принимает ABI, имя метода и аргументы, выполняет вызов и возвращает ошибку в понятном виде. Это сократит дублирование кода.

    3. Интеграция с The Graph или событиями

    Для сложных сценариев можно использовать индексирование через The Graph - это позволит получать ошибки и статусы транзакций без прямых вызовов контракта.

    Пример оптимизированного подхода

    Вместо ручного парсинга ABI для каждого вызова, создайте единый обработчик:

    func callWithErrorParsing(contractABI abi.ABI, method string, args ...interface{}) ([]byte, error) {
        callData, err := contractABI.Pack(method, args...)
        if err != nil {
            return nil, fmt.Errorf("pack failed: %v", err)
        }
        msg := ethereum.CallMsg{Data: callData}
        res, err := client.CallContract(context.Background(), msg, nil)
        if err != nil {
            reason, parseErr := bind.UnpackRevertReason(res)
            if parseErr == nil {
                return nil, fmt.Errorf("revert: %s", reason)
            }
            return nil, err
        }
        return res, nil
    }

    Часто задаваемые вопросы

    Вопросы ниже помогут быстро разобраться в типичных ситуациях при работе со смарт-контрактами на Golang.

    Часто задаваемые вопросы