Skip to content

Commit

Permalink
Improve Contract Reader cfg handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ilija42 committed Dec 24, 2024
1 parent 382f401 commit c2d68d2
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 250 deletions.
16 changes: 5 additions & 11 deletions pkg/solana/chainreader/chain_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package chainreader

import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
Expand Down Expand Up @@ -251,15 +250,10 @@ func (s *SolanaChainReaderService) addCodecDef(forEncoding bool, namespace, chai
}

func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainContractReader) error {
for namespace, namespaceReads := range namespaces {
for readName, read := range namespaceReads.Reads {
var idl codec.IDL
if err := json.Unmarshal([]byte(namespaceReads.IDL), &idl); err != nil {
return err
}

for namespace, nameSpaceDef := range namespaces {
for readName, read := range nameSpaceDef.Reads {
injectAddressModifier(read.InputModifications, read.OutputModifications)
idlDef, err := codec.FindDefinitionFromIDL(codec.ChainConfigTypeAccountDef, read.ChainSpecificName, idl)
idlDef, err := codec.FindDefinitionFromIDL(codec.ChainConfigTypeAccountDef, read.ChainSpecificName, nameSpaceDef.IDL)
if err != nil {
return err
}
Expand All @@ -270,10 +264,10 @@ func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainContra
if !isOk {
return fmt.Errorf("unexpected type %T from IDL definition for account read: %q, with onchain onChainName: %q, of type: %q", accountIDLDef, readName, read.ChainSpecificName, read.ReadType)
}
if err = s.addAccountRead(namespace, readName, idl, accountIDLDef, read); err != nil {
if err = s.addAccountRead(namespace, readName, nameSpaceDef.IDL, accountIDLDef, read); err != nil {
return err
}
case config.Log:
case config.Event:
eventIDlDef, isOk := idlDef.(codec.IdlEvent)
if !isOk {
return fmt.Errorf("unexpected type %T from IDL definition for log read: %q, with onchain onChainName: %q, of type: %q", eventIDlDef, readName, read.ChainSpecificName, read.ReadType)
Expand Down
24 changes: 14 additions & 10 deletions pkg/solana/chainreader/chain_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ func newTestConfAndCodec(t *testing.T) (types.RemoteCodec, config.ContractReader
conf := config.ContractReader{
Namespaces: map[string]config.ChainContractReader{
Namespace: {
IDL: rawIDL,
IDL: mustUnmarshalIDL(t, rawIDL),
Reads: map[string]config.ReadDefinition{
NamedMethod: {
ChainSpecificName: testutils.TestStructWithNestedStruct,
Expand All @@ -312,6 +312,16 @@ func newTestConfAndCodec(t *testing.T) (types.RemoteCodec, config.ContractReader
return testCodec, conf
}

func mustUnmarshalIDL(t *testing.T, rawIDL string) codec.IDL {
var idl codec.IDL
if err := json.Unmarshal([]byte(rawIDL), &idl); err != nil {
t.Logf("failed to unmarshal test IDL: %s", err.Error())
t.FailNow()
}

return idl
}

type modifiedStructWithNestedStruct struct {
V uint8
InnerStruct testutils.ObjectRef1
Expand Down Expand Up @@ -421,7 +431,7 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) {
r.conf = config.ContractReader{
Namespaces: map[string]config.ChainContractReader{
AnyContractName: {
IDL: fullTestIDL(t),
IDL: mustUnmarshalIDL(t, fullTestIDL(t)),
Reads: map[string]config.ReadDefinition{
MethodTakingLatestParamsReturningTestStruct: {
ReadType: config.Account,
Expand Down Expand Up @@ -462,7 +472,7 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) {
},
},
AnySecondContractName: {
IDL: fmt.Sprintf(baseIDL, uint64BaseTypeIDL, ""),
IDL: mustUnmarshalIDL(t, fmt.Sprintf(baseIDL, uint64BaseTypeIDL, "")),
Reads: map[string]config.ReadDefinition{
MethodReturningUint64: {
ChainSpecificName: "SimpleUint64Value",
Expand Down Expand Up @@ -748,13 +758,7 @@ func (r *chainReaderInterfaceTester) MaxWaitTimeForEvents() time.Duration {
func makeTestCodec(t *testing.T, rawIDL string) types.RemoteCodec {
t.Helper()

var idl codec.IDL
if err := json.Unmarshal([]byte(rawIDL), &idl); err != nil {
t.Logf("failed to unmarshal test IDL: %s", err.Error())
t.FailNow()
}

testCodec, err := codec.NewIDLAccountCodec(idl, binary.LittleEndian())
testCodec, err := codec.NewIDLAccountCodec(mustUnmarshalIDL(t, rawIDL), binary.LittleEndian())
if err != nil {
t.Logf("failed to create new codec from test IDL: %s", err.Error())
t.FailNow()
Expand Down
55 changes: 44 additions & 11 deletions pkg/solana/config/chain_reader.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package config

import (
"encoding/json"
"fmt"

"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"

commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec"
commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/codec"
)

type ContractReader struct {
Namespaces map[string]ChainContractReader `json:"namespaces" toml:"namespaces"`
Namespaces map[string]ChainContractReader `json:"namespaces"`
}

type ChainContractReader struct {
IDL string `json:"anchorIDL" toml:"anchorIDL"`
codec.IDL `json:"anchorIDL"`
// Reads key is the off-chain name for this read.
Reads map[string]ReadDefinition
Reads map[string]ReadDefinition `json:"reads"`
// TODO ContractPollingFilter same as EVM?
}

Expand All @@ -26,26 +29,21 @@ type ReadDefinition struct {
InputModifications commoncodec.ModifiersConfig `json:"inputModifications,omitempty"`
OutputModifications commoncodec.ModifiersConfig `json:"outputModifications,omitempty"`
RPCOpts *RPCOpts `json:"rpcOpts,omitempty"`

// TODO EventDefinitions *EventDefinitions similar to EVM?
// TODO Lookup details for PDAs and lookup tables to be merged with CW
//LookupTables *LookupTables
//Accounts *[]Lookup
}

type ReadType int

const (
Account ReadType = iota
Log
Event
)

func (r ReadType) String() string {
switch r {
case Account:
return "Account"
case Log:
return "Log"
case Event:
return "Event"
default:
return fmt.Sprintf("Unknown(%d)", r)
}
Expand All @@ -56,3 +54,38 @@ type RPCOpts struct {
Commitment *rpc.CommitmentType `json:"commitment,omitempty"`
DataSlice *rpc.DataSlice `json:"dataSlice,omitempty"`
}

func (c *ChainContractReader) UnmarshalJSON(bytes []byte) error {
rawJson := make(map[string]json.RawMessage)
if err := json.Unmarshal(bytes, &rawJson); err != nil {
return err
}

idlBytes := rawJson["anchorIDL"]
var rawString string
if err := json.Unmarshal(idlBytes, &rawString); err == nil {
if err = json.Unmarshal([]byte(rawString), &c.IDL); err != nil {
return fmt.Errorf("failed to parse anchorIDL string as IDL struct: %w", err)
}
return nil
}

// If we didn't get a string, attempt to parse directly as an IDL object
if err := json.Unmarshal(idlBytes, &c.IDL); err != nil {
return fmt.Errorf("anchorIDL field is neither a valid JSON string nor a valid IDL object: %w", err)
}

if len(c.Accounts) == 0 && len(c.Events) == 0 {
return fmt.Errorf("namespace idl must have at least one account or event: %w", commontypes.ErrInvalidConfig)
}

if err := json.Unmarshal(rawJson["reads"], &c.Reads); err != nil {
return err
}

if c.Reads == nil || len(c.Reads) == 0 {
return fmt.Errorf("namespace must have at least one read: %w", commontypes.ErrInvalidConfig)
}

return nil
}
23 changes: 22 additions & 1 deletion pkg/solana/config/chain_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

codeccommon "github.com/smartcontractkit/chainlink-common/pkg/codec"
"github.com/smartcontractkit/chainlink-common/pkg/types"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/codec"

"github.com/smartcontractkit/chainlink-solana/pkg/solana/codec/testutils"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/config"
Expand All @@ -20,18 +21,28 @@ import (
//go:embed testChainReader_valid.json
var validJSON string

//go:embed testChainReader_valid_with_IDL_as_string.json
var validJSONWithIDLAsString string

//go:embed testChainReader_invalid.json
var invalidJSON string

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

t.Run("valid unmarshal", func(t *testing.T) {
t.Run("valid unmarshal with idl as struct", func(t *testing.T) {
t.Parallel()

var result config.ContractReader
require.NoError(t, json.Unmarshal([]byte(validJSON), &result))
assert.Equal(t, validChainReaderConfig, result)

})

t.Run("valid unmarshal with idl as string", func(t *testing.T) {
var result config.ContractReader
require.NoError(t, json.Unmarshal([]byte(validJSONWithIDLAsString), &result))
assert.Equal(t, validChainReaderConfig, result)
})

t.Run("invalid unmarshal", func(t *testing.T) {
Expand Down Expand Up @@ -62,9 +73,18 @@ var (
length = uint64(10)
)

var nilIDL = codec.IDL{
Version: "0.1.0",
Name: "myProgram",
Accounts: codec.IdlTypeDefSlice{
{Name: "NilType", Type: codec.IdlTypeDefTy{Kind: codec.IdlTypeDefTyKindStruct, Fields: &codec.IdlTypeDefStruct{}}},
},
}

var validChainReaderConfig = config.ContractReader{
Namespaces: map[string]config.ChainContractReader{
"Contract": {
IDL: nilIDL,
Reads: map[string]config.ReadDefinition{
"Method": {
ChainSpecificName: testutils.TestStructWithNestedStruct,
Expand All @@ -86,6 +106,7 @@ var validChainReaderConfig = config.ContractReader{
},
},
"OtherContract": {
IDL: nilIDL,
Reads: map[string]config.ReadDefinition{
"Method": {
ChainSpecificName: testutils.TestStructWithNestedStruct,
Expand Down
Loading

0 comments on commit c2d68d2

Please sign in to comment.