Skip to content

Commit

Permalink
BCF-3015 Add Configuration for Encoding (#616)
Browse files Browse the repository at this point in the history
Solana allows multiple encoding types for account data. This commit adds support for
two encoding types `borsh` and `bincode`.
  • Loading branch information
EasterTheBunny committed Mar 15, 2024
1 parent fc4541c commit 786829f
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 32 deletions.
2 changes: 1 addition & 1 deletion pkg/solana/chainreader/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainReader
return err
}

idlCodec, err := codec.NewIDLCodec(idl)
idlCodec, err := codec.NewIDLCodec(idl, config.BuilderForEncoding(method.Encoding))
if err != nil {
return err
}
Expand Down
28 changes: 14 additions & 14 deletions pkg/solana/chainreader/chain_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

codeccommon "github.com/smartcontractkit/chainlink-common/pkg/codec"
"github.com/smartcontractkit/chainlink-common/pkg/codec/encodings"
"github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary"
"github.com/smartcontractkit/chainlink-common/pkg/logger"
commontestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils"
"github.com/smartcontractkit/chainlink-common/pkg/types"
Expand Down Expand Up @@ -239,7 +240,7 @@ func newTestIDLAndCodec(t *testing.T) (string, codec.IDL, encodings.CodecFromTyp
t.FailNow()
}

