-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable ChainReader to read PDA account state #1003
Changes from 5 commits
228bf30
723c222
5ca4719
3584a6f
18975c9
35b2596
6127ff8
22afa9f
4a9b5ee
d1cb908
2f82859
483b336
88b4d92
a241428
67a24e1
75e069c
23ee58d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -249,6 +249,23 @@ func (s *SolanaChainReaderService) addCodecDef(forEncoding bool, namespace, gene | |
return nil | ||
} | ||
|
||
func (s *SolanaChainReaderService) addSeedsCodecDef(namespace, genericName string, seedDefs []codec.SeedDefinition, modCfg commoncodec.ModifiersConfig) error { | ||
mod, err := modCfg.ToModifier(codec.DecoderHooks...) | ||
if err != nil { | ||
return err | ||
} | ||
// Append seed suffix to differentiate the entry for encoding seeds from the account read entry | ||
seedEntry, err := codec.NewSeedEntry(genericName, mod, seedDefs) | ||
if err != nil { | ||
return fmt.Errorf("failed to create a codec entry for seed definitions %v: %w", seedDefs, err) | ||
} | ||
|
||
// Seed codec entry is only used for encoding. Read type is not used by WrapItemType for encoding string. | ||
s.parsed.EncoderDefs[codec.WrapItemType(true, namespace, genericName, "")] = seedEntry | ||
|
||
return nil | ||
} | ||
|
||
func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainContractReader) error { | ||
for namespace, nameSpaceDef := range namespaces { | ||
for genericName, read := range nameSpaceDef.Reads { | ||
|
@@ -284,25 +301,26 @@ func (s *SolanaChainReaderService) init(namespaces map[string]config.ChainContra | |
} | ||
|
||
func (s *SolanaChainReaderService) addAccountRead(namespace string, genericName string, idl codec.IDL, idlType codec.IdlTypeDef, readDefinition config.ReadDefinition) error { | ||
inputAccountIDLDef := codec.NilIdlTypeDefTy | ||
// TODO: | ||
// if hasPDA{ | ||
// inputAccountIDLDef = pdaType | ||
// } | ||
if err := s.addCodecDef(true, namespace, genericName, codec.ChainConfigTypeAccountDef, idl, inputAccountIDLDef, readDefinition.InputModifications); err != nil { | ||
return err | ||
} | ||
|
||
if err := s.addCodecDef(false, namespace, genericName, codec.ChainConfigTypeAccountDef, idl, idlType, readDefinition.OutputModifications); err != nil { | ||
return err | ||
} | ||
|
||
s.lookup.addReadNameForContract(namespace, genericName) | ||
|
||
s.bindings.AddReadBinding(namespace, genericName, newAccountReadBinding( | ||
namespace, | ||
genericName, | ||
)) | ||
var reader readBinding | ||
// Create PDA read binding if PDA prefix or seeds configs are populated | ||
if len(readDefinition.PDAPrefix) > 0 || len(readDefinition.Seeds) > 0 { | ||
if err := s.addSeedsCodecDef(namespace, genericName, readDefinition.Seeds, readDefinition.InputModifications); err != nil { | ||
return fmt.Errorf("failed to add codec entry for seed configs: %w", err) | ||
} | ||
reader = newPdaReadBinding(namespace, genericName, readDefinition.PDAPrefix) | ||
} else { | ||
if err := s.addCodecDef(true, namespace, genericName, codec.ChainConfigTypeAccountDef, idl, codec.NilIdlTypeDefTy, readDefinition.InputModifications); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this do? There are no inputs for non PDA account reads There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is unused for normal account reads. For encoding PDAs, we only really need the new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my bad actually, I wrote this code lol There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought I left it as a placeholder for PDA to be input, not sure why it throws errors |
||
return err | ||
} | ||
reader = newAccountReadBinding(namespace, genericName) | ||
} | ||
s.bindings.AddReadBinding(namespace, genericName, reader) | ||
|
||
return nil | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package chainreader | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/gagliardetto/solana-go" | ||
|
||
"github.com/smartcontractkit/chainlink-common/pkg/types" | ||
|
||
"github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" | ||
) | ||
|
||
// pdaReadBinding provides calculating PDA addresses with the provided seeds and reading decoded PDA Account data using a defined codec | ||
type pdaReadBinding struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think we could merge this into account_read_binding.go? They essentially do the same thing, since we’re reading an account in both cases. The only difference is that we need to calculate the PDA address if parameters are provided. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm good point. I think I could. I'll give this a shot There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I merged the two in the latest commit |
||
namespace string | ||
genericName string | ||
codec types.RemoteCodec | ||
programID solana.PublicKey | ||
prefix string | ||
} | ||
|
||
func newPdaReadBinding(namespace, genericName string, prefix string) *pdaReadBinding { | ||
return &pdaReadBinding{ | ||
namespace: namespace, | ||
genericName: genericName, | ||
prefix: prefix, | ||
} | ||
} | ||
|
||
var _ readBinding = &pdaReadBinding{} | ||
|
||
func (b *pdaReadBinding) SetCodec(codec types.RemoteCodec) { | ||
b.codec = codec | ||
} | ||
|
||
func (b *pdaReadBinding) SetAddress(programID solana.PublicKey) { | ||
b.programID = programID | ||
} | ||
|
||
func (b *pdaReadBinding) GetAddress(ctx context.Context, params any) (solana.PublicKey, error) { | ||
seedBytes, err := b.buildSeedsSlice(ctx, params) | ||
if err != nil { | ||
return solana.PublicKey{}, fmt.Errorf("failed build seeds list for PDA calculation: %w", err) | ||
} | ||
key, _, err := solana.FindProgramAddress(seedBytes, b.programID) | ||
if err != nil { | ||
return solana.PublicKey{}, fmt.Errorf("failed find program address for PDA: %w", err) | ||
} | ||
return key, nil | ||
} | ||
|
||
func (b *pdaReadBinding) CreateType(forEncoding bool) (any, error) { | ||
return b.codec.CreateType(codec.WrapItemType(forEncoding, b.namespace, b.genericName, codec.ChainConfigTypeAccountDef), forEncoding) | ||
} | ||
|
||
func (b *pdaReadBinding) Decode(ctx context.Context, bts []byte, outVal any) error { | ||
return b.codec.Decode(ctx, bts, outVal, codec.WrapItemType(false, b.namespace, b.genericName, codec.ChainConfigTypeAccountDef)) | ||
} | ||
|
||
func (b *pdaReadBinding) buildSeedsSlice(ctx context.Context, params any) ([][]byte, error) { | ||
flattenedSeeds := make([]byte, 0, solana.MaxSeeds*solana.MaxSeedLength) | ||
// Append the static prefix string first | ||
flattenedSeeds = append(flattenedSeeds, []byte(b.prefix)...) | ||
// Encode the seeds provided in the params | ||
encodedParamSeeds, err := b.codec.Encode(ctx, params, codec.WrapItemType(true, b.namespace, b.genericName, "")) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to encode params into bytes for PDA seeds: %w", err) | ||
} | ||
// Append the encoded seeds | ||
flattenedSeeds = append(flattenedSeeds, encodedParamSeeds...) | ||
|
||
if len(flattenedSeeds) > solana.MaxSeeds*solana.MaxSeedLength { | ||
return nil, fmt.Errorf("seeds exceed the maximum allowed length") | ||
} | ||
|
||
// Splitting the seeds since they are expected to be provided separately to FindProgramAddress | ||
// Arbitrarily separating the seeds at max seed length would still yield the same PDA since | ||
// FindProgramAddress appends the seed bytes together under the hood | ||
numSeeds := len(flattenedSeeds) / solana.MaxSeedLength | ||
if len(flattenedSeeds)%solana.MaxSeedLength != 0 { | ||
numSeeds++ | ||
} | ||
seedByteArray := make([][]byte, 0, numSeeds) | ||
for i := 0; i < numSeeds; i++ { | ||
startIdx := i * solana.MaxSeedLength | ||
endIdx := startIdx + solana.MaxSeedLength | ||
if endIdx > len(flattenedSeeds) { | ||
endIdx = len(flattenedSeeds) | ||
} | ||
seedByteArray = append(seedByteArray, flattenedSeeds[startIdx:endIdx]) | ||
} | ||
return seedByteArray, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can go away in favour of just using
addCodecDef()
. You just need to define seeds as a new IDL type and handle it like events, args and accounts are handled hereCreateCodecEntry()
which is a func used byaddCodecDef()
.