From f73928c2124cffc464e5c8b6dabc1e4cb1f29a19 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Fri, 20 Dec 2024 11:42:21 -0500 Subject: [PATCH] updated comments and slight tweaks --- .../relayinterface/chain_components_test.go | 131 ++++++++++++++++-- .../relayinterface/lookups_test.go | 24 ++-- pkg/solana/chainwriter/ccip_example_config.go | 44 +++--- pkg/solana/chainwriter/chain_writer.go | 6 +- pkg/solana/chainwriter/chain_writer_test.go | 24 ++-- pkg/solana/chainwriter/helpers.go | 6 +- pkg/solana/chainwriter/lookups.go | 55 +++++--- pkg/solana/txm/txm.go | 14 ++ 8 files changed, 226 insertions(+), 78 deletions(-) diff --git a/integration-tests/relayinterface/chain_components_test.go b/integration-tests/relayinterface/chain_components_test.go index 8b06b94c5..4346fa930 100644 --- a/integration-tests/relayinterface/chain_components_test.go +++ b/integration-tests/relayinterface/chain_components_test.go @@ -6,7 +6,9 @@ package relayinterface import ( "context" "encoding/binary" + "fmt" "io" + "log" "os" "path/filepath" "sync" @@ -18,20 +20,27 @@ import ( "github.com/gagliardetto/solana-go/rpc/ws" "github.com/gagliardetto/solana-go/text" "github.com/stretchr/testify/require" + "github.com/test-go/testify/mock" "github.com/smartcontractkit/chainlink-common/pkg/codec" "github.com/smartcontractkit/chainlink-common/pkg/logger" commontestutils "github.com/smartcontractkit/chainlink-common/pkg/loop/testutils" + "github.com/smartcontractkit/chainlink-common/pkg/services/servicetest" "github.com/smartcontractkit/chainlink-common/pkg/types" . "github.com/smartcontractkit/chainlink-common/pkg/types/interfacetests" //nolint common practice to import test mods with . "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" + commonutils "github.com/smartcontractkit/chainlink-common/pkg/utils" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" contract "github.com/smartcontractkit/chainlink-solana/contracts/generated/contract_reader_interface" "github.com/smartcontractkit/chainlink-solana/integration-tests/solclient" "github.com/smartcontractkit/chainlink-solana/integration-tests/utils" "github.com/smartcontractkit/chainlink-solana/pkg/solana/chainreader" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/chainwriter" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" + keyMocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/mocks" solanautils "github.com/smartcontractkit/chainlink-solana/pkg/solana/utils" ) @@ -124,7 +133,9 @@ type SolanaChainComponentsInterfaceTesterHelper[T TestingT[T]] interface { Context(t T) context.Context Logger(t T) logger.Logger GetJSONEncodedIDL(t T) []byte - CreateAccount(t T, value uint64) solana.PublicKey + CreateAccount(t T, it SolanaChainComponentsInterfaceTester[T], value uint64) solana.PublicKey + TXM() *txm.TxManager + SolanaClient() *client.Client } type SolanaChainComponentsInterfaceTester[T TestingT[T]] struct { @@ -132,6 +143,7 @@ type SolanaChainComponentsInterfaceTester[T TestingT[T]] struct { Helper SolanaChainComponentsInterfaceTesterHelper[T] cr *chainreader.SolanaChainReaderService chainReaderConfig config.ChainReader + chainWriterConfig chainwriter.ChainWriterConfig } func (it *SolanaChainComponentsInterfaceTester[T]) Setup(t T) { @@ -179,6 +191,41 @@ func (it *SolanaChainComponentsInterfaceTester[T]) Setup(t T) { }, }, } + + it.chainWriterConfig = chainwriter.ChainWriterConfig{ + Programs: map[string]chainwriter.ProgramConfig{ + AnyContractName: { + IDL: string(it.Helper.GetJSONEncodedIDL(t)), + Methods: map[string]chainwriter.MethodConfig{ + "initialize": { + FromAddress: solana.MustPrivateKeyFromBase58(solclient.DefaultPrivateKeysSolValidator[1]).PublicKey().String(), + InputModifications: nil, + ChainSpecificName: "initialize", + LookupTables: chainwriter.LookupTables{}, + Accounts: []chainwriter.Lookup{ + chainwriter.PDALookups{ + Name: "Account", + PublicKey: chainwriter.AccountConstant{ + Name: "ProgramID", + Address: programPubKey, + }, + Seeds: []chainwriter.Seed{ + {Static: []byte("data")}, + {Dynamic: chainwriter.AccountLookup{ + Name: "TestIDX", + Location: "testIdx", + }}, + }, + IsWritable: true, + IsSigner: false, + }, + }, + DebugIDLocation: "", + }, + }, + }, + }, + } } func (it *SolanaChainComponentsInterfaceTester[T]) Name() string { @@ -210,14 +257,18 @@ func (it *SolanaChainComponentsInterfaceTester[T]) GetContractReader(t T) types. } func (it *SolanaChainComponentsInterfaceTester[T]) GetContractWriter(t T) types.ContractWriter { - return nil + cw, err := chainwriter.NewSolanaChainWriterService(it.Helper.Logger(t), it.Helper.SolanaClient(), *it.Helper.TXM(), nil, it.chainWriterConfig) + require.NoError(t, err) + + servicetest.Run(t, cw) + return cw } func (it *SolanaChainComponentsInterfaceTester[T]) GetBindings(t T) []types.BoundContract { // Create a new account with fresh state for each test return []types.BoundContract{ - {Name: AnyContractName, Address: it.Helper.CreateAccount(t, AnyValueToReadWithoutAnArgument).String()}, - {Name: AnySecondContractName, Address: it.Helper.CreateAccount(t, AnyDifferentValueToReadWithoutAnArgument).String()}, + {Name: AnyContractName, Address: it.Helper.CreateAccount(t, *it, AnyValueToReadWithoutAnArgument).String()}, + {Name: AnySecondContractName, Address: it.Helper.CreateAccount(t, *it, AnyDifferentValueToReadWithoutAnArgument).String()}, } } @@ -240,6 +291,8 @@ type helper struct { idlBts []byte nonce uint64 nonceMu sync.Mutex + txm txm.TxManager + sc *client.Client } func (h *helper) Init(t *testing.T) { @@ -256,6 +309,26 @@ func (h *helper) Init(t *testing.T) { solanautils.FundAccounts(t, []solana.PrivateKey{privateKey}, h.rpcClient) + cfg := config.NewDefault() + solanaClient, err := client.NewClient(h.rpcURL, cfg, 5*time.Second, nil) + require.NoError(t, err) + + h.sc = solanaClient + + loader := commonutils.NewLazyLoad(func() (client.ReaderWriter, error) { return solanaClient, nil }) + mkey := keyMocks.NewSimpleKeystore(t) + mkey.On("Sign", mock.Anything, privateKey.PublicKey().String(), mock.Anything).Return(func(_ context.Context, _ string, data []byte) []byte { + sig, _ := privateKey.Sign(data) + verifySignature(privateKey.PublicKey(), sig[:], data) + fmt.Printf("Signed for %s: %x\n", privateKey.PublicKey().String(), sig) + return sig[:] + }, nil) + lggr := logger.Test(t) + + txm := txm.NewTxm("localnet", loader, nil, cfg, mkey, lggr) + txm.Start(tests.Context(t)) + h.txm = txm + pubkey, err := solana.PublicKeyFromBase58(programPubKey) require.NoError(t, err) @@ -263,10 +336,28 @@ func (h *helper) Init(t *testing.T) { h.programID = pubkey } +func verifySignature(publicKey solana.PublicKey, signature []byte, message []byte) bool { + valid := publicKey.Verify(message, solana.SignatureFromBytes(signature)) + if valid { + log.Printf("Signature is valid for public key: %s\n", publicKey.String()) + } else { + log.Printf("Signature is invalid for public key: %s\n", publicKey.String()) + } + return valid +} + func (h *helper) RPCClient() *chainreader.RPCClientWrapper { return &chainreader.RPCClientWrapper{Client: h.rpcClient} } +func (h *helper) TXM() *txm.TxManager { + return &h.txm +} + +func (h *helper) SolanaClient() *client.Client { + return h.sc +} + func (h *helper) Context(t *testing.T) context.Context { return tests.Context(t) } @@ -298,7 +389,7 @@ func (h *helper) GetJSONEncodedIDL(t *testing.T) []byte { return h.idlBts } -func (h *helper) CreateAccount(t *testing.T, value uint64) solana.PublicKey { +func (h *helper) CreateAccount(t *testing.T, it SolanaChainComponentsInterfaceTester[*testing.T], value uint64) solana.PublicKey { t.Helper() // avoid collisions in parallel tests @@ -317,7 +408,7 @@ func (h *helper) CreateAccount(t *testing.T, value uint64) solana.PublicKey { privateKey, err := solana.PrivateKeyFromBase58(solclient.DefaultPrivateKeysSolValidator[1]) require.NoError(t, err) - h.runInitialize(t, nonce, value, pubKey, func(key solana.PublicKey) *solana.PrivateKey { + h.runInitialize(t, it, nonce, value, pubKey, func(key solana.PublicKey) *solana.PrivateKey { return &privateKey }, privateKey.PublicKey()) @@ -326,6 +417,7 @@ func (h *helper) CreateAccount(t *testing.T, value uint64) solana.PublicKey { func (h *helper) runInitialize( t *testing.T, + it SolanaChainComponentsInterfaceTester[*testing.T], nonce uint64, value uint64, data solana.PublicKey, @@ -334,10 +426,33 @@ func (h *helper) runInitialize( ) { t.Helper() - inst, err := contract.NewInitializeInstruction(nonce*value, value, data, payer, solana.SystemProgramID).ValidateAndBuild() + cw := it.GetContractWriter(t) + + args := map[string]interface{}{ + "testIdx": nonce * value, + "value": value, + } + + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, nonce*value) + + data, _, err := solana.FindProgramAddress( + [][]byte{ + []byte("data"), // Seed 1 + buf, // Seed 2 (test_idx) + }, + solana.MustPublicKeyFromBase58(programPubKey), // The program ID + ) require.NoError(t, err) - h.sendInstruction(t, inst, signerFunc, payer) + fmt.Printf("Derived PDA in test: %s\n", data.String()) + + SubmitTransactionToCW(t, &it, cw, "initialize", args, types.BoundContract{Name: AnyContractName, Address: h.programID.String()}, types.Finalized) + + // inst, err := contract.NewInitializeInstruction(nonce*value, value, data, payer, solana.SystemProgramID).ValidateAndBuild() + // require.NoError(t, err) + + // h.sendInstruction(t, inst, signerFunc, payer) } func (h *helper) sendInstruction( diff --git a/integration-tests/relayinterface/lookups_test.go b/integration-tests/relayinterface/lookups_test.go index 7333b2e8d..fd148abff 100644 --- a/integration-tests/relayinterface/lookups_test.go +++ b/integration-tests/relayinterface/lookups_test.go @@ -145,8 +145,8 @@ func TestPDALookups(t *testing.T) { pdaLookup := chainwriter.PDALookups{ Name: "TestPDA", PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ - chainwriter.AccountConstant{Name: "seed", Address: seed.String()}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountConstant{Name: "seed", Address: seed.String()}}, }, IsSigner: false, IsWritable: true, @@ -175,9 +175,9 @@ func TestPDALookups(t *testing.T) { pdaLookup := chainwriter.PDALookups{ Name: "TestPDA", PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ - chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}, - chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}}, }, IsSigner: false, IsWritable: true, @@ -198,8 +198,8 @@ func TestPDALookups(t *testing.T) { pdaLookup := chainwriter.PDALookups{ Name: "TestPDA", PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ - chainwriter.AccountLookup{Name: "seed1", Location: "MissingSeed"}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "MissingSeed"}}, }, IsSigner: false, IsWritable: true, @@ -233,9 +233,9 @@ func TestPDALookups(t *testing.T) { pdaLookup := chainwriter.PDALookups{ Name: "TestPDA", PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ - chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}, - chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}}, }, IsSigner: false, IsWritable: true, @@ -403,8 +403,8 @@ func TestLookupTables(t *testing.T) { Accounts: chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ - chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, }, IsSigner: false, IsWritable: false, diff --git a/pkg/solana/chainwriter/ccip_example_config.go b/pkg/solana/chainwriter/ccip_example_config.go index f277935d9..adbd4d324 100644 --- a/pkg/solana/chainwriter/ccip_example_config.go +++ b/pkg/solana/chainwriter/ccip_example_config.go @@ -47,8 +47,8 @@ func TestConfig() { IsWritable: false, }, // Seeds would be used if the user needed to look up addresses to use as seeds, which isn't the case here. - Seeds: []Lookup{ - AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}}, }, IsSigner: false, IsWritable: false, @@ -86,9 +86,9 @@ func TestConfig() { IsWritable: false, }, // Similar to the RegistryTokenState above, the user is looking up PDA accounts based on the dest tokens. - Seeds: []Lookup{ - AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}, - AccountLookup{Location: "Message.Header.DestChainSelector"}, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}}, + {Dynamic: AccountLookup{Location: "Message.Header.DestChainSelector"}}, }, IsSigner: false, IsWritable: false, @@ -120,13 +120,13 @@ func TestConfig() { IsWritable: false, }, // The seed is the receiver address. - Seeds: []Lookup{ - AccountLookup{ + Seeds: []Seed{ + {Dynamic: AccountLookup{ Name: "Receiver", Location: "Message.Receiver", IsSigner: false, IsWritable: false, - }, + }}, }, }, // Account constant @@ -146,8 +146,8 @@ func TestConfig() { IsWritable: false, }, // The seed, once again, is the destination token address. - Seeds: []Lookup{ - AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.TokenAmounts.DestTokenAddress"}}, }, IsSigner: false, IsWritable: false, @@ -175,9 +175,9 @@ func TestConfig() { IsSigner: false, IsWritable: false, }, - Seeds: []Lookup{ - AccountLookup{Location: "Message.Header.DestChainSelector"}, - AccountLookup{Location: "Message.Header.SourceChainSelector"}, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.Header.DestChainSelector"}}, + {Dynamic: AccountLookup{Location: "Message.Header.SourceChainSelector"}}, }, IsSigner: false, IsWritable: false, @@ -191,11 +191,11 @@ func TestConfig() { IsSigner: false, IsWritable: false, }, - Seeds: []Lookup{ - AccountLookup{ + Seeds: []Seed{ + {Dynamic: AccountLookup{ // The seed is the merkle root of the report, as passed into the input params. Location: "args.MerkleRoot", - }, + }}, }, IsSigner: false, IsWritable: false, @@ -211,9 +211,9 @@ func TestConfig() { }, // In this case, the user configured multiple seeds. These will be used in conjunction // with the public key to generate one or multiple PDA accounts. - Seeds: []Lookup{ - AccountLookup{Location: "Message.Receiver"}, - AccountLookup{Location: "Message.Header.DestChainSelector"}, + Seeds: []Seed{ + {Dynamic: AccountLookup{Location: "Message.Receiver"}}, + {Dynamic: AccountLookup{Location: "Message.Header.DestChainSelector"}}, }, }, // Account constant @@ -284,11 +284,11 @@ func TestConfig() { IsSigner: false, IsWritable: false, }, - Seeds: []Lookup{ - AccountLookup{ + Seeds: []Seed{ + {Dynamic: AccountLookup{ // The seed is the merkle root of the report, as passed into the input params. Location: "args.MerkleRoots", - }, + }}, }, IsSigner: false, IsWritable: false, diff --git a/pkg/solana/chainwriter/chain_writer.go b/pkg/solana/chainwriter/chain_writer.go index e16a55e60..76600c243 100644 --- a/pkg/solana/chainwriter/chain_writer.go +++ b/pkg/solana/chainwriter/chain_writer.go @@ -250,6 +250,10 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra codec := s.codecs[contractName] encodedPayload, err := codec.Encode(ctx, args, method) + + discriminator := GetDiscriminator(methodConfig.ChainSpecificName) + encodedPayload = append(discriminator[:], encodedPayload...) + if err != nil { return errorWithDebugID(fmt.Errorf("error encoding transaction payload: %w", err), debugID) } @@ -302,7 +306,7 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra } // Enqueue transaction - if err = s.txm.Enqueue(ctx, accounts[0].PublicKey.String(), tx, &transactionID, blockhash.Value.LastValidBlockHeight); err != nil { + if err = s.txm.Enqueue(ctx, methodConfig.FromAddress, tx, &transactionID, blockhash.Value.LastValidBlockHeight); err != nil { return errorWithDebugID(fmt.Errorf("error enqueuing transaction: %w", err), debugID) } diff --git a/pkg/solana/chainwriter/chain_writer_test.go b/pkg/solana/chainwriter/chain_writer_test.go index 9798bdb4c..ff1d357c2 100644 --- a/pkg/solana/chainwriter/chain_writer_test.go +++ b/pkg/solana/chainwriter/chain_writer_test.go @@ -85,9 +85,9 @@ func TestChainWriter_GetAddresses(t *testing.T) { Accounts: chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed2 for PDA lookup - chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}}, }, IsSigner: derivedTablePdaLookupMeta.IsSigner, IsWritable: derivedTablePdaLookupMeta.IsWritable, @@ -129,9 +129,9 @@ func TestChainWriter_GetAddresses(t *testing.T) { chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: solana.SystemProgramID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed1 for PDA lookup - chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, }, IsSigner: pdaLookupMeta.IsSigner, IsWritable: pdaLookupMeta.IsWritable, @@ -272,9 +272,9 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { Accounts: chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed1 for PDA lookup - chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, }, IsSigner: true, IsWritable: true, @@ -289,9 +289,9 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { Accounts: chainwriter.PDALookups{ Name: "MiscPDA", PublicKey: chainwriter.AccountConstant{Name: "UnusedAccount", Address: unusedProgramID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed2 for PDA lookup - chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}}, }, IsSigner: true, IsWritable: true, @@ -428,9 +428,9 @@ func TestChainWriter_SubmitTransaction(t *testing.T) { Accounts: chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed2 for PDA lookup - chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}}, }, IsSigner: false, IsWritable: false, @@ -459,9 +459,9 @@ func TestChainWriter_SubmitTransaction(t *testing.T) { chainwriter.PDALookups{ Name: "DataAccountPDA", PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: solana.SystemProgramID.String()}, - Seeds: []chainwriter.Lookup{ + Seeds: []chainwriter.Seed{ // extract seed1 for PDA lookup - chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}, + {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, }, IsSigner: false, IsWritable: false, diff --git a/pkg/solana/chainwriter/helpers.go b/pkg/solana/chainwriter/helpers.go index 6f78c7a63..a4b18e4d5 100644 --- a/pkg/solana/chainwriter/helpers.go +++ b/pkg/solana/chainwriter/helpers.go @@ -3,6 +3,7 @@ package chainwriter import ( "context" "crypto/sha256" + "encoding/binary" "errors" "fmt" "reflect" @@ -41,12 +42,15 @@ func GetValuesAtLocation(args any, location string) ([][]byte, error) { if err != nil { return nil, err } - for _, value := range addressList { if byteArray, ok := value.([]byte); ok { vals = append(vals, byteArray) } else if address, ok := value.(solana.PublicKey); ok { vals = append(vals, address.Bytes()) + } else if num, ok := value.(uint64); ok { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, num) + vals = append(vals, buf) } else { return nil, fmt.Errorf("invalid value format at path: %s", location) } diff --git a/pkg/solana/chainwriter/lookups.go b/pkg/solana/chainwriter/lookups.go index b9d3ca7cd..668fa1e4f 100644 --- a/pkg/solana/chainwriter/lookups.go +++ b/pkg/solana/chainwriter/lookups.go @@ -33,6 +33,11 @@ type AccountLookup struct { IsWritable bool } +type Seed struct { + Static []byte // Static seed value + Dynamic Lookup // Dynamic lookup for seed +} + // PDALookups generates Program Derived Addresses (PDA) by combining a derived public key with one or more seeds. type PDALookups struct { Name string @@ -40,7 +45,7 @@ type PDALookups struct { // there will be multiple PDAs generated by combining each PublicKey with the seeds. PublicKey Lookup // Seeds to be derived from an additional lookup - Seeds []Lookup + Seeds []Seed IsSigner bool IsWritable bool // OPTIONAL: On-chain location and type of desired data from PDA (e.g. a sub-account of the data account) @@ -210,29 +215,35 @@ func getSeedBytes(ctx context.Context, lookup PDALookups, args any, derivedTable var seedBytes [][]byte for _, seed := range lookup.Seeds { - if lookupSeed, ok := seed.(AccountLookup); ok { - // Get value from a location (This doesn't have to be an address, it can be any value) - bytes, err := GetValuesAtLocation(args, lookupSeed.Location) - if err != nil { - return nil, fmt.Errorf("error getting address seed: %w", err) - } - // validate seed length - for _, b := range bytes { - if len(b) > solana.MaxSeedLength { - return nil, fmt.Errorf("seed byte array exceeds maximum length of %d: got %d bytes", solana.MaxSeedLength, len(b)) + if seed.Static != nil { + seedBytes = append(seedBytes, seed.Static) + } + if seed.Dynamic != nil { + dynamicSeed := seed.Dynamic + if lookupSeed, ok := dynamicSeed.(AccountLookup); ok { + // Get value from a location (This doens't have to be an address, it can be any value) + bytes, err := GetValuesAtLocation(args, lookupSeed.Location) + if err != nil { + return nil, fmt.Errorf("error getting address seed: %w", err) + } + // validate seed length + for _, b := range bytes { + if len(b) > solana.MaxSeedLength { + return nil, fmt.Errorf("seed byte array exceeds maximum length of %d: got %d bytes", solana.MaxSeedLength, len(b)) + } + seedBytes = append(seedBytes, b) + } + } else { + // Get address seeds from the lookup + seedAddresses, err := GetAddresses(ctx, args, []Lookup{dynamicSeed}, derivedTableMap, reader) + if err != nil { + return nil, fmt.Errorf("error getting address seed: %w", err) } - seedBytes = append(seedBytes, b) - } - } else { - // Get address seeds from the lookup - seedAddresses, err := GetAddresses(ctx, args, []Lookup{seed}, derivedTableMap, reader) - if err != nil { - return nil, fmt.Errorf("error getting address seed: %w", err) - } - // Add each address seed as bytes - for _, address := range seedAddresses { - seedBytes = append(seedBytes, address.PublicKey.Bytes()) + // Add each address seed as bytes + for _, address := range seedAddresses { + seedBytes = append(seedBytes, address.PublicKey.Bytes()) + } } } } diff --git a/pkg/solana/txm/txm.go b/pkg/solana/txm/txm.go index c87089060..06729ca63 100644 --- a/pkg/solana/txm/txm.go +++ b/pkg/solana/txm/txm.go @@ -253,10 +253,24 @@ func (txm *Txm) buildTx(ctx context.Context, msg pendingTx, retryCount int) (sol if err != nil { return solanaGo.Transaction{}, fmt.Errorf("error in Sign: %w", err) } + fmt.Printf("Transaction Message (hex): %x\n", txMsg) + var finalSig [64]byte copy(finalSig[:], sigBytes) newTx.Signatures = append(newTx.Signatures, finalSig) + for i, sig := range newTx.Signatures { + fmt.Printf("Signature[%d]: %x\n", i, sig) + } + + for i, account := range newTx.Message.AccountKeys { + writable, err := newTx.Message.IsWritable(account) + if err != nil { + return solanaGo.Transaction{}, fmt.Errorf("error in IsWritable: %w", err) + } + fmt.Printf("Account[%d]: %s (Signer: %v, Writable: %v)\n", i, account, newTx.Message.IsSigner(account), writable) + } + return newTx, nil }