diff --git a/pkg/utils/hex/hex.go b/pkg/utils/hex/hex.go new file mode 100644 index 000000000..926af75da --- /dev/null +++ b/pkg/utils/hex/hex.go @@ -0,0 +1,55 @@ +package hex + +import ( + "encoding/hex" + "errors" + "fmt" + "math/big" + "strings" +) + +// EnsurePrefix adds the prefix (0x) to a given hex string. +func EnsurePrefix(str string) string { + if !strings.HasPrefix(str, "0x") { + str = "0x" + str + } + return str +} + +// ParseBig parses the given hex string and returns error if it is invalid. +func ParseBig(s string) (*big.Int, error) { + n, ok := new(big.Int).SetString(s, 16) + if !ok { + return nil, fmt.Errorf(`failed to convert "%s" as hex to big.Int`, s) + } + return n, nil +} + +// TrimPrefix removes the prefix (0x) of a given hex string. +func TrimPrefix(str string) string { + if HasPrefix(str) { + return str[2:] + } + return str +} + +// HasPrefix returns true if the string starts with 0x. +func HasPrefix(str string) bool { + return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') +} + +// DecodeString parses the given hex string to bytes, +// it can return error if the hex string is invalid. +// Follows the semantic of ethereum's FromHex. +func DecodeString(s string) (b []byte, err error) { + if !HasPrefix(s) { + err = errors.New("hex string must have 0x prefix") + } else { + s = s[2:] + if len(s)%2 == 1 { + s = "0" + s + } + b, err = hex.DecodeString(s) + } + return +} diff --git a/pkg/utils/hex/hex_test.go b/pkg/utils/hex/hex_test.go new file mode 100644 index 000000000..d09cb9b98 --- /dev/null +++ b/pkg/utils/hex/hex_test.go @@ -0,0 +1,103 @@ +package hex_test + +import ( + "testing" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/hex" + "github.com/stretchr/testify/assert" +) + +func TestParseBig(t *testing.T) { + t.Parallel() + + t.Run("parses successfully", func(t *testing.T) { + t.Parallel() + + _, err := hex.ParseBig("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F") + assert.NoError(t, err) + }) + + t.Run("returns error", func(t *testing.T) { + t.Parallel() + + _, err := hex.ParseBig("0xabc") + assert.Error(t, err) + }) +} + +func TestTrimPrefix(t *testing.T) { + t.Parallel() + + t.Run("trims prefix", func(t *testing.T) { + t.Parallel() + + s := hex.TrimPrefix("0xabc") + assert.Equal(t, "abc", s) + }) + + t.Run("returns the same string if it doesn't have prefix", func(t *testing.T) { + t.Parallel() + + s := hex.TrimPrefix("defg") + assert.Equal(t, "defg", s) + }) +} + +func TestHasPrefix(t *testing.T) { + t.Parallel() + + t.Run("has prefix", func(t *testing.T) { + t.Parallel() + + r := hex.HasPrefix("0xabc") + assert.True(t, r) + }) + + t.Run("doesn't have prefix", func(t *testing.T) { + t.Parallel() + + r := hex.HasPrefix("abc") + assert.False(t, r) + }) + + t.Run("has 0x suffix", func(t *testing.T) { + t.Parallel() + + r := hex.HasPrefix("abc0x") + assert.False(t, r) + }) +} + +func TestDecodeString(t *testing.T) { + t.Parallel() + + t.Run("0x prefix missing", func(t *testing.T) { + t.Parallel() + + _, err := hex.DecodeString("abcd") + assert.Error(t, err) + }) + + t.Run("wrong hex characters", func(t *testing.T) { + t.Parallel() + + _, err := hex.DecodeString("0xabcdzzz") + assert.Error(t, err) + }) + + t.Run("valid hex string", func(t *testing.T) { + t.Parallel() + + b, err := hex.DecodeString("0x1234") + assert.NoError(t, err) + assert.Equal(t, []byte{0x12, 0x34}, b) + }) + + t.Run("prepend odd length with zero", func(t *testing.T) { + t.Parallel() + + b, err := hex.DecodeString("0x123") + assert.NoError(t, err) + assert.Equal(t, []byte{0x1, 0x23}, b) + }) +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 9adff93e7..57ea147c3 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -4,7 +4,6 @@ import ( "context" "math" mrand "math/rand" - "strings" "time" "github.com/smartcontractkit/chainlink-common/pkg/services" @@ -50,11 +49,3 @@ func IsZero[C comparable](val C) bool { var zero C return zero == val } - -// EnsureHexPrefix adds the prefix (0x) to a given hex string. -func EnsureHexPrefix(str string) string { - if !strings.HasPrefix(str, "0x") { - str = "0x" + str - } - return str -}