From 2f828596267570bc6c96bff60950e532f41ecb82 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Thu, 16 Jan 2025 09:53:49 -0600 Subject: [PATCH] Added new PDA account read unit tests --- pkg/solana/chainreader/chain_reader_test.go | 188 ++++++++++++++------ pkg/solana/codec/anchoridl.go | 6 +- pkg/solana/codec/codec_entry.go | 2 +- 3 files changed, 141 insertions(+), 55 deletions(-) diff --git a/pkg/solana/chainreader/chain_reader_test.go b/pkg/solana/chainreader/chain_reader_test.go index d87573b72..019ef3e09 100644 --- a/pkg/solana/chainreader/chain_reader_test.go +++ b/pkg/solana/chainreader/chain_reader_test.go @@ -271,6 +271,7 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { t.Run("PDA account read success", func(t *testing.T) { t.Parallel() + programID := solana.NewWallet().PublicKey() pubKey := solana.NewWallet().PublicKey() uint64Seed := uint64(5) prefixString := "Prefix" @@ -278,69 +279,148 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { readDef := config.ReadDefinition{ ChainSpecificName: testutils.TestStructWithNestedStruct, ReadType: config.Account, - PDADefiniton: codec.PDATypeDef{ - Prefix: prefixString, - Seeds: []codec.PDASeed{ - { - Name: "PubKey", - Type: codec.IdlTypePublicKey, + OutputModifications: codeccommon.ModifiersConfig{ + &codeccommon.RenameModifierConfig{Fields: map[string]string{"Value": "V"}}, + }, + } + + testCases := []struct { + name string + pdaDefinition codec.PDATypeDef + inputModifier codeccommon.ModifiersConfig + expected solana.PublicKey + params map[string]any + }{ + { + name: "happy path", + pdaDefinition: codec.PDATypeDef{ + Prefix: prefixString, + Seeds: []codec.PDASeed{ + { + Name: "PubKey", + Type: codec.IdlTypePublicKey, + }, + { + Name: "Uint64Seed", + Type: codec.IdlTypeU64, + }, }, - { - Name: "Uint64Seed", - Type: codec.IdlTypeU64, + }, + expected: mustFindProgramAddress(t, programID, [][]byte{[]byte(prefixString), pubKey.Bytes(), go_binary.LittleEndian.AppendUint64([]byte{}, uint64Seed)}), + params: map[string]any{ + "PubKey": pubKey, + "Uint64Seed": uint64Seed, + }, + }, + { + name: "with modifier and random field", + pdaDefinition: codec.PDATypeDef{ + Prefix: prefixString, + Seeds: []codec.PDASeed{ + { + Name: "PubKey", + Type: codec.IdlTypePublicKey, + }, + { + Name: "Uint64Seed", + Type: codec.IdlTypeU64, + }, }, }, + inputModifier: codeccommon.ModifiersConfig{ + &codeccommon.RenameModifierConfig{Fields: map[string]string{"PubKey": "PublicKey"}}, + }, + expected: mustFindProgramAddress(t, programID, [][]byte{[]byte(prefixString), pubKey.Bytes(), go_binary.LittleEndian.AppendUint64([]byte{}, uint64Seed)}), + params: map[string]any{ + "PublicKey": pubKey, + "randomField": "randomValue", // unused field should be ignored by the codec + "Uint64Seed": uint64Seed, + }, }, - InputModifications: codeccommon.ModifiersConfig{ - &codeccommon.RenameModifierConfig{Fields: map[string]string{"PubKey": "PublicKey"}}, + { + name: "only prefix", + pdaDefinition: codec.PDATypeDef{ + Prefix: prefixString, + }, + expected: mustFindProgramAddress(t, programID, [][]byte{[]byte(prefixString)}), + params: nil, }, - OutputModifications: codeccommon.ModifiersConfig{ - &codeccommon.RenameModifierConfig{Fields: map[string]string{"Value": "V"}}, + { + name: "no prefix", + pdaDefinition: codec.PDATypeDef{ + Prefix: "", + Seeds: []codec.PDASeed{ + { + Name: "PubKey", + Type: codec.IdlTypePublicKey, + }, + { + Name: "Uint64Seed", + Type: codec.IdlTypeU64, + }, + }, + }, + expected: mustFindProgramAddress(t, programID, [][]byte{pubKey.Bytes(), go_binary.LittleEndian.AppendUint64([]byte{}, uint64Seed)}), + params: map[string]any{ + "PubKey": pubKey, + "Uint64Seed": uint64Seed, + }, + }, + { + name: "public key seed provided as bytes", + pdaDefinition: codec.PDATypeDef{ + Prefix: prefixString, + Seeds: []codec.PDASeed{ + { + Name: "PubKey", + Type: codec.IdlTypePublicKey, + }, + }, + }, + expected: mustFindProgramAddress(t, programID, [][]byte{[]byte(prefixString), pubKey.Bytes()}), + params: map[string]any{ + "PubKey": pubKey.Bytes(), + }, }, - } - testCodec, conf := newTestConfAndCodecWithInjectibleReadDef(t, PDAAccount, readDef) - encoded, err := testCodec.Encode(ctx, expected, testutils.TestStructWithNestedStruct) - require.NoError(t, err) - - client := new(mockedRPCClient) - svc, err := chainreader.NewChainReaderService(logger.Test(t), client, conf) - require.NoError(t, err) - require.NotNil(t, svc) - require.NoError(t, svc.Start(ctx)) - - t.Cleanup(func() { - require.NoError(t, svc.Close()) - }) - - programID := solana.NewWallet().PublicKey() - - binding := types.BoundContract{ - Name: Namespace, - Address: programID.String(), // Set the program ID used to calculate the PDA } - pdaAccount, _, err := solana.FindProgramAddress([][]byte{ - []byte(prefixString), - pubKey.Bytes(), - go_binary.LittleEndian.AppendUint64([]byte{}, uint64Seed), - }, programID) - require.NoError(t, err) + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + testReadDef := readDef + testReadDef.PDADefiniton = testCase.pdaDefinition + testReadDef.InputModifications = testCase.inputModifier + testCodec, conf := newTestConfAndCodecWithInjectibleReadDef(t, PDAAccount, testReadDef) + encoded, err := testCodec.Encode(ctx, expected, testutils.TestStructWithNestedStruct) + require.NoError(t, err) + + client := new(mockedRPCClient) + svc, err := chainreader.NewChainReaderService(logger.Test(t), client, conf) + require.NoError(t, err) + require.NotNil(t, svc) + require.NoError(t, svc.Start(ctx)) + + t.Cleanup(func() { + require.NoError(t, svc.Close()) + }) + + binding := types.BoundContract{ + Name: Namespace, + Address: programID.String(), // Set the program ID used to calculate the PDA + } - client.SetForAddress(pdaAccount, encoded, nil, 0) + client.SetForAddress(testCase.expected, encoded, nil, 0) - require.NoError(t, svc.Bind(ctx, []types.BoundContract{binding})) + require.NoError(t, svc.Bind(ctx, []types.BoundContract{binding})) - var result modifiedStructWithNestedStruct - require.NoError(t, svc.GetLatestValue(ctx, binding.ReadIdentifier(PDAAccount), primitives.Unconfirmed, map[string]any{ - "PublicKey": pubKey, - "randomField": "randomValue", // unused field should be ignored by the codec - "Uint64Seed": uint64Seed, - }, &result)) + var result modifiedStructWithNestedStruct + require.NoError(t, svc.GetLatestValue(ctx, binding.ReadIdentifier(PDAAccount), primitives.Unconfirmed, testCase.params, &result)) - assert.Equal(t, expected.InnerStruct, result.InnerStruct) - assert.Equal(t, expected.Value, result.V) - assert.Equal(t, expected.TimeVal, result.TimeVal) - assert.Equal(t, expected.DurationVal, result.DurationVal) + assert.Equal(t, expected.InnerStruct, result.InnerStruct) + assert.Equal(t, expected.Value, result.V) + assert.Equal(t, expected.TimeVal, result.TimeVal) + assert.Equal(t, expected.DurationVal, result.DurationVal) + }) + } }) t.Run("PDA account read errors if missing param", func(t *testing.T) { @@ -1061,3 +1141,9 @@ func mustUnmarshalIDL(t *testing.T, rawIDL string) codec.IDL { return idl } + +func mustFindProgramAddress(t *testing.T, programID solana.PublicKey, seeds [][]byte) solana.PublicKey { + key, _, err := solana.FindProgramAddress(seeds, programID) + require.NoError(t, err) + return key +} diff --git a/pkg/solana/codec/anchoridl.go b/pkg/solana/codec/anchoridl.go index 74ea747ae..0ea1322ad 100644 --- a/pkg/solana/codec/anchoridl.go +++ b/pkg/solana/codec/anchoridl.go @@ -143,12 +143,12 @@ type IdlField struct { // PDA is a struct that does not correlate to an official IDL type // It is needed to encode seeds to calculate the address for PDA account reads type PDATypeDef struct { - Prefix string `json:"prefix,omitempty"` - Seeds []PDASeed `json:"seeds,omitempty"` + Prefix string `json:"prefix,omitempty"` + Seeds []PDASeed `json:"seeds,omitempty"` } type PDASeed struct { - Name string `json:"name"` + Name string `json:"name"` Type IdlTypeAsString `json:"type"` } diff --git a/pkg/solana/codec/codec_entry.go b/pkg/solana/codec/codec_entry.go index f9f817a0b..d3b459d57 100644 --- a/pkg/solana/codec/codec_entry.go +++ b/pkg/solana/codec/codec_entry.go @@ -231,4 +231,4 @@ func pdaSeedsToIdlField(seeds []PDASeed) []IdlField { }) } return idlFields -} \ No newline at end of file +}