Загрузка данных


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
}