Как правильно использовать смарт-контракты в Go (Golang)?

Я только начал работать с смарт-контрактами на Go и не удовлетворён результатами. Основная проблема заключается в обработке пользовательских ошибок из Solidity. Я использую abigen, и для обработки каждой ошибки мне приходится делать отдельный запрос к контракту через ethclient с использованием CallContract, предварительно собирая для этого msg, затем calldata и, в конечном итоге, парсить ошибки.

Привожу пример неэффективного кода:

```go
callOpts := &bind.CallOpts{
	From:    sender,
	Context: context.Background(),
}

contractABI, err := abi.JSON(strings.NewReader(contract.ContractABI))
if err != nil {
	return fmt.Errorf("failed to parse ABI: %v", err)
}

callData, err := contractABI.Pack("registerProvider", provider)
if err != nil {
	return fmt.Errorf("failed to pack call data: %v", err)
}

msg := ethereum.CallMsg{
	From:     callOpts.From,
	To:       &s.contractAddr,
	Gas:      auth.GasLimit,
	GasPrice: auth.GasPrice,
	Value:    auth.Value,
	Data:     callData,
}

res, err := s.client.CallContract(context.Background(), msg, nil)
if err != nil {
	log.Printf("CallContract failed with error: %v", err)
	log.Printf("Revert data (hex): %s", hex.EncodeToString(res))
	log.Printf("CallMsg details: From=%s, To=%s, Value=%s, Gas=%d", msg.From.Hex(), msg.To.Hex(), msg.Value.String(), msg.Gas)
	if strings.Contains(err.Error(), "execution reverted") {
		reason, parseErr := parseRevertReason(err, res)
		if parseErr != nil {
			return fmt.Errorf("execution reverted: unable to parse reason: %v (raw data: %s)", parseErr, hex.EncodeToString(res))
		}
		return fmt.Errorf("execution reverted: %s", reason)
	}
}
```

Как я могу улучшить свой подход к обработке ошибок из Solidity, чтобы избежать такой громоздкости и повысить эффективность кода?
  • 8 марта 2025 г. 14:14
Ответы на вопрос 1
Использование смарт-контрактов в Go, особенно при обработке ошибок, может быть сложной задачей. Основная проблема, с которой вы столкнулись, — это повторное выполнение запроса к смарт-контракту для каждого вызова, чтобы обрабатывать возможные ошибки, возвращаемые из Solidity. 

Вот несколько советов, которые могут помочь оптимизировать ваш подход:

### 1. Использование `Contract Call`

Вместо использования `CallContract` вручную, вы можете использовать генерированные функции контракта, созданные с помощью `abigen`. Эти функции автоматически обрабатывают ошибки и возвращают более удобные для использования структуры данных.

Например, для функции вашего контракта `registerProvider`, генерированный метод может выглядеть так:

```go
tx, err := s.contract.RegisterProvider(auth, provider)
if err != nil {
    return fmt.Errorf("failed to call registerProvider: %v", err)
}
```

Такой метод автоматически обрабатывает ошибки, возникшие при выполнении транзакции.

### 2. Улучшение обработки ошибок

Если вы хотите обрабатывать ошибки из Solidity напрямую, используйте встроенные механизмы для обработки ошибок в Go. Как только вы получите ошибку, вы можете обрабатывать её, используя различные подходы. Вместо того, чтобы вручную вызывать `CallContract`, вы можете установить уровень логирования ошибок либо в логах, либо возвращая их в виде структур, которые легче обрабатывать.

### 3. Используйте структурированные ошибки

Создайте свою структуру ошибок, чтобы обрабатывать их в одном месте. Это позволит уменьшить дублирование кода. Например:

```go
type SmartContractError struct {
    Reason string
}

func (e *SmartContractError) Error() string {
    return fmt.Sprintf("Smart Contract Error: %s", e.Reason)
}

func parseError(err error, revertData []byte) error {
    reason, _ := parseRevertReason(err, revertData) // используйте вашу функцию вывода ошибки
    return &SmartContractError{Reason: reason}
}
```

### 4. Асинронные вызовы и Goroutines

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

### Обновленный код 

Пример улучшенного кода обработки ошибки может выглядеть так:

```go
package main

import (
    "fmt"
    "log"
    "strings"
    "github.com/ethereum/go-ethereum/rpc"
)

func registerProvider(auth *bind.TransactOpts, provider string) error {
    tx, err := contract.RegisterProvider(auth, provider)
    if err != nil {
        if strings.Contains(err.Error(), "execution reverted") {
            reason, parseErr := parseRevertReason(err, res)
            if parseErr != nil {
                return fmt.Errorf("execution reverted: unable to parse reason: %v (raw data: %s)", parseErr, hex.EncodeToString(res))
            }
            return &SmartContractError{Reason: reason}
        }
        return fmt.Errorf("failed to call registerProvider: %v", err)
    }
    return nil
}
```

### Заключение

Преобразование вашего подхода к более элегантному и структурированному вызову функций смарт-контрактов позволит избежать множества лога и ошибок и упростит диагностику. Использование ролей и действий также повысит читаемость кода и упростит его поддержку.
Похожие вопросы