Skip to content

Commit

Permalink
Added new PDA account read unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
amit-momin committed Jan 16, 2025
1 parent d1cb908 commit 2f82859
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 55 deletions.
188 changes: 137 additions & 51 deletions pkg/solana/chainreader/chain_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,76 +271,156 @@ 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"

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) {
Expand Down Expand Up @@ -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
}
6 changes: 3 additions & 3 deletions pkg/solana/codec/anchoridl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/solana/codec/codec_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,4 @@ func pdaSeedsToIdlField(seeds []PDASeed) []IdlField {
})
}
return idlFields
}
}

0 comments on commit 2f82859

Please sign in to comment.