Обработка ошибок смарт-контрактов 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.