Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add bytes to string modifier for solana contracts #1040

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 104 additions & 2 deletions pkg/codec/byte_string_modifier.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,79 @@
package codec

import (
"bytes"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"reflect"

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

type ExampleAddressModifier struct{}

func (m *ExampleAddressModifier) EncodeAddress(bts []byte) (string, error) {
if len(bts) > 32 {
return "", errors.New("upexpected address byte length")
}

normalized := make([]byte, 32)

// apply byts as big endian
copy(normalized[:], bts[:])

return base64.StdEncoding.EncodeToString(normalized), nil
}

func (m *ExampleAddressModifier) DecodeAddress(str string) ([]byte, error) {
decodedBytes, err := base64.StdEncoding.DecodeString(str)
if err != nil {
return nil, err
}

if len(decodedBytes) != 32 {
return nil, errors.New("unexpected address byte length")
}

return decodedBytes, nil
}

func (m *ExampleAddressModifier) Length() int {
return 32
}

func ExampleAddressBytesToStringModifier() {
type onChainNested struct {
X []byte
}

type onChain struct {
A [32]byte
B onChainNested
}

encoder := &ExampleAddressModifier{}
mod := NewPathTraverseAddressBytesToStringModifier([]string{"B.X"}, encoder, true)

// call RetypeToOffChain first with empty itemType to set base types
offChainType, _ := mod.RetypeToOffChain(reflect.TypeOf(&onChain{}), "")

fmt.Println("offChainType:")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stays?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of the example and should stay, yes.

fmt.Println(offChainType)
// offChainType:
// struct { A: string; B: struct { X: string } }

// calls to transform can transform the entire struct or nested fields specified by itemType
onChainAddress := [32]byte{}
_, _ = rand.Read(onChainAddress[:])

offChainAddress, _ := mod.TransformToOffChain(onChainAddress, "A")

// the onChainAddress value is modified to the offChainType
fmt.Println(offChainAddress)
}

// AddressModifier defines the interface for encoding, decoding, and handling addresses.
// This interface allows for chain-specific logic to be injected into the modifier without
// modifying the common repository.
Expand Down Expand Up @@ -66,6 +133,41 @@ func NewPathTraverseAddressBytesToStringModifier(
return m
}

func NewConstrainedLengthBytesToStringModifier(
fields []string,
maxLen int,
) Modifier {
return NewPathTraverseAddressBytesToStringModifier(fields, &constrainedLengthBytesToStringModifier{maxLen: maxLen}, false)
}

func NewPathTraverseConstrainedLengthBytesToStringModifier(
fields []string,
maxLen int,
enablePathTraverse bool,
) Modifier {
return NewPathTraverseAddressBytesToStringModifier(fields, &constrainedLengthBytesToStringModifier{maxLen: maxLen}, enablePathTraverse)
}

type constrainedLengthBytesToStringModifier struct {
maxLen int
}

func (m constrainedLengthBytesToStringModifier) EncodeAddress(bts []byte) (string, error) {
return string(bytes.Trim(bts, "\x00")), nil
}

func (m constrainedLengthBytesToStringModifier) DecodeAddress(str string) ([]byte, error) {
output := make([]byte, m.maxLen)

copy(output, []byte(str)[:])

return output, nil
}

func (m constrainedLengthBytesToStringModifier) Length() int {
return m.maxLen
}

type bytesToStringModifier struct {
// Injected modifier that contains chain-specific logic
modifier AddressModifier
Expand Down Expand Up @@ -110,7 +212,7 @@ func (m *bytesToStringModifier) TransformToOnChain(offChainValue any, itemType s
}

if itemType != "" {
return valueForPath(reflect.ValueOf(modified), itemType)
return ValueForPath(reflect.ValueOf(modified), itemType)
}

return modified, nil
Expand All @@ -132,7 +234,7 @@ func (m *bytesToStringModifier) TransformToOffChain(onChainValue any, itemType s
}

if itemType != "" {
return valueForPath(reflect.ValueOf(modified), itemType)
return ValueForPath(reflect.ValueOf(modified), itemType)
}

return modified, nil
Expand Down
25 changes: 23 additions & 2 deletions pkg/codec/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ func (m *ModifiersConfig) UnmarshalJSON(data []byte) error {
(*m)[i] = &PropertyExtractorConfig{}
case ModifierAddressToString:
(*m)[i] = &AddressBytesToStringModifierConfig{}
case ModifierBytesToString:
(*m)[i] = &ConstrainedBytesToStringModifierConfig{}
case ModifierWrapper:
(*m)[i] = &WrapperModifierConfig{}
case ModifierPreCodec:
Expand Down Expand Up @@ -103,6 +105,7 @@ const (
ModifierEpochToTime ModifierType = "epoch to time"
ModifierExtractProperty ModifierType = "extract property"
ModifierAddressToString ModifierType = "address to string"
ModifierBytesToString ModifierType = "constrained bytes to string"
ModifierWrapper ModifierType = "wrapper"
)

Expand Down Expand Up @@ -334,11 +337,12 @@ func (e *EpochToTimeModifierConfig) MarshalJSON() ([]byte, error) {
}

type PropertyExtractorConfig struct {
FieldName string
FieldName string
EnablePathTraverse bool
}

func (c *PropertyExtractorConfig) ToModifier(_ ...mapstructure.DecodeHookFunc) (Modifier, error) {
return NewPropertyExtractor(upperFirstCharacter(c.FieldName)), nil
return NewPathTraversePropertyExtractor(upperFirstCharacter(c.FieldName), c.EnablePathTraverse), nil
}

func (c *PropertyExtractorConfig) MarshalJSON() ([]byte, error) {
Expand Down Expand Up @@ -368,6 +372,23 @@ func (c *AddressBytesToStringModifierConfig) MarshalJSON() ([]byte, error) {
})
}

type ConstrainedBytesToStringModifierConfig struct {
Fields []string
MaxLen int
EnablePathTraverse bool
}

func (c *ConstrainedBytesToStringModifierConfig) ToModifier(_ ...mapstructure.DecodeHookFunc) (Modifier, error) {
return NewPathTraverseConstrainedLengthBytesToStringModifier(c.Fields, c.MaxLen, c.EnablePathTraverse), nil
}

func (c *ConstrainedBytesToStringModifierConfig) MarshalJSON() ([]byte, error) {
return json.Marshal(&modifierMarshaller[ConstrainedBytesToStringModifierConfig]{
Type: ModifierBytesToString,
T: c,
})
}

// WrapperModifierConfig replaces each field based on cfg map keys with a struct containing one field with the value of the original field which has is named based on map values.
// Wrapper modifier does not maintain the original pointers.
// Wrapper modifier config shouldn't edit fields that affect each other since the results are not deterministic.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package types_test
package binary_test

import (
"context"
Expand All @@ -9,8 +9,8 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary"
)

// ExampleCodec provides a minimal example of constructing and using a codec.
func ExampleCodec() {
// ExampleBigEndian provides a minimal example of constructing and using a codec.
func ExampleBigEndian() {
ctx := context.Background()
typeCodec, _ := binary.BigEndian().BigInt(32, true)

Expand Down
4 changes: 2 additions & 2 deletions pkg/codec/epoch_to_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (m *timeToUnixModifier) TransformToOnChain(offChainValue any, itemType stri
}

if itemType != "" {
return valueForPath(reflect.ValueOf(modified), itemType)
return ValueForPath(reflect.ValueOf(modified), itemType)
}

return modified, nil
Expand All @@ -77,7 +77,7 @@ func (m *timeToUnixModifier) TransformToOffChain(onChainValue any, itemType stri
}

if itemType != "" {
return valueForPath(reflect.ValueOf(modified), itemType)
return ValueForPath(reflect.ValueOf(modified), itemType)
}

return modified, nil
Expand Down
21 changes: 21 additions & 0 deletions pkg/codec/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"math"
"math/big"
"time"

"github.com/smartcontractkit/chainlink-common/pkg/codec"
Expand Down Expand Up @@ -53,6 +54,26 @@ func (ExampleStructJSONCodec) CreateType(_ string, _ bool) (any, error) {
return &OnChainStruct{}, nil
}

type ExampleOffChainNestedTestStruct struct {
X *int
Y *big.Int
Z map[string]any
}

type ExampleOffChainTestStruct struct {
A string
B ExampleOffChainNestedTestStruct
C []byte
}

type ExampleOnChainNestedTestStruct struct{}

type ExampleOnChainTestStruct struct {
A []byte
B ExampleOnChainNestedTestStruct
C [32]byte
}

type OnChainStruct struct {
Aa int64
Bb string
Expand Down
4 changes: 2 additions & 2 deletions pkg/codec/hard_coder.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func (m *onChainHardCoder) TransformToOnChain(offChainValue any, itemType string
}

if itemType != "" {
return valueForPath(reflect.ValueOf(modified), itemType)
return ValueForPath(reflect.ValueOf(modified), itemType)
}

return modified, nil
Expand Down Expand Up @@ -143,7 +143,7 @@ func (m *onChainHardCoder) TransformToOffChain(onChainValue any, itemType string
}

if itemType != "" {
return valueForPath(reflect.ValueOf(modified), itemType)
return ValueForPath(reflect.ValueOf(modified), itemType)
}

return modified, nil
Expand Down
80 changes: 1 addition & 79 deletions pkg/codec/modifier_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func (m *modifierBase[T]) selectType(inputValue any, savedType reflect.Type, ite
if itemType != "" {
into := reflect.New(savedType)

if err := applyValueForPath(into, reflect.ValueOf(inputValue), itemType); err != nil {
if err := SetValueAtPath(into, reflect.ValueOf(inputValue), itemType); err != nil {
return nil, itemType, err
}

Expand Down Expand Up @@ -394,84 +394,6 @@ func typeForPath(from reflect.Type, itemType string) (reflect.Type, error) {
}
}

func valueForPath(from reflect.Value, itemType string) (any, error) {
if itemType == "" {
return from.Interface(), nil
}

switch from.Kind() {
case reflect.Pointer:
elem, err := valueForPath(from.Elem(), itemType)
if err != nil {
return nil, err
}

return elem, nil
case reflect.Array, reflect.Slice:
return nil, fmt.Errorf("%w: cannot extract a field from an array or slice", types.ErrInvalidType)
case reflect.Struct:
head, tail := ItemTyper(itemType).Next()

field := from.FieldByName(head)
if !field.IsValid() {
return nil, fmt.Errorf("%w: field not found for path %s and itemType %s", types.ErrInvalidType, from, itemType)
}

if tail == "" {
return field.Interface(), nil
}

return valueForPath(field, tail)
default:
return nil, fmt.Errorf("%w: cannot extract a field from kind %s", types.ErrInvalidType, from.Kind())
}
}

func applyValueForPath(vInto, vField reflect.Value, itemType string) error {
switch vInto.Kind() {
case reflect.Pointer:
if !vInto.Elem().IsValid() {
into := reflect.New(vInto.Type().Elem())

vInto.Set(into)
}

err := applyValueForPath(vInto.Elem(), vField, itemType)
if err != nil {
return err
}

return nil
case reflect.Array, reflect.Slice:
return fmt.Errorf("%w: cannot set a field from an array or slice", types.ErrInvalidType)
case reflect.Struct:
head, tail := ItemTyper(itemType).Next()

field := vInto.FieldByName(head)
if !field.IsValid() {
return fmt.Errorf("%w: invalid field for type %s and name %s", types.ErrInvalidType, vInto, head)
}

if tail == "" {
if field.Type() != vField.Type() {
return fmt.Errorf("%w: value type mismatch for field %s", types.ErrInvalidType, head)
}

if !field.CanSet() {
return fmt.Errorf("%w: cannot set field %s", types.ErrInvalidType, head)
}

field.Set(vField)

return nil
}

return applyValueForPath(field, vField, tail)
default:
return fmt.Errorf("%w: cannot set a field from kind %s", types.ErrInvalidType, vInto.Kind())
}
}

type PathMappingError struct {
Err error
Path string
Expand Down
Loading
Loading