From 22a3c5f9a973521e6728ad4af39d24ed9991ed15 Mon Sep 17 00:00:00 2001 From: ilija Date: Mon, 23 Dec 2024 21:51:21 +0100 Subject: [PATCH] Fix most of the interface tests --- .../chainreader/account_read_binding.go | 30 ++- pkg/solana/chainreader/bindings.go | 9 + pkg/solana/chainreader/bindings_test.go | 2 + pkg/solana/chainreader/chain_reader.go | 58 ++--- pkg/solana/chainreader/chain_reader_test.go | 12 +- pkg/solana/chainreader/lookup.go | 28 ++- pkg/solana/codec/solana.go | 4 +- .../config/testChainReader_invalid.json | 224 +++++++++++++++--- 8 files changed, 271 insertions(+), 96 deletions(-) diff --git a/pkg/solana/chainreader/account_read_binding.go b/pkg/solana/chainreader/account_read_binding.go index 71ebb131b..4f14c0aa7 100644 --- a/pkg/solana/chainreader/account_read_binding.go +++ b/pkg/solana/chainreader/account_read_binding.go @@ -7,27 +7,33 @@ import ( "github.com/gagliardetto/solana-go/rpc" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" ) // accountReadBinding provides decoding and reading Solana Account data using a defined codec. The -// `idlAccount` refers to the account name in the IDL for which the codec has a type mapping. +// `idlAccount` refers to the account onChainName in the IDL for which the codec has a type mapping. type accountReadBinding struct { - idlAccount string - codec types.RemoteCodec - key solana.PublicKey - opts *rpc.GetAccountInfoOpts + namespace, onChainName, chainAgnosticName string + codec types.RemoteCodec + key solana.PublicKey + opts *rpc.GetAccountInfoOpts } -func newAccountReadBinding(acct string, codec types.RemoteCodec, opts *rpc.GetAccountInfoOpts) *accountReadBinding { +func newAccountReadBinding(namespace, onChainName, chainAgnosticName string, opts *rpc.GetAccountInfoOpts) *accountReadBinding { return &accountReadBinding{ - idlAccount: acct, - codec: codec, - opts: opts, + namespace: namespace, + onChainName: onChainName, + chainAgnosticName: chainAgnosticName, + opts: opts, } } var _ readBinding = &accountReadBinding{} +func (b *accountReadBinding) SetCodec(codec types.RemoteCodec) { + b.codec = codec +} + func (b *accountReadBinding) SetAddress(key solana.PublicKey) { b.key = key } @@ -36,10 +42,10 @@ func (b *accountReadBinding) GetAddress() solana.PublicKey { return b.key } -func (b *accountReadBinding) CreateType(_ bool) (any, error) { - return b.codec.CreateType(b.idlAccount, false) +func (b *accountReadBinding) CreateType(forEncoding bool) (any, error) { + return b.codec.CreateType(codec.WrapItemType(forEncoding, b.namespace, b.chainAgnosticName, codec.ChainConfigTypeAccountDef), forEncoding) } func (b *accountReadBinding) Decode(ctx context.Context, bts []byte, outVal any) error { - return b.codec.Decode(ctx, bts, outVal, b.idlAccount) + return b.codec.Decode(ctx, bts, outVal, codec.WrapItemType(false, b.namespace, b.chainAgnosticName, codec.ChainConfigTypeAccountDef)) } diff --git a/pkg/solana/chainreader/bindings.go b/pkg/solana/chainreader/bindings.go index 51cc8980a..751a58fdd 100644 --- a/pkg/solana/chainreader/bindings.go +++ b/pkg/solana/chainreader/bindings.go @@ -12,6 +12,7 @@ import ( type readBinding interface { SetAddress(solana.PublicKey) GetAddress() solana.PublicKey + SetCodec(types.RemoteCodec) CreateType(bool) (any, error) Decode(context.Context, []byte, any) error } @@ -70,3 +71,11 @@ func (b namespaceBindings) Bind(binding types.BoundContract) error { return nil } + +func (b namespaceBindings) SetCodec(codec types.RemoteCodec) { + for _, nbs := range b { + for _, rb := range nbs { + rb.SetCodec(codec) + } + } +} diff --git a/pkg/solana/chainreader/bindings_test.go b/pkg/solana/chainreader/bindings_test.go index d8b510648..e8dbea89a 100644 --- a/pkg/solana/chainreader/bindings_test.go +++ b/pkg/solana/chainreader/bindings_test.go @@ -46,6 +46,8 @@ type mockBinding struct { mock.Mock } +func (_m *mockBinding) SetCodec(_ types.RemoteCodec) {} + func (_m *mockBinding) SetAddress(_ solana.PublicKey) {} func (_m *mockBinding) GetAddress() solana.PublicKey { diff --git a/pkg/solana/chainreader/chain_reader.go b/pkg/solana/chainreader/chain_reader.go index b7fda7346..706db02b0 100644 --- a/pkg/solana/chainreader/chain_reader.go +++ b/pkg/solana/chainreader/chain_reader.go @@ -60,12 +60,15 @@ func NewChainReaderService(lggr logger.Logger, dataReader MultipleAccountGetter, return nil, err } - if svcCodec, err := svc.parsed.ToCodec(); err != nil { + svcCodec, err := svc.parsed.ToCodec() + if err != nil { return nil, err - } else { - svc.codec = svcCodec - return svc, nil } + + svc.codec = svcCodec + + svc.bindings.SetCodec(svcCodec) + return svc, nil } // Name implements the services.ServiceCtx interface and returns the logger service name. @@ -122,7 +125,7 @@ func (s *SolanaChainReaderService) GetLatestValue(ctx context.Context, readIdent batch := []call{ { ContractName: vals.contract, - ReadName: vals.readName, + ReadName: vals.name.onChainName, Params: params, ReturnVal: returnVal, }, @@ -225,21 +228,25 @@ func (s *SolanaChainReaderService) CreateContractType(readIdentifier string, for return nil, fmt.Errorf("%w: no contract for read identifier", types.ErrInvalidConfig) } - return s.bindings.CreateType(values.contract, values.readName, forEncoding) + return s.bindings.CreateType(values.contract, values.name.onChainName, forEncoding) } -func (s *SolanaChainReaderService) addCodecDef(isInput bool, namespace, itemType string, readType codec.ChainConfigType, idl codec.IDL, idlDefinition interface{}, modCfg commoncodec.ModifiersConfig) error { +func (s *SolanaChainReaderService) addCodecDef(forEncoding bool, namespace, chainAgnosticName string, readType codec.ChainConfigType, idl codec.IDL, idlDefinition interface{}, modCfg commoncodec.ModifiersConfig) error { mod, err := modCfg.ToModifier(codec.DecoderHooks...) if err != nil { return err } - cEntry, err := codec.CreateCodecEntry(idlDefinition, itemType, idl, mod) + cEntry, err := codec.CreateCodecEntry(idlDefinition, chainAgnosticName, idl, mod) if err != nil { return err } - s.parsed.EncoderDefs[codec.WrapItemType(isInput, namespace, itemType, readType)] = cEntry + if forEncoding { + s.parsed.EncoderDefs[codec.WrapItemType(forEncoding, namespace, chainAgnosticName, readType)] = cEntry + } else { + s.parsed.DecoderDefs[codec.WrapItemType(forEncoding, namespace, chainAgnosticName, readType)] = cEntry + } return nil } @@ -261,18 +268,15 @@ func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainContra case config.Account: accountIDLDef, isOk := idlDef.(codec.IdlTypeDef) if !isOk { - return fmt.Errorf("unexpected type %T from IDL definition for account read: %q, with onchain name: %q, of type: %q", accountIDLDef, readName, read.ChainSpecificName, read.ReadType) + 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, accountIDLTypes{ - Account: accountIDLDef, - Types: idl.Types, - }, read); err != nil { + if err = s.addAccountRead(namespace, readName, idl, accountIDLDef, read); err != nil { return err } case config.Log: eventIDlDef, isOk := idlDef.(codec.IdlEvent) if !isOk { - return fmt.Errorf("unexpected type %T from IDL definition for log read: %q, with onchain name: %q, of type: %q", eventIDlDef, readName, read.ChainSpecificName, read.ReadType) + 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) } // TODO s.addLogRead() default: @@ -289,33 +293,31 @@ type accountIDLTypes struct { Types codec.IdlTypeDefSlice } -func (s *SolanaChainReaderService) addAccountRead(namespace string, itemType string, idl codec.IDL, idlType accountIDLTypes, readDefinition config.ReadDefinition) error { - // TODO if readDefinition.HasPDASeedInput{ - // if err := s.addCodecDef(false, namespace, itemType, codec.ChainConfigTypeAccountDef, idl,idlType, readDefinition.InputModifications,; err != nil { - // return err - // } - // } +func (s *SolanaChainReaderService) addAccountRead(namespace string, itemType string, idl codec.IDL, idlType codec.IdlTypeDef, readDefinition config.ReadDefinition) error { + if err := s.addCodecDef(true, namespace, itemType, codec.ChainConfigTypeAccountDef, idl, idlType, readDefinition.InputModifications); err != nil { + return err + } if err := s.addCodecDef(false, namespace, itemType, codec.ChainConfigTypeAccountDef, idl, idlType, readDefinition.OutputModifications); err != nil { return err } - s.lookup.addReadNameForContract(namespace, itemType) + s.lookup.addReadNameForContract(namespace, namePair{ + onChainName: itemType, + genericName: idlType.Name, + }) + s.bindings.AddReadBinding(namespace, itemType, newAccountReadBinding( + namespace, readDefinition.ChainSpecificName, + itemType, // TODO codec is not created at this point, set codec for all bindings after init - s.codec, createRPCOpts(readDefinition.RPCOpts), )) return nil } -func (s *SolanaChainReaderService) addLogRead() error { - // TODO: init codec types and modifiers, do address lookup here if needed? ... - return nil -} - // injectAddressModifier injects AddressModifier into OutputModifications. // This is necessary because AddressModifier cannot be serialized and must be applied at runtime. func injectAddressModifier(inputModifications, outputModifications commoncodec.ModifiersConfig) { diff --git a/pkg/solana/chainreader/chain_reader_test.go b/pkg/solana/chainreader/chain_reader_test.go index ca3f0f3e5..357332851 100644 --- a/pkg/solana/chainreader/chain_reader_test.go +++ b/pkg/solana/chainreader/chain_reader_test.go @@ -438,19 +438,20 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) { MethodReturningUint64: { ReadType: config.Account, ChainSpecificName: "SimpleUint64Value", - OutputModifications: codeccommon.ModifiersConfig{ &codeccommon.PropertyExtractorConfig{FieldName: "I"}, }, }, MethodReturningUint64Slice: { ChainSpecificName: "Uint64Slice", + ReadType: config.Account, OutputModifications: codeccommon.ModifiersConfig{ &codeccommon.PropertyExtractorConfig{FieldName: "Vals"}, }, }, MethodReturningSeenStruct: { ChainSpecificName: "TestStruct", + ReadType: config.Account, OutputModifications: codeccommon.ModifiersConfig{ &codeccommon.AddressBytesToStringModifierConfig{ Fields: []string{"Accountstruct.Accountstr"}, @@ -465,6 +466,7 @@ func (r *chainReaderInterfaceTester) Setup(t *testing.T) { Reads: map[string]config.ReadDefinition{ MethodReturningUint64: { ChainSpecificName: "SimpleUint64Value", + ReadType: config.Account, OutputModifications: codeccommon.ModifiersConfig{ &codeccommon.PropertyExtractorConfig{FieldName: "I"}, }, @@ -776,19 +778,15 @@ func fullTestIDL(t *testing.T) string { // Combine all of the type definitions into one comma-separated string. allTypes := strings.Join([]string{ - midLevelDynamicStructIDL, - midLevelStaticStructIDL, - innerDynamicStructIDL, - innerStaticStructIDL, - accountStructIDL, + testStructIDL, uint64BaseTypeIDL, uint64SliceBaseTypeIDL, }, ",") return fmt.Sprintf( baseIDL, - testStructIDL, allTypes, + strings.Join([]string{midLevelDynamicStructIDL, midLevelStaticStructIDL, innerDynamicStructIDL, innerStaticStructIDL, accountStructIDL}, ","), ) } diff --git a/pkg/solana/chainreader/lookup.go b/pkg/solana/chainreader/lookup.go index b4295d20f..5c7d06241 100644 --- a/pkg/solana/chainreader/lookup.go +++ b/pkg/solana/chainreader/lookup.go @@ -9,7 +9,11 @@ import ( type readValues struct { address string contract string - readName string + name namePair +} + +type namePair struct { + onChainName, genericName string } // lookup provides basic utilities for mapping a complete readIdentifier to @@ -17,45 +21,45 @@ type readValues struct { type lookup struct { mu sync.RWMutex // contractReadNames maps a contract name to all available readNames (method, log, event, etc.) - contractReadNames map[string][]string + contractReadNames map[string][]namePair // readIdentifiers maps from a complete readIdentifier string to finite read data - // a readIdentifier is a combination of address, contract, and readName as a concatenated string + // a readIdentifier is a combination of address, contract, and namePair as a concatenated string readIdentifiers map[string]readValues } func newLookup() *lookup { return &lookup{ - contractReadNames: make(map[string][]string), + contractReadNames: make(map[string][]namePair), readIdentifiers: make(map[string]readValues), } } -func (l *lookup) addReadNameForContract(contract, readName string) { +func (l *lookup) addReadNameForContract(contract string, name namePair) { l.mu.Lock() defer l.mu.Unlock() readNames, exists := l.contractReadNames[contract] if !exists { - readNames = []string{} + readNames = []namePair{} } - l.contractReadNames[contract] = append(readNames, readName) + l.contractReadNames[contract] = append(readNames, name) } func (l *lookup) bindAddressForContract(contract, address string) { l.mu.Lock() defer l.mu.Unlock() - for _, readName := range l.contractReadNames[contract] { + for _, namePair := range l.contractReadNames[contract] { readIdentifier := types.BoundContract{ Address: address, Name: contract, - }.ReadIdentifier(readName) + }.ReadIdentifier(namePair.onChainName) l.readIdentifiers[readIdentifier] = readValues{ address: address, contract: contract, - readName: readName, + name: namePair, } } } @@ -64,11 +68,11 @@ func (l *lookup) unbindAddressForContract(contract, address string) { l.mu.Lock() defer l.mu.Unlock() - for _, readName := range l.contractReadNames[contract] { + for _, namePair := range l.contractReadNames[contract] { readIdentifier := types.BoundContract{ Address: address, Name: contract, - }.ReadIdentifier(readName) + }.ReadIdentifier(namePair.onChainName) delete(l.readIdentifiers, readIdentifier) } diff --git a/pkg/solana/codec/solana.go b/pkg/solana/codec/solana.go index 3ecada671..2a730ffa2 100644 --- a/pkg/solana/codec/solana.go +++ b/pkg/solana/codec/solana.go @@ -138,8 +138,8 @@ func FindDefinitionFromIDL(cfgType ChainConfigType, onChainName string, idl IDL) return nil, fmt.Errorf("unknown type: %q", cfgType) } -func WrapItemType(isInput bool, contractName, itemType string, readType ChainConfigType) string { - if isInput { +func WrapItemType(forEncoding bool, contractName, itemType string, readType ChainConfigType) string { + if forEncoding { return fmt.Sprintf("input.%s.%s", contractName, itemType) } diff --git a/pkg/solana/config/testChainReader_invalid.json b/pkg/solana/config/testChainReader_invalid.json index c7313f8d5..0572ee0b4 100644 --- a/pkg/solana/config/testChainReader_invalid.json +++ b/pkg/solana/config/testChainReader_invalid.json @@ -1,38 +1,192 @@ { - "namespaces": { - "example_namespace": { - "anchorIDL": { - "name": "example_invalid_idl_name", - "version": "1.0.0" - }, - "reads": { - "someRead": { - "chainSpecificName": "some_chain_specific_name", - "readType": 0, - "inputModifications": [ - { - "path": "someInputPath", - "action": "replace", - "value": "newValue" - } - ], - "outputModifications": [ - { - "path": "someOutputPath", - "action": "extract", - "value": "desiredField" - } - ], - "rpcOpts": { - "encoding": "base64", - "commitment": "processed", - "dataSlice": { - "offset": 0, - "length": 32 - } - } - } + "version": "0.1.0", + "name": "some_test_idl", + "accounts": [ + { + "name": "TestStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "field", + "type": { + "option": "i32" + } + }, + { + "name": "differentField", + "type": "string" + }, + { + "name": "bigField", + "type": "i128" + }, + { + "name": "nestedDynamicStruct", + "type": { + "defined": "MidLevelDynamicStruct" + } + }, + { + "name": "nestedStaticStruct", + "type": { + "defined": "MidLevelStaticStruct" + } + }, + { + "name": "oracleID", + "type": "u8" + }, + { + "name": "oracleIDs", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "accountstruct", + "type": { + "defined": "accountstruct" + } + }, + { + "name": "accounts", + "type": { + "vec": "bytes" + } + } + ] + } + } + ], + "types": [ + { + "name": "MidLevelDynamicStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "fixedBytes", + "type": { + "array": [ + "u8", + 2 + ] + } + }, + { + "name": "inner", + "type": { + "defined": "InnerDynamicTestStruct" + } + } + ] + } + }, + { + "name": "MidLevelStaticStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "fixedBytes", + "type": { + "array": [ + "u8", + 2 + ] + } + }, + { + "name": "inner", + "type": { + "defined": "InnerStaticTestStruct" + } + } + ] + } + }, + { + "name": "InnerDynamicTestStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "i", + "type": "i32" + }, + { + "name": "s", + "type": "string" + } + ] + } + }, + { + "name": "InnerStaticTestStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "i", + "type": "i32" + }, + { + "name": "a", + "type": "bytes" + } + ] + } + }, + { + "name": "accountstruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "account", + "type": "bytes" + }, + { + "name": "accountstr", + "type": { + "array": [ + "u8", + 32 + ] + } + } + ] + } + }, + { + "name": "SimpleUint64Value", + "type": { + "kind": "struct", + "fields": [ + { + "name": "i", + "type": "u64" + } + ] + } + }, + { + "name": "Uint64Slice", + "type": { + "kind": "struct", + "fields": [ + { + "name": "vals", + "type": { + "vec": "u64" + } + } + ] } } - } -} + ] +} \ No newline at end of file