diff --git a/api/gateway/chain/internal/service/gateway/commands/transfer/fee.go b/api/gateway/chain/internal/service/gateway/commands/transfer/fee.go index f622f8d..295d3f4 100644 --- a/api/gateway/chain/internal/service/gateway/commands/transfer/fee.go +++ b/api/gateway/chain/internal/service/gateway/commands/transfer/fee.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" "github.com/shopspring/decimal" "github.com/tech/sendico/gateway/chain/internal/service/gateway/rpcclient" @@ -199,17 +198,11 @@ func erc20Decimals(ctx context.Context, client *rpc.Client, token common.Address if err := client.CallContext(ctx, &hexResp, "eth_call", call, "latest"); err != nil { return 0, merrors.Internal("decimals call failed: " + err.Error()) } - val, err := hexutil.DecodeBig(hexResp) + val, err := shared.DecodeHexUint8(hexResp) if err != nil { return 0, merrors.Internal("decimals decode failed: " + err.Error()) } - if val == nil { - return 0, merrors.Internal("decimals decode failed: empty response") - } - if val.BitLen() > 8 { - return 0, merrors.Internal("decimals decode failed: value out of range") - } - return uint8(val.Uint64()), nil + return val, nil } func toBaseUnits(amount string, decimals uint8) (*big.Int, error) { diff --git a/api/gateway/chain/internal/service/gateway/commands/wallet/onchain_balance.go b/api/gateway/chain/internal/service/gateway/commands/wallet/onchain_balance.go index 61c1cdb..e56ee9a 100644 --- a/api/gateway/chain/internal/service/gateway/commands/wallet/onchain_balance.go +++ b/api/gateway/chain/internal/service/gateway/commands/wallet/onchain_balance.go @@ -8,9 +8,9 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rpc" "github.com/shopspring/decimal" + "github.com/tech/sendico/gateway/chain/internal/service/gateway/shared" "github.com/tech/sendico/gateway/chain/storage/model" "github.com/tech/sendico/pkg/merrors" moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" @@ -104,17 +104,11 @@ func readDecimals(ctx context.Context, client *rpc.Client, token string) (uint8, if err := client.CallContext(ctx, &hexResp, "eth_call", call, "latest"); err != nil { return 0, merrors.Internal("decimals call failed: " + err.Error()) } - val, err := hexutil.DecodeBig(hexResp) + val, err := shared.DecodeHexUint8(hexResp) if err != nil { return 0, merrors.Internal("decimals decode failed: " + err.Error()) } - if val == nil { - return 0, merrors.Internal("decimals decode failed: empty response") - } - if val.BitLen() > 8 { - return 0, merrors.Internal("decimals decode failed: value out of range") - } - return uint8(val.Uint64()), nil + return val, nil } func readBalanceOf(ctx context.Context, client *rpc.Client, token string, wallet string) (*big.Int, error) { @@ -132,7 +126,7 @@ func readBalanceOf(ctx context.Context, client *rpc.Client, token string, wallet if err := client.CallContext(ctx, &hexResp, "eth_call", call, "latest"); err != nil { return nil, merrors.Internal("balanceOf call failed: " + err.Error()) } - bigVal, err := hexutil.DecodeBig(hexResp) + bigVal, err := shared.DecodeHexBig(hexResp) if err != nil { return nil, merrors.Internal("balanceOf decode failed: " + err.Error()) } diff --git a/api/gateway/chain/internal/service/gateway/executor.go b/api/gateway/chain/internal/service/gateway/executor.go index a76222e..cedd55a 100644 --- a/api/gateway/chain/internal/service/gateway/executor.go +++ b/api/gateway/chain/internal/service/gateway/executor.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" "github.com/shopspring/decimal" @@ -315,17 +314,11 @@ func erc20Decimals(ctx context.Context, client *rpc.Client, token common.Address if err := client.CallContext(ctx, &hexResp, "eth_call", call, "latest"); err != nil { return 0, executorInternal("decimals call failed", err) } - val, err := hexutil.DecodeBig(hexResp) + val, err := shared.DecodeHexUint8(hexResp) if err != nil { return 0, executorInternal("decimals decode failed", err) } - if val == nil { - return 0, executorInternal("decimals decode failed", errors.New("empty response")) - } - if val.BitLen() > 8 { - return 0, executorInternal("decimals decode failed", errors.New("value out of range")) - } - return uint8(val.Uint64()), nil + return val, nil } func toBaseUnits(amount string, decimals uint8) (*big.Int, error) { diff --git a/api/gateway/chain/internal/service/gateway/shared/hex.go b/api/gateway/chain/internal/service/gateway/shared/hex.go new file mode 100644 index 0000000..da1fc54 --- /dev/null +++ b/api/gateway/chain/internal/service/gateway/shared/hex.go @@ -0,0 +1,49 @@ +package shared + +import ( + "errors" + "math/big" + "strings" +) + +var ( + errHexEmpty = errors.New("hex value is empty") + errHexInvalid = errors.New("invalid hex number") + errHexOutOfRange = errors.New("hex number out of range") +) + +// DecodeHexBig parses a hex string that may include leading zero digits. +func DecodeHexBig(input string) (*big.Int, error) { + trimmed := strings.TrimSpace(input) + if trimmed == "" { + return nil, errHexEmpty + } + noPrefix := strings.TrimPrefix(trimmed, "0x") + if noPrefix == "" { + return nil, errHexEmpty + } + value := strings.TrimLeft(noPrefix, "0") + if value == "" { + return big.NewInt(0), nil + } + val := new(big.Int) + if _, ok := val.SetString(value, 16); !ok { + return nil, errHexInvalid + } + return val, nil +} + +// DecodeHexUint8 parses a hex string into uint8, allowing leading zeros. +func DecodeHexUint8(input string) (uint8, error) { + val, err := DecodeHexBig(input) + if err != nil { + return 0, err + } + if val == nil { + return 0, errHexInvalid + } + if val.BitLen() > 8 { + return 0, errHexOutOfRange + } + return uint8(val.Uint64()), nil +} diff --git a/api/gateway/chain/internal/service/gateway/shared/hex_test.go b/api/gateway/chain/internal/service/gateway/shared/hex_test.go new file mode 100644 index 0000000..e99a6ef --- /dev/null +++ b/api/gateway/chain/internal/service/gateway/shared/hex_test.go @@ -0,0 +1,16 @@ +package shared + +import "testing" + +func TestDecodeHexUint8_LeadingZeros(t *testing.T) { + t.Parallel() + + const resp = "0x0000000000000000000000000000000000000000000000000000000000000006" + val, err := DecodeHexUint8(resp) + if err != nil { + t.Fatalf("DecodeHexUint8 error: %v", err) + } + if val != 6 { + t.Fatalf("DecodeHexUint8 value = %d, want 6", val) + } +}