entry, err := codec.NewIDLCodec(idl)
entry, err := codec.NewIDLCodec(idl, binary.LittleEndian())
if err != nil {
t.Logf("failed to create new codec from test IDL: %s", err.Error())
t.FailNow()
Expand All @@ -263,7 +264,6 @@ func newTestConfAndCodec(t *testing.T) (encodings.CodecFromTypeCodec, config.Cha
Procedures: []config.ChainReaderProcedure{
{
IDLAccount: testutils.TestStructWithNestedStruct,
Type: config.ProcedureTypeAnchor,
OutputModifications: codeccommon.ModifiersConfig{
&codeccommon.RenameModifierConfig{Fields: map[string]string{"Value": "V"}},
},
Expand Down Expand Up @@ -337,19 +337,19 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) {
Methods: map[string]config.ChainDataReader{
MethodTakingLatestParamsReturningTestStruct: {
AnchorIDL: fmt.Sprintf(baseIDL, testStructIDL, strings.Join([]string{midLevelStructIDL, innerStructIDL}, ",")),
Encoding: config.EncodingTypeBorsh,
Procedures: []config.ChainReaderProcedure{
{
IDLAccount: "TestStruct",
Type: config.ProcedureTypeAnchor,
},
},
},
MethodReturningUint64: {
AnchorIDL: fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""),
Encoding: config.EncodingTypeBorsh,
Procedures: []config.ChainReaderProcedure{
{
IDLAccount: "SimpleUint64Value",
Type: config.ProcedureTypeAnchor,
OutputModifications: codeccommon.ModifiersConfig{
&codeccommon.PropertyExtractorConfig{FieldName: "I"},
},
Expand All @@ -358,10 +358,10 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) {
},
DifferentMethodReturningUint64: {
AnchorIDL: fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""),
Encoding: config.EncodingTypeBorsh,
Procedures: []config.ChainReaderProcedure{
{
IDLAccount: "SimpleUint64Value",
Type: config.ProcedureTypeAnchor,
OutputModifications: codeccommon.ModifiersConfig{
&codeccommon.PropertyExtractorConfig{FieldName: "I"},
},
Expand All @@ -370,10 +370,10 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) {
},
MethodReturningUint64Slice: {
AnchorIDL: fmt.Sprintf(baseIDL, uint64SliceBaseTypeIDL, ""),
Encoding: config.EncodingTypeBincode,
Procedures: []config.ChainReaderProcedure{
{
IDLAccount: "Uint64Slice",
Type: config.ProcedureTypeAnchor,
OutputModifications: codeccommon.ModifiersConfig{
&codeccommon.PropertyExtractorConfig{FieldName: "Vals"},
},
Expand All @@ -382,10 +382,10 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) {
},
MethodReturningSeenStruct: {
AnchorIDL: fmt.Sprintf(baseIDL, testStructIDL, strings.Join([]string{midLevelStructIDL, innerStructIDL}, ",")),
Encoding: config.EncodingTypeBorsh,
Procedures: []config.ChainReaderProcedure{
{
IDLAccount: "TestStruct",
Type: config.ProcedureTypeAnchor,
OutputModifications: codeccommon.ModifiersConfig{
&codeccommon.HardCodeModifierConfig{OffChainValues: map[string]any{"ExtraField": AnyExtraValue}},
// &codeccommon.RenameModifierConfig{Fields: map[string]string{"NestedStruct.Inner.IntVal": "I"}},
Expand All @@ -399,10 +399,10 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) {
Methods: map[string]config.ChainDataReader{
MethodReturningUint64: {
AnchorIDL: fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""),
Encoding: config.EncodingTypeBorsh,
Procedures: []config.ChainReaderProcedure{
{
IDLAccount: "SimpleUint64Value",
Type: config.ProcedureTypeAnchor,
OutputModifications: codeccommon.ModifiersConfig{
&codeccommon.PropertyExtractorConfig{FieldName: "I"},
},
Expand Down Expand Up @@ -451,7 +451,7 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, contractNam
// returning the expected error to satisfy the test
return types.ErrNotFound
case AnyContractName + MethodReturningUint64:
cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""))
cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""), config.EncodingTypeBorsh)
onChainStruct := struct {
I uint64
}{
Expand All @@ -466,7 +466,7 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, contractNam

r.client.On("ReadAll", mock.Anything, mock.Anything).Return(bts, nil).Once()
case AnyContractName + MethodReturningUint64Slice:
cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64SliceBaseTypeIDL, ""))
cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64SliceBaseTypeIDL, ""), config.EncodingTypeBincode)
onChainStruct := struct {
Vals []uint64
}{
Expand All @@ -480,7 +480,7 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, contractNam

r.client.On("ReadAll", mock.Anything, mock.Anything).Return(bts, nil).Once()
case AnySecondContractName + MethodReturningUint64, AnyContractName + DifferentMethodReturningUint64:
cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""))
cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""), config.EncodingTypeBorsh)
onChainStruct := struct {
I uint64
}{
Expand All @@ -506,7 +506,7 @@ func (r *wrappedTestChainReader) GetLatestValue(ctx context.Context, contractNam
nextTestStruct := r.testStructQueue[0]
r.testStructQueue = r.testStructQueue[1:len(r.testStructQueue)]

cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, testStructIDL, strings.Join([]string{midLevelStructIDL, innerStructIDL}, ",")))
cdc := makeTestCodec(r.test, fmt.Sprintf(baseIDL, testStructIDL, strings.Join([]string{midLevelStructIDL, innerStructIDL}, ",")), config.EncodingTypeBorsh)
bts, err := cdc.Encode(ctx, nextTestStruct, "TestStruct")
if err != nil {
r.test.FailNow()
Expand Down Expand Up @@ -575,7 +575,7 @@ func (r *chainReaderInterfaceTester) MaxWaitTimeForEvents() time.Duration {
return maxWaitTime
}

func makeTestCodec(t *testing.T, rawIDL string) encodings.CodecFromTypeCodec {
func makeTestCodec(t *testing.T, rawIDL string, encoding config.EncodingType) encodings.CodecFromTypeCodec {
t.Helper()

var idl codec.IDL
Expand All @@ -584,7 +584,7 @@ func makeTestCodec(t *testing.T, rawIDL string) encodings.CodecFromTypeCodec {
t.FailNow()
}

testCodec, err := codec.NewIDLCodec(idl)
testCodec, err := codec.NewIDLCodec(idl, config.BuilderForEncoding(encoding))
if err != nil {
t.Logf("failed to create new codec from test IDL: %s", err.Error())
t.FailNow()
Expand Down
5 changes: 2 additions & 3 deletions pkg/solana/codec/solana.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (

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

Expand Down Expand Up @@ -62,11 +61,11 @@ func NewNamedModifierCodec(original types.RemoteCodec, itemType string, modifier
}

// NewIDLCodec is for Anchor custom types
func NewIDLCodec(idl IDL) (encodings.CodecFromTypeCodec, error) {
func NewIDLCodec(idl IDL, builder encodings.Builder) (encodings.CodecFromTypeCodec, error) {
accounts := make(map[string]encodings.TypeCodec)

refs := &codecRefs{
builder: binary.LittleEndian(),
builder: builder,
codecs: make(map[string]encodings.TypeCodec),
typeDefs: idl.Types,
dependencies: make(map[string][]string),
Expand Down
5 changes: 3 additions & 2 deletions pkg/solana/codec/solana_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

codeccommon "github.com/smartcontractkit/chainlink-common/pkg/codec"
"github.com/smartcontractkit/chainlink-common/pkg/codec/encodings"
"github.com/smartcontractkit/chainlink-common/pkg/codec/encodings/binary"
"github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"

Expand Down Expand Up @@ -113,7 +114,7 @@ func TestNewIDLCodec_CircularDependency(t *testing.T) {
t.FailNow()
}

_, err := codec.NewIDLCodec(idl)
_, err := codec.NewIDLCodec(idl, binary.LittleEndian())

assert.ErrorIs(t, err, types.ErrInvalidConfig)
}
Expand All @@ -127,7 +128,7 @@ func newTestIDLAndCodec(t *testing.T) (string, codec.IDL, encodings.CodecFromTyp
t.FailNow()
}

entry, err := codec.NewIDLCodec(idl)
entry, err := codec.NewIDLCodec(idl, binary.LittleEndian())
if err != nil {
t.Logf("failed to create new codec from test IDL: %s", err.Error())
t.FailNow()
Expand Down
74 changes: 62 additions & 12 deletions pkg/solana/config/chain_reader.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package config

import "github.com/smartcontractkit/chainlink-common/pkg/codec"
import (
"encoding/json"
"fmt"

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

type ChainReader struct {
Namespaces map[string]ChainReaderMethods `json:"namespaces" toml:"namespaces"`
Expand All @@ -11,29 +19,71 @@ type ChainReaderMethods struct {
}

type ChainDataReader struct {
AnchorIDL string `json:"anchorIDL" toml:"anchorIDL"`
AnchorIDL string `json:"anchorIDL" toml:"anchorIDL"`
// Encoding defines the type of encoding used for on-chain data. Currently supported
// are 'borsh' and 'bincode'.
Encoding EncodingType `json:"encoding" toml:"encoding"`
Procedures []ChainReaderProcedure `json:"procedures" toml:"procedures"`
}

type ProcedureType int
type EncodingType int

const (
ProcedureTypeInternal ProcedureType = iota
ProcedureTypeAnchor
EncodingTypeBorsh EncodingType = iota
EncodingTypeBincode

encodingTypeBorshStr = "borsh"
encodingTypeBincodeStr = "bincode"
)

func (t EncodingType) MarshalJSON() ([]byte, error) {
switch t {
case EncodingTypeBorsh:
return json.Marshal(encodingTypeBorshStr)
case EncodingTypeBincode:
return json.Marshal(encodingTypeBincodeStr)
default:
return nil, fmt.Errorf("%w: unrecognized encoding type: %d", types.ErrInvalidConfig, t)
}
}

func (t *EncodingType) UnmarshalJSON(data []byte) error {
var str string

if err := json.Unmarshal(data, &str); err != nil {
return fmt.Errorf("%w: %s", types.ErrInvalidConfig, err.Error())
}

switch str {
case encodingTypeBorshStr:
*t = EncodingTypeBorsh
case encodingTypeBincodeStr:
*t = EncodingTypeBincode
default:
return fmt.Errorf("%w: unrecognized encoding type: %s", types.ErrInvalidConfig, str)
}

return nil
}

type ChainReaderProcedure chainDataProcedureFields

type chainDataProcedureFields struct {
// IDLAccount refers to the account defined in the IDL.
IDLAccount string `json:"idlAccount"`
// Type describes the procedure type to use such as internal for static values,
// anchor-read for using an anchor generated IDL to read values from an account,
// or custom structure for reading from a native account. Currently, only anchor
// reads are supported, but the type is a placeholder to allow internal functions
// to be run apart from anchor reads.
Type ProcedureType `json:"type"`
IDLAccount string `json:"idlAccount,omitempty"`
// OutputModifications provides modifiers to convert chain data format to custom
// output formats.
OutputModifications codec.ModifiersConfig `json:"outputModifications,omitempty"`
}

// BuilderForEncoding returns a builder for the encoding configuration. Defaults to little endian.
func BuilderForEncoding(eType EncodingType) encodings.Builder {
switch eType {
case EncodingTypeBorsh:
return binary.LittleEndian()
case EncodingTypeBincode:
return binary.BigEndian()
default:
return binary.LittleEndian()
}
}
Loading

0 comments on commit 786829f

Please sign in to comment.