Skip to content

Commit

Permalink
add pointers types and test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
Farber98 committed Sep 20, 2024
1 parent 7b76566 commit 18095de
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 11 deletions.
89 changes: 78 additions & 11 deletions core/services/relay/evm/codec/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,21 +161,88 @@ func decodeAddress(data any) (any, error) {
return common.Address(decoded), nil
}

// addressStringDecodeHook converts between string and common.Address, and vice versa.
// It handles both converting a string (hex format) to a common.Address and converting a common.Address to a string.
// addressStringDecodeHook is a decode hook that converts between `from` and `to` types involving string and common.Address types.
// It handles the following conversions:
// 1. `from` string or *string -> `to` common.Address or *common.Address
// 2. `from` common.Address or *common.Address -> `to` string or *string
//
// The function gracefully handles invalid `from` values and `nil` pointers:
// - If `from` is a string or *string and is invalid (e.g., an empty string or a non-hex string),
// it returns an appropriate error (types.ErrInvalidType).
// - If `from` is an empty common.Address{} or *common.Address, the function returns an error
// (types.ErrInvalidType) instead of treating it as the zero address ("0x0000000000000000000000000000000000000000").
// - If `from` is a nil *string or nil *common.Address, the function returns nil without attempting
// to dereference the pointer.
//
// For unsupported `from` and `to` conversions, the function returns the original value unchanged.
func addressStringDecodeHook(from reflect.Type, to reflect.Type, value interface{}) (interface{}, error) {
// Check if we're converting from string to common.Address
if from == reflect.TypeOf("") && to == reflect.TypeOf(common.Address{}) {
// Decode the string (hex format) to common.Address
return decodeAddress(value)
// Handle conversion from string or *string to common.Address or *common.Address
if (from == reflect.TypeOf("") || from == reflect.PointerTo(reflect.TypeOf(""))) &&
(to == reflect.TypeOf(common.Address{}) || to == reflect.TypeOf(&common.Address{})) {
// Extract string value, handling both *string and string
var strValue string
if from == reflect.PointerTo(reflect.TypeOf("")) {
// Handle *string
// Return nil for nil *string values
if value == nil || reflect.ValueOf(value).IsNil() {
return nil, nil
}
strValue = *value.(*string)
} else {
// Handle string
strValue = value.(string)
}

// Decode the string into common.Address, returning an error if invalid
address, err := decodeAddress(strValue)
if err != nil {
return nil, err
}

// Return the decoded address as a *common.Address or common.Address depending on the target type
if to == reflect.TypeOf(&common.Address{}) {
addr := address.(common.Address)
return &addr, nil
}
return address, nil
}

// Check if we're converting from common.Address to string
if from == reflect.TypeOf(common.Address{}) && to == reflect.TypeOf("") {
// Convert the common.Address to its string (hex) representation
return value.(common.Address).Hex(), nil
// Handle conversion from common.Address or *common.Address to string or *string
if (from == reflect.TypeOf(common.Address{}) || from == reflect.TypeOf(&common.Address{})) &&
(to == reflect.TypeOf("") || to == reflect.PointerTo(reflect.TypeOf(""))) {

// Handle nil *common.Address values
if from == reflect.TypeOf(&common.Address{}) {
if value == nil || reflect.ValueOf(value).IsNil() {
return nil, nil
}
}

var addressStr string
if from == reflect.TypeOf(&common.Address{}) {
// Handle *common.Address
if (*value.(*common.Address) == common.Address{}) {
// Return an error if the *common.Address is empty
return nil, fmt.Errorf("%w: empty address", commontypes.ErrInvalidType)
}
addressStr = value.(*common.Address).Hex()
} else {
// Handle common.Address
if (value.(common.Address) == common.Address{}) {
// Return an error if the common.Address is empty
return nil, fmt.Errorf("%w: empty address", commontypes.ErrInvalidType)
}
addressStr = value.(common.Address).Hex()
}

// If converting to *string, return a *string
if to == reflect.PointerTo(reflect.TypeOf("")) {
return &addressStr, nil
}
// Otherwise, return the string
return addressStr, nil
}

// If no valid conversion, return the original value unchanged
// Return the original value unchanged for unsupported conversions
return value, nil
}
140 changes: 140 additions & 0 deletions core/services/relay/evm/codec/codec_hooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package codec

import (
"errors"
"reflect"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/smartcontractkit/chainlink-common/pkg/types"
)

func TestAddressStringDecodeHook(t *testing.T) {
t.Parallel()

var nilAddress *common.Address
hexString := "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"
address := common.HexToAddress(hexString)
addressToString := address.Hex()
emptyAddress := common.Address{}
emptyString := ""

t.Run("Converts from string to common.Address", func(t *testing.T) {
result, err := addressStringDecodeHook(reflect.TypeOf(""), reflect.TypeOf(common.Address{}), hexString)
require.NoError(t, err)
require.IsType(t, common.Address{}, result)
assert.Equal(t, address, result)
})

t.Run("Converts from string to *common.Address", func(t *testing.T) {
result, err := addressStringDecodeHook(reflect.TypeOf(""), reflect.TypeOf(&common.Address{}), hexString)
require.NoError(t, err)
assert.Equal(t, &address, result)
})

t.Run("Converts from *string to common.Address", func(t *testing.T) {
result, err := addressStringDecodeHook(reflect.PointerTo(reflect.TypeOf("")), reflect.TypeOf(common.Address{}), &hexString)
require.NoError(t, err)
require.IsType(t, common.Address{}, result)
assert.Equal(t, address, result)
})

t.Run("Converts from *string to *common.Address", func(t *testing.T) {
result, err := addressStringDecodeHook(reflect.PointerTo(reflect.TypeOf("")), reflect.TypeOf(&common.Address{}), &hexString)
require.NoError(t, err)
assert.Equal(t, &address, result)
})

t.Run("Converts from common.Address to string", func(t *testing.T) {
result, err := addressStringDecodeHook(reflect.TypeOf(common.Address{}), reflect.TypeOf(""), address)
require.NoError(t, err)
require.IsType(t, "", result)
assert.Equal(t, addressToString, result)
})

t.Run("Converts from common.Address to *string", func(t *testing.T) {
result, err := addressStringDecodeHook(reflect.TypeOf(common.Address{}), reflect.PointerTo(reflect.TypeOf("")), address)
require.NoError(t, err)
assert.Equal(t, &addressToString, result)
})

t.Run("Converts from *common.Address to string", func(t *testing.T) {
result, err := addressStringDecodeHook(reflect.TypeOf(&common.Address{}), reflect.TypeOf(""), &address)
require.NoError(t, err)
assert.Equal(t, addressToString, result)
})

t.Run("Converts from *common.Address to *string", func(t *testing.T) {
result, err := addressStringDecodeHook(reflect.TypeOf(&common.Address{}), reflect.PointerTo(reflect.TypeOf("")), &address)
require.NoError(t, err)
assert.Equal(t, &addressToString, result)
})

t.Run("Returns error on invalid hex string", func(t *testing.T) {
_, err := addressStringDecodeHook(reflect.TypeOf(""), reflect.TypeOf(common.Address{}), "NotAHexString")
assert.True(t, errors.Is(err, types.ErrInvalidType))
_, err = addressStringDecodeHook(reflect.TypeOf(""), reflect.TypeOf(&common.Address{}), "NotAHexString")
assert.True(t, errors.Is(err, types.ErrInvalidType))
})

t.Run("Returns error on empty string and empty *string", func(t *testing.T) {
_, err := addressStringDecodeHook(reflect.TypeOf(""), reflect.TypeOf(common.Address{}), emptyString)
assert.True(t, errors.Is(err, types.ErrInvalidType), "Expected an error for empty string")
_, err = addressStringDecodeHook(reflect.TypeOf(""), reflect.TypeOf(&common.Address{}), emptyString)
assert.True(t, errors.Is(err, types.ErrInvalidType), "Expected an error for empty string")
_, err = addressStringDecodeHook(reflect.PointerTo(reflect.TypeOf("")), reflect.TypeOf(common.Address{}), &emptyString)
assert.True(t, errors.Is(err, types.ErrInvalidType), "Expected an error for empty string")
_, err = addressStringDecodeHook(reflect.PointerTo(reflect.TypeOf("")), reflect.TypeOf(&common.Address{}), &emptyString)
assert.True(t, errors.Is(err, types.ErrInvalidType), "Expected an error for empty string")
})

t.Run("Returns error for empty common.Address and empty *common.Address", func(t *testing.T) {
_, err := addressStringDecodeHook(reflect.TypeOf(common.Address{}), reflect.TypeOf(""), emptyAddress)
assert.True(t, errors.Is(err, types.ErrInvalidType), "Expected error for empty common.Address")
_, err = addressStringDecodeHook(reflect.TypeOf(common.Address{}), reflect.PointerTo(reflect.TypeOf("")), emptyAddress)
assert.True(t, errors.Is(err, types.ErrInvalidType), "Expected error for empty common.Address")
_, err = addressStringDecodeHook(reflect.TypeOf(&common.Address{}), reflect.TypeOf(""), &emptyAddress)
assert.True(t, errors.Is(err, types.ErrInvalidType), "Expected error for empty *common.Address")
_, err = addressStringDecodeHook(reflect.TypeOf(&common.Address{}), reflect.PointerTo(reflect.TypeOf("")), &emptyAddress)
assert.True(t, errors.Is(err, types.ErrInvalidType), "Expected error for empty *common.Address")
})

t.Run("Returns nil for nil *string", func(t *testing.T) {
var nilString *string
result, err := addressStringDecodeHook(reflect.PointerTo(reflect.TypeOf("")), reflect.TypeOf(common.Address{}), nilString)
require.NoError(t, err)
assert.Nil(t, result, "Expected nil to be returned for nil *string input")
result, err = addressStringDecodeHook(reflect.PointerTo(reflect.TypeOf("")), reflect.TypeOf(&common.Address{}), nilString)
require.NoError(t, err)
assert.Nil(t, result, "Expected nil to be returned for nil *string input")
})
t.Run("Returns nil for nil *common.Address", func(t *testing.T) {
result, err := addressStringDecodeHook(reflect.TypeOf(&common.Address{}), reflect.TypeOf(""), nilAddress)
require.NoError(t, err)
assert.Nil(t, result, "Expected nil to be returned for nil common.Address input")
result, err = addressStringDecodeHook(reflect.TypeOf(&common.Address{}), reflect.PointerTo(reflect.TypeOf("")), nilAddress)
require.NoError(t, err)
assert.Nil(t, result, "Expected nil to be returned for nil common.Address input")
})

t.Run("Returns input unchanged for unsupported conversion", func(t *testing.T) {
unsupportedCases := []struct {
fromType reflect.Type
toType reflect.Type
input interface{}
}{
{fromType: reflect.TypeOf(12345), toType: reflect.TypeOf(common.Address{}), input: 12345},
{fromType: reflect.TypeOf(12345), toType: reflect.TypeOf(""), input: 12345},
{fromType: reflect.TypeOf([]byte{}), toType: reflect.TypeOf(common.Address{}), input: []byte{0x01, 0x02, 0x03}},
}

for _, tc := range unsupportedCases {
result, err := addressStringDecodeHook(tc.fromType, tc.toType, tc.input)
require.NoError(t, err)
assert.Equal(t, tc.input, result, "Expected original value to be returned for unsupported conversion")
}
})
}

0 comments on commit 18095de

Please sign in to comment.