-
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 2 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 |
---|---|---|
@@ -0,0 +1,97 @@ | ||
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" | ||
"github.com/smartcontractkit/chainlink-solana/pkg/solana/config" | ||
"github.com/smartcontractkit/chainlink-solana/pkg/solana/utils" | ||
) | ||
|
||
// 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 | ||
seeds []config.Seed | ||
} | ||
|
||
func newPdaReadBinding(namespace, genericName string, seeds []config.Seed) *pdaReadBinding { | ||
return &pdaReadBinding{ | ||
namespace: namespace, | ||
genericName: genericName, | ||
seeds: seeds, | ||
} | ||
} | ||
|
||
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(params any) (solana.PublicKey, error) { | ||
seedBytes, err := b.buildSeedsSlice(params) | ||
if err != nil { | ||
return solana.PublicKey{}, fmt.Errorf("failed build seeds list for PDA generation: %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(params any) ([][]byte, error) { | ||
if b.seeds == nil { | ||
return [][]byte{}, nil | ||
} | ||
|
||
seedByteArray := make([][]byte, 0, len(b.seeds)) | ||
for _, seed := range b.seeds { | ||
silaslenihan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if seed.Value != nil && len(seed.Location) > 0 { | ||
return nil, fmt.Errorf("seed cannot have both Value (%v) and Location (%s) defined", seed.Value, seed.Location) | ||
} | ||
if seed.Value != nil { | ||
byteArray := utils.ConvertAnyToPDASeed(seed.Value) | ||
if byteArray == nil { | ||
return nil, fmt.Errorf("failed to convert seed %v to byte array", seed.Value) | ||
} | ||
if len(byteArray) > solana.MaxSeedLength { | ||
return nil, fmt.Errorf("seed length %d exceeds the max allowed length %d", len(byteArray), solana.MaxSeedLength) | ||
} | ||
seedByteArray = append(seedByteArray, utils.ConvertAnyToPDASeed(seed.Value)) | ||
continue | ||
} | ||
if len(seed.Location) > 0 { | ||
byteArrays, err := utils.GetValuesAtLocation(params, seed.Location) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to find seed at location %s in params: %w", seed.Location, err) | ||
} | ||
if len(byteArrays) != 1 { | ||
return nil, fmt.Errorf("expected 1 seed. found %d seeds at location %s", len(byteArrays), seed.Location) | ||
} | ||
seedByteArray = append(seedByteArray, byteArrays[0]) | ||
continue | ||
} | ||
} | ||
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.
Would an empty seeds list just be a normal account read?
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
FindProgramAddress
still outputs a different programID even with empty seeds since it's still applying the bump seed. I think every PDA in the on-chain code uses some sort of seed though. So guess it wouldn't hurt to also validate empty seeds for our current use case but I wanted to keep this open in case something changes in the future.