Загрузка данных
package main
import (
"encoding/json"
"fmt"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
)
// ConvertByABI приводит значение val (пришедшее из JSON: float64/json.Number/
// string/bool/[]any/map[string]any) к Go-типу, который ожидает abi.Pack
// для данного abi.Type t.
func ConvertByABI(val any, t abi.Type) (any, error) {
switch t.T {
case abi.UintTy, abi.IntTy:
bi, err := toBigInt(val)
if err != nil {
return nil, fmt.Errorf("field type %s: %w", t.String(), err)
}
return fitIntoIntType(bi, t)
case abi.AddressTy:
s, ok := val.(string)
if !ok {
return nil, fmt.Errorf("expected string for address, got %T", val)
}
if !common.IsHexAddress(s) {
return nil, fmt.Errorf("invalid address: %s", s)
}
return common.HexToAddress(s), nil
case abi.BoolTy:
b, ok := val.(bool)
if !ok {
return nil, fmt.Errorf("expected bool, got %T", val)
}
return b, nil
case abi.StringTy:
s, ok := val.(string)
if !ok {
return nil, fmt.Errorf("expected string, got %T", val)
}
return s, nil
case abi.BytesTy:
s, ok := val.(string)
if !ok {
return nil, fmt.Errorf("expected hex string for bytes, got %T", val)
}
return hexToBytes(s)
case abi.FixedBytesTy:
s, ok := val.(string)
if !ok {
return nil, fmt.Errorf("expected hex string for fixed bytes, got %T", val)
}
b, err := hexToBytes(s)
if err != nil {
return nil, err
}
return fitFixedBytes(b, t.Size)
case abi.SliceTy, abi.ArrayTy:
arr, ok := val.([]any)
if !ok {
return nil, fmt.Errorf("expected array, got %T", val)
}
return convertSlice(arr, *t.Elem)
case abi.TupleTy:
m, ok := val.(map[string]any)
if !ok {
return nil, fmt.Errorf("expected object for tuple, got %T", val)
}
return convertTuple(m, t)
default:
return nil, fmt.Errorf("unsupported abi type: %s", t.String())
}
}
// toBigInt приводит значение любого JSON-числового представления к *big.Int.
func toBigInt(val any) (*big.Int, error) {
switch v := val.(type) {
case json.Number:
bi, ok := new(big.Int).SetString(v.String(), 10)
if !ok {
return nil, fmt.Errorf("invalid number: %s", v)
}
return bi, nil
case string:
s := strings.TrimSpace(v)
base := 10
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
s = s[2:]
base = 16
}
bi, ok := new(big.Int).SetString(s, base)
if !ok {
return nil, fmt.Errorf("invalid number string: %s", v)
}
return bi, nil
case float64:
// ОПАСНО для больших чисел (>2^53) — точность теряется.
// Если исходный JSON парсился с UseNumber(), сюда не попадём.
bi, _ := big.NewFloat(v).Int(nil)
return bi, nil
case int, int32, int64, uint, uint32, uint64:
return new(big.Int).SetInt64(toInt64(v)), nil
default:
return nil, fmt.Errorf("unsupported numeric type: %T", val)
}
}
func toInt64(v any) int64 {
switch n := v.(type) {
case int:
return int64(n)
case int32:
return int64(n)
case int64:
return n
case uint:
return int64(n)
case uint32:
return int64(n)
case uint64:
return int64(n)
}
return 0
}
// fitIntoIntType приводит *big.Int к конкретному Go-типу в зависимости
// от битности ABI-типа (uint8/uint16/uint32/uint64/*big.Int, аналогично int).
func fitIntoIntType(bi *big.Int, t abi.Type) (any, error) {
signed := t.T == abi.IntTy
switch t.Size {
case 8:
if signed {
return int8(bi.Int64()), nil
}
if !bi.IsUint64() {
return nil, fmt.Errorf("value out of range for uint8: %s", bi.String())
}
u := bi.Uint64()
if u > 255 {
return nil, fmt.Errorf("value out of range for uint8: %s", bi.String())
}
return uint8(u), nil
case 16:
if signed {
return int16(bi.Int64()), nil
}
return uint16(bi.Uint64()), nil
case 32:
if signed {
return int32(bi.Int64()), nil
}
return uint32(bi.Uint64()), nil
case 64:
if signed {
return bi.Int64(), nil
}
return bi.Uint64(), nil
default:
// всё, что больше 64 бит (uint72...uint256 / int72...int256) — *big.Int
return bi, nil
}
}
func hexToBytes(s string) ([]byte, error) {
s = strings.TrimPrefix(s, "0x")
s = strings.TrimPrefix(s, "0X")
if len(s)%2 != 0 {
s = "0" + s
}
b := make([]byte, len(s)/2)
_, err := fmt.Sscanf(s, "%x", &b) // либо hex.DecodeString, см. ниже
if err != nil {
return nil, err
}
return b, nil
}