From 89b84088231cd695a79b32522028f3511a876cf4 Mon Sep 17 00:00:00 2001 From: ilija42 <57732589+ilija42@users.noreply.github.com> Date: Thu, 13 Feb 2025 00:15:57 +0100 Subject: [PATCH 1/4] multi read for BatchGetLatestValue (#1070) * Start working on multi read for BatchGetLatestValue * add test for batch multi-read --------- Co-authored-by: Awbrey Hughlett --- .../relayinterface/chain_components_test.go | 54 +++++++++++++++++++ pkg/solana/chainreader/chain_reader.go | 52 ++++++++++++++---- 2 files changed, 95 insertions(+), 11 deletions(-) diff --git a/integration-tests/relayinterface/chain_components_test.go b/integration-tests/relayinterface/chain_components_test.go index b58e1dd76..2d4cd6981 100644 --- a/integration-tests/relayinterface/chain_components_test.go +++ b/integration-tests/relayinterface/chain_components_test.go @@ -199,6 +199,7 @@ func RunContractReaderTests[T WrappedTestingT[T]](t T, it *SolanaChainComponents // GetLatestValue method const ( ContractReaderGetLatestValueUsingMultiReader = "Get latest value using multi reader" + ContractReaderBatchGetLatestValueUsingMultiReader = "Batch Get latest value using multi reader" ContractReaderGetLatestValueWithAddressHardcodedIntoResponse = "Get latest value with AddressHardcoded into response" ContractReaderGetLatestValueUsingMultiReaderWithParmsReuse = "Get latest value using multi reader with params reuse" ContractReaderGetLatestValueGetTokenPrices = "Get latest value handles get token prices edge case" @@ -322,6 +323,59 @@ func RunContractReaderInLoopTests[T WrappedTestingT[T]](t T, it ChainComponentsI require.Equal(t, uint32(1800000002), res[1].Timestamp) }, }, + { + Name: ContractReaderBatchGetLatestValueUsingMultiReader, + Test: func(t T) { + cr := it.GetContractReader(t) + bindings := it.GetBindings(t) + ctx := tests.Context(t) + bound := BindingsByName(bindings, AnyContractName)[0] + + require.NoError(t, cr.Bind(ctx, bindings)) + + type MultiReadResult struct { + A uint8 + B int16 + U string + V bool + } + + // setup call data + actual := uint64(0) + multiParams, multiActual := map[string]any{"ID": 1}, &MultiReadResult{} + + batchGetLatestValueRequest := make(types.BatchGetLatestValuesRequest) + batchGetLatestValueRequest[bound] = []types.BatchRead{ + { + ReadName: MethodReturningUint64, + Params: nil, + ReturnVal: &actual, + }, + { + ReadName: MultiReadWithParamsReuse, + Params: multiParams, + ReturnVal: multiActual, + }, + } + + result, err := cr.BatchGetLatestValues(ctx, batchGetLatestValueRequest) + + require.NoError(t, err) + + expectedMRR := MultiReadResult{A: 10, B: 20, U: "olleH", V: true} + anyContractBatch := result[bound] + + returnValue, err := anyContractBatch[1].GetResult() + assert.NoError(t, err) + assert.Contains(t, anyContractBatch[1].ReadName, MultiReadWithParamsReuse) + require.Equal(t, &expectedMRR, returnValue) + + returnValue, err = anyContractBatch[0].GetResult() + assert.NoError(t, err) + assert.Contains(t, anyContractBatch[0].ReadName, MethodReturningUint64) + assert.Equal(t, AnyValueToReadWithoutAnArgument, *returnValue.(*uint64)) + }, + }, } RunTests(t, it, testCases) } diff --git a/pkg/solana/chainreader/chain_reader.go b/pkg/solana/chainreader/chain_reader.go index 10790e2a5..b9b0b0991 100644 --- a/pkg/solana/chainreader/chain_reader.go +++ b/pkg/solana/chainreader/chain_reader.go @@ -217,15 +217,40 @@ func (s *ContractReaderService) GetLatestValue(ctx context.Context, readIdentifi // BatchGetLatestValues implements the types.ContractReader interface. func (s *ContractReaderService) BatchGetLatestValues(ctx context.Context, request types.BatchGetLatestValuesRequest) (types.BatchGetLatestValuesResult, error) { - idxLookup := make(map[types.BoundContract][]int) - var batch []call + idxLookup := make(map[types.BoundContract]map[int]int) + multiIdxLookup := make(map[types.BoundContract]map[int]int) + result := make(types.BatchGetLatestValuesResult) + + var ( + batch []call + multiReadResults []batchResultWithErr + ) for bound, req := range request { - idxLookup[bound] = make([]int, len(req)) + idxLookup[bound] = make(map[int]int) + multiIdxLookup[bound] = make(map[int]int) + result[bound] = make(types.ContractBatchResults, len(req)) for idx, readReq := range req { + readIdentifier := bound.ReadIdentifier(readReq.ReadName) + vals, ok := s.lookup.getContractForReadIdentifiers(readIdentifier) + if !ok { + return nil, fmt.Errorf("%w: no contract for read identifier: %q", types.ErrInvalidType, readIdentifier) + } + + // exclude multi read reads from the big batch request and populate them separately and merge results later. + if len(vals.reads) > 1 { + err := doMultiRead(ctx, s.client, s.bdRegistry, vals, readReq.Params, readReq.ReturnVal) + + multiIdxLookup[bound][idx] = len(multiReadResults) + multiReadResults = append(multiReadResults, batchResultWithErr{address: vals.address, namespace: vals.contract, readName: readReq.ReadName, returnVal: readReq.ReturnVal, err: err}) + + continue + } + idxLookup[bound][idx] = len(batch) - // TODO this is a temporary edge case - NONEVM-1320 + + // TODO: this is a temporary edge case - NONEVM-1320 if readReq.ReadName == GetTokenPrices { return nil, fmt.Errorf("%w: %s is not supported in batch requests", types.ErrInvalidType, GetTokenPrices) } @@ -248,20 +273,25 @@ func (s *ContractReaderService) BatchGetLatestValues(ctx context.Context, reques return nil, errors.New("unexpected number of results") } - result := make(types.BatchGetLatestValuesResult) + populateResultFromLookup(idxLookup, result, results) + populateResultFromLookup(multiIdxLookup, result, multiReadResults) - for bound, idxs := range idxLookup { - result[bound] = make(types.ContractBatchResults, len(idxs)) + return result, nil +} - for idx, callIdx := range idxs { +func populateResultFromLookup( + idxLookup map[types.BoundContract]map[int]int, + output types.BatchGetLatestValuesResult, + results []batchResultWithErr, +) { + for bound, idxs := range idxLookup { + for reqIdx, callIdx := range idxs { res := types.BatchReadResult{ReadName: results[callIdx].readName} res.SetResult(results[callIdx].returnVal, results[callIdx].err) - result[bound][idx] = res + output[bound][reqIdx] = res } } - - return result, nil } // QueryKey implements the types.ContractReader interface. From 376726a51e78ad1672991159b3ea60f04f52ebce Mon Sep 17 00:00:00 2001 From: amit-momin <108959691+amit-momin@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:16:09 -0600 Subject: [PATCH 2/4] Fixed lookup table filtering (#1074) * Fixed lookup table filtering * Updated lookup table to only use static seed * Fixed linting * Fixed derived lookup table test --- .../InitializeLookupTable.go | 88 +++---- .../InitializeLookupTable_test.go | 8 +- .../contract_reader_interface/Storeval.go | 194 ++++++++++++++++ .../Storeval_test.go | 32 +++ .../contract_reader_interface/accounts.go | 11 + .../contract_reader_interface/instructions.go | 15 +- .../contract-reader-interface/src/lib.rs | 41 +++- .../relayinterface/chain_components_test.go | 216 +++++++++++++++++- .../relayinterface/lookups_test.go | 2 +- pkg/solana/chainwriter/chain_writer.go | 25 +- pkg/solana/chainwriter/chain_writer_test.go | 11 +- pkg/solana/chainwriter/helpers.go | 6 +- 12 files changed, 568 insertions(+), 81 deletions(-) create mode 100644 contracts/generated/contract_reader_interface/Storeval.go create mode 100644 contracts/generated/contract_reader_interface/Storeval_test.go diff --git a/contracts/generated/contract_reader_interface/InitializeLookupTable.go b/contracts/generated/contract_reader_interface/InitializeLookupTable.go index 5b339f169..37ad99e6a 100644 --- a/contracts/generated/contract_reader_interface/InitializeLookupTable.go +++ b/contracts/generated/contract_reader_interface/InitializeLookupTable.go @@ -10,92 +10,92 @@ import ( ag_treeout "github.com/gagliardetto/treeout" ) -// InitializeLookupTable is the `initializeLookupTable` instruction. -type InitializeLookupTable struct { +// Initializelookuptable is the `initializelookuptable` instruction. +type Initializelookuptable struct { LookupTable *ag_solanago.PublicKey - // [0] = [WRITE] writeDataAccount - // ··········· PDA for LookupTableDataAccount, derived from seeds and created by the System Program - // - // [1] = [WRITE, SIGNER] admin + // [0] = [WRITE, SIGNER] admin // ··········· Admin account that pays for PDA creation and signs the transaction // + // [1] = [WRITE] writeDataAccount + // ··········· PDA for LookupTableDataAccount, derived from seeds and created by the System Program + // // [2] = [] systemProgram // ··········· System Program required for PDA creation ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` } -// NewInitializeLookupTableInstructionBuilder creates a new `InitializeLookupTable` instruction builder. -func NewInitializeLookupTableInstructionBuilder() *InitializeLookupTable { - nd := &InitializeLookupTable{ +// NewInitializelookuptableInstructionBuilder creates a new `Initializelookuptable` instruction builder. +func NewInitializelookuptableInstructionBuilder() *Initializelookuptable { + nd := &Initializelookuptable{ AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), } return nd } // SetLookupTable sets the "lookupTable" parameter. -func (inst *InitializeLookupTable) SetLookupTable(lookupTable ag_solanago.PublicKey) *InitializeLookupTable { +func (inst *Initializelookuptable) SetLookupTable(lookupTable ag_solanago.PublicKey) *Initializelookuptable { inst.LookupTable = &lookupTable return inst } -// SetWriteDataAccountAccount sets the "writeDataAccount" account. -// PDA for LookupTableDataAccount, derived from seeds and created by the System Program -func (inst *InitializeLookupTable) SetWriteDataAccountAccount(writeDataAccount ag_solanago.PublicKey) *InitializeLookupTable { - inst.AccountMetaSlice[0] = ag_solanago.Meta(writeDataAccount).WRITE() +// SetAdminAccount sets the "admin" account. +// Admin account that pays for PDA creation and signs the transaction +func (inst *Initializelookuptable) SetAdminAccount(admin ag_solanago.PublicKey) *Initializelookuptable { + inst.AccountMetaSlice[0] = ag_solanago.Meta(admin).WRITE().SIGNER() return inst } -// GetWriteDataAccountAccount gets the "writeDataAccount" account. -// PDA for LookupTableDataAccount, derived from seeds and created by the System Program -func (inst *InitializeLookupTable) GetWriteDataAccountAccount() *ag_solanago.AccountMeta { +// GetAdminAccount gets the "admin" account. +// Admin account that pays for PDA creation and signs the transaction +func (inst *Initializelookuptable) GetAdminAccount() *ag_solanago.AccountMeta { return inst.AccountMetaSlice[0] } -// SetAdminAccount sets the "admin" account. -// Admin account that pays for PDA creation and signs the transaction -func (inst *InitializeLookupTable) SetAdminAccount(admin ag_solanago.PublicKey) *InitializeLookupTable { - inst.AccountMetaSlice[1] = ag_solanago.Meta(admin).WRITE().SIGNER() +// SetWriteDataAccountAccount sets the "writeDataAccount" account. +// PDA for LookupTableDataAccount, derived from seeds and created by the System Program +func (inst *Initializelookuptable) SetWriteDataAccountAccount(writeDataAccount ag_solanago.PublicKey) *Initializelookuptable { + inst.AccountMetaSlice[1] = ag_solanago.Meta(writeDataAccount).WRITE() return inst } -// GetAdminAccount gets the "admin" account. -// Admin account that pays for PDA creation and signs the transaction -func (inst *InitializeLookupTable) GetAdminAccount() *ag_solanago.AccountMeta { +// GetWriteDataAccountAccount gets the "writeDataAccount" account. +// PDA for LookupTableDataAccount, derived from seeds and created by the System Program +func (inst *Initializelookuptable) GetWriteDataAccountAccount() *ag_solanago.AccountMeta { return inst.AccountMetaSlice[1] } // SetSystemProgramAccount sets the "systemProgram" account. // System Program required for PDA creation -func (inst *InitializeLookupTable) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *InitializeLookupTable { +func (inst *Initializelookuptable) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Initializelookuptable { inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram) return inst } // GetSystemProgramAccount gets the "systemProgram" account. // System Program required for PDA creation -func (inst *InitializeLookupTable) GetSystemProgramAccount() *ag_solanago.AccountMeta { +func (inst *Initializelookuptable) GetSystemProgramAccount() *ag_solanago.AccountMeta { return inst.AccountMetaSlice[2] } -func (inst InitializeLookupTable) Build() *Instruction { +func (inst Initializelookuptable) Build() *Instruction { return &Instruction{BaseVariant: ag_binary.BaseVariant{ Impl: inst, - TypeID: Instruction_InitializeLookupTable, + TypeID: Instruction_Initializelookuptable, }} } // ValidateAndBuild validates the instruction parameters and accounts; // if there is a validation error, it returns the error. // Otherwise, it builds and returns the instruction. -func (inst InitializeLookupTable) ValidateAndBuild() (*Instruction, error) { +func (inst Initializelookuptable) ValidateAndBuild() (*Instruction, error) { if err := inst.Validate(); err != nil { return nil, err } return inst.Build(), nil } -func (inst *InitializeLookupTable) Validate() error { +func (inst *Initializelookuptable) Validate() error { // Check whether all (required) parameters are set: { if inst.LookupTable == nil { @@ -106,10 +106,10 @@ func (inst *InitializeLookupTable) Validate() error { // Check whether all (required) accounts are set: { if inst.AccountMetaSlice[0] == nil { - return errors.New("accounts.WriteDataAccount is not set") + return errors.New("accounts.Admin is not set") } if inst.AccountMetaSlice[1] == nil { - return errors.New("accounts.Admin is not set") + return errors.New("accounts.WriteDataAccount is not set") } if inst.AccountMetaSlice[2] == nil { return errors.New("accounts.SystemProgram is not set") @@ -118,11 +118,11 @@ func (inst *InitializeLookupTable) Validate() error { return nil } -func (inst *InitializeLookupTable) EncodeToTree(parent ag_treeout.Branches) { +func (inst *Initializelookuptable) EncodeToTree(parent ag_treeout.Branches) { parent.Child(ag_format.Program(ProgramName, ProgramID)). // ParentFunc(func(programBranch ag_treeout.Branches) { - programBranch.Child(ag_format.Instruction("InitializeLookupTable")). + programBranch.Child(ag_format.Instruction("Initializelookuptable")). // ParentFunc(func(instructionBranch ag_treeout.Branches) { @@ -133,15 +133,15 @@ func (inst *InitializeLookupTable) EncodeToTree(parent ag_treeout.Branches) { // Accounts of the instruction: instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { - accountsBranch.Child(ag_format.Meta(" writeData", inst.AccountMetaSlice[0])) - accountsBranch.Child(ag_format.Meta(" admin", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta(" admin", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" writeData", inst.AccountMetaSlice[1])) accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2])) }) }) }) } -func (obj InitializeLookupTable) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { +func (obj Initializelookuptable) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { // Serialize `LookupTable` param: err = encoder.Encode(obj.LookupTable) if err != nil { @@ -149,7 +149,7 @@ func (obj InitializeLookupTable) MarshalWithEncoder(encoder *ag_binary.Encoder) } return nil } -func (obj *InitializeLookupTable) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { +func (obj *Initializelookuptable) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { // Deserialize `LookupTable`: err = decoder.Decode(&obj.LookupTable) if err != nil { @@ -158,17 +158,17 @@ func (obj *InitializeLookupTable) UnmarshalWithDecoder(decoder *ag_binary.Decode return nil } -// NewInitializeLookupTableInstruction declares a new InitializeLookupTable instruction with the provided parameters and accounts. -func NewInitializeLookupTableInstruction( +// NewInitializelookuptableInstruction declares a new Initializelookuptable instruction with the provided parameters and accounts. +func NewInitializelookuptableInstruction( // Parameters: lookupTable ag_solanago.PublicKey, // Accounts: - writeDataAccount ag_solanago.PublicKey, admin ag_solanago.PublicKey, - systemProgram ag_solanago.PublicKey) *InitializeLookupTable { - return NewInitializeLookupTableInstructionBuilder(). + writeDataAccount ag_solanago.PublicKey, + systemProgram ag_solanago.PublicKey) *Initializelookuptable { + return NewInitializelookuptableInstructionBuilder(). SetLookupTable(lookupTable). - SetWriteDataAccountAccount(writeDataAccount). SetAdminAccount(admin). + SetWriteDataAccountAccount(writeDataAccount). SetSystemProgramAccount(systemProgram) } diff --git a/contracts/generated/contract_reader_interface/InitializeLookupTable_test.go b/contracts/generated/contract_reader_interface/InitializeLookupTable_test.go index e30e2204d..8bd83e288 100644 --- a/contracts/generated/contract_reader_interface/InitializeLookupTable_test.go +++ b/contracts/generated/contract_reader_interface/InitializeLookupTable_test.go @@ -10,18 +10,18 @@ import ( "testing" ) -func TestEncodeDecode_InitializeLookupTable(t *testing.T) { +func TestEncodeDecode_Initializelookuptable(t *testing.T) { fu := ag_gofuzz.New().NilChance(0) for i := 0; i < 1; i++ { - t.Run("InitializeLookupTable"+strconv.Itoa(i), func(t *testing.T) { + t.Run("Initializelookuptable"+strconv.Itoa(i), func(t *testing.T) { { - params := new(InitializeLookupTable) + params := new(Initializelookuptable) fu.Fuzz(params) params.AccountMetaSlice = nil buf := new(bytes.Buffer) err := encodeT(*params, buf) ag_require.NoError(t, err) - got := new(InitializeLookupTable) + got := new(Initializelookuptable) err = decodeT(got, buf.Bytes()) got.AccountMetaSlice = nil ag_require.NoError(t, err) diff --git a/contracts/generated/contract_reader_interface/Storeval.go b/contracts/generated/contract_reader_interface/Storeval.go new file mode 100644 index 000000000..b124148b0 --- /dev/null +++ b/contracts/generated/contract_reader_interface/Storeval.go @@ -0,0 +1,194 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package contract_reader_interface + +import ( + "errors" + ag_binary "github.com/gagliardetto/binary" + ag_solanago "github.com/gagliardetto/solana-go" + ag_format "github.com/gagliardetto/solana-go/text/format" + ag_treeout "github.com/gagliardetto/treeout" +) + +// Storeval is the `storeval` instruction. +type Storeval struct { + TestIdx *uint64 + Value *uint64 + + // [0] = [WRITE, SIGNER] admin + // ··········· Admin account that pays for PDA creation and signs the transaction + // + // [1] = [WRITE] data + // + // [2] = [] systemProgram + // ··········· System Program required for PDA creation + ag_solanago.AccountMetaSlice `bin:"-" borsh_skip:"true"` +} + +// NewStorevalInstructionBuilder creates a new `Storeval` instruction builder. +func NewStorevalInstructionBuilder() *Storeval { + nd := &Storeval{ + AccountMetaSlice: make(ag_solanago.AccountMetaSlice, 3), + } + return nd +} + +// SetTestIdx sets the "testIdx" parameter. +func (inst *Storeval) SetTestIdx(testIdx uint64) *Storeval { + inst.TestIdx = &testIdx + return inst +} + +// SetValue sets the "value" parameter. +func (inst *Storeval) SetValue(value uint64) *Storeval { + inst.Value = &value + return inst +} + +// SetAdminAccount sets the "admin" account. +// Admin account that pays for PDA creation and signs the transaction +func (inst *Storeval) SetAdminAccount(admin ag_solanago.PublicKey) *Storeval { + inst.AccountMetaSlice[0] = ag_solanago.Meta(admin).WRITE().SIGNER() + return inst +} + +// GetAdminAccount gets the "admin" account. +// Admin account that pays for PDA creation and signs the transaction +func (inst *Storeval) GetAdminAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[0] +} + +// SetDataAccount sets the "data" account. +func (inst *Storeval) SetDataAccount(data ag_solanago.PublicKey) *Storeval { + inst.AccountMetaSlice[1] = ag_solanago.Meta(data).WRITE() + return inst +} + +// GetDataAccount gets the "data" account. +func (inst *Storeval) GetDataAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[1] +} + +// SetSystemProgramAccount sets the "systemProgram" account. +// System Program required for PDA creation +func (inst *Storeval) SetSystemProgramAccount(systemProgram ag_solanago.PublicKey) *Storeval { + inst.AccountMetaSlice[2] = ag_solanago.Meta(systemProgram) + return inst +} + +// GetSystemProgramAccount gets the "systemProgram" account. +// System Program required for PDA creation +func (inst *Storeval) GetSystemProgramAccount() *ag_solanago.AccountMeta { + return inst.AccountMetaSlice[2] +} + +func (inst Storeval) Build() *Instruction { + return &Instruction{BaseVariant: ag_binary.BaseVariant{ + Impl: inst, + TypeID: Instruction_Storeval, + }} +} + +// ValidateAndBuild validates the instruction parameters and accounts; +// if there is a validation error, it returns the error. +// Otherwise, it builds and returns the instruction. +func (inst Storeval) ValidateAndBuild() (*Instruction, error) { + if err := inst.Validate(); err != nil { + return nil, err + } + return inst.Build(), nil +} + +func (inst *Storeval) Validate() error { + // Check whether all (required) parameters are set: + { + if inst.TestIdx == nil { + return errors.New("TestIdx parameter is not set") + } + if inst.Value == nil { + return errors.New("Value parameter is not set") + } + } + + // Check whether all (required) accounts are set: + { + if inst.AccountMetaSlice[0] == nil { + return errors.New("accounts.Admin is not set") + } + if inst.AccountMetaSlice[1] == nil { + return errors.New("accounts.Data is not set") + } + if inst.AccountMetaSlice[2] == nil { + return errors.New("accounts.SystemProgram is not set") + } + } + return nil +} + +func (inst *Storeval) EncodeToTree(parent ag_treeout.Branches) { + parent.Child(ag_format.Program(ProgramName, ProgramID)). + // + ParentFunc(func(programBranch ag_treeout.Branches) { + programBranch.Child(ag_format.Instruction("Storeval")). + // + ParentFunc(func(instructionBranch ag_treeout.Branches) { + + // Parameters of the instruction: + instructionBranch.Child("Params[len=2]").ParentFunc(func(paramsBranch ag_treeout.Branches) { + paramsBranch.Child(ag_format.Param("TestIdx", *inst.TestIdx)) + paramsBranch.Child(ag_format.Param(" Value", *inst.Value)) + }) + + // Accounts of the instruction: + instructionBranch.Child("Accounts[len=3]").ParentFunc(func(accountsBranch ag_treeout.Branches) { + accountsBranch.Child(ag_format.Meta(" admin", inst.AccountMetaSlice[0])) + accountsBranch.Child(ag_format.Meta(" data", inst.AccountMetaSlice[1])) + accountsBranch.Child(ag_format.Meta("systemProgram", inst.AccountMetaSlice[2])) + }) + }) + }) +} + +func (obj Storeval) MarshalWithEncoder(encoder *ag_binary.Encoder) (err error) { + // Serialize `TestIdx` param: + err = encoder.Encode(obj.TestIdx) + if err != nil { + return err + } + // Serialize `Value` param: + err = encoder.Encode(obj.Value) + if err != nil { + return err + } + return nil +} +func (obj *Storeval) UnmarshalWithDecoder(decoder *ag_binary.Decoder) (err error) { + // Deserialize `TestIdx`: + err = decoder.Decode(&obj.TestIdx) + if err != nil { + return err + } + // Deserialize `Value`: + err = decoder.Decode(&obj.Value) + if err != nil { + return err + } + return nil +} + +// NewStorevalInstruction declares a new Storeval instruction with the provided parameters and accounts. +func NewStorevalInstruction( + // Parameters: + testIdx uint64, + value uint64, + // Accounts: + admin ag_solanago.PublicKey, + data ag_solanago.PublicKey, + systemProgram ag_solanago.PublicKey) *Storeval { + return NewStorevalInstructionBuilder(). + SetTestIdx(testIdx). + SetValue(value). + SetAdminAccount(admin). + SetDataAccount(data). + SetSystemProgramAccount(systemProgram) +} diff --git a/contracts/generated/contract_reader_interface/Storeval_test.go b/contracts/generated/contract_reader_interface/Storeval_test.go new file mode 100644 index 000000000..6e8b89599 --- /dev/null +++ b/contracts/generated/contract_reader_interface/Storeval_test.go @@ -0,0 +1,32 @@ +// Code generated by https://github.com/gagliardetto/anchor-go. DO NOT EDIT. + +package contract_reader_interface + +import ( + "bytes" + ag_gofuzz "github.com/gagliardetto/gofuzz" + ag_require "github.com/stretchr/testify/require" + "strconv" + "testing" +) + +func TestEncodeDecode_Storeval(t *testing.T) { + fu := ag_gofuzz.New().NilChance(0) + for i := 0; i < 1; i++ { + t.Run("Storeval"+strconv.Itoa(i), func(t *testing.T) { + { + params := new(Storeval) + fu.Fuzz(params) + params.AccountMetaSlice = nil + buf := new(bytes.Buffer) + err := encodeT(*params, buf) + ag_require.NoError(t, err) + got := new(Storeval) + err = decodeT(got, buf.Bytes()) + got.AccountMetaSlice = nil + ag_require.NoError(t, err) + ag_require.Equal(t, params, got) + } + }) + } +} diff --git a/contracts/generated/contract_reader_interface/accounts.go b/contracts/generated/contract_reader_interface/accounts.go index 71a1dfc91..c6b449d32 100644 --- a/contracts/generated/contract_reader_interface/accounts.go +++ b/contracts/generated/contract_reader_interface/accounts.go @@ -13,6 +13,7 @@ type LookupTableDataAccount struct { Administrator ag_solanago.PublicKey PendingAdministrator ag_solanago.PublicKey LookupTable ag_solanago.PublicKey + Bump uint8 } var LookupTableDataAccountDiscriminator = [8]byte{220, 119, 44, 40, 237, 41, 223, 7} @@ -43,6 +44,11 @@ func (obj LookupTableDataAccount) MarshalWithEncoder(encoder *ag_binary.Encoder) if err != nil { return err } + // Serialize `Bump` param: + err = encoder.Encode(obj.Bump) + if err != nil { + return err + } return nil } @@ -80,6 +86,11 @@ func (obj *LookupTableDataAccount) UnmarshalWithDecoder(decoder *ag_binary.Decod if err != nil { return err } + // Deserialize `Bump`: + err = decoder.Decode(&obj.Bump) + if err != nil { + return err + } return nil } diff --git a/contracts/generated/contract_reader_interface/instructions.go b/contracts/generated/contract_reader_interface/instructions.go index cb9a19736..43c01a787 100644 --- a/contracts/generated/contract_reader_interface/instructions.go +++ b/contracts/generated/contract_reader_interface/instructions.go @@ -36,7 +36,9 @@ var ( Instruction_Initializetokenprices = ag_binary.TypeID([8]byte{48, 105, 228, 116, 187, 196, 252, 244}) - Instruction_InitializeLookupTable = ag_binary.TypeID([8]byte{149, 120, 10, 249, 212, 185, 177, 216}) + Instruction_Initializelookuptable = ag_binary.TypeID([8]byte{20, 175, 141, 85, 140, 232, 205, 25}) + + Instruction_Storeval = ag_binary.TypeID([8]byte{182, 242, 86, 70, 26, 41, 111, 133}) Instruction_Store = ag_binary.TypeID([8]byte{220, 28, 207, 235, 0, 234, 193, 246}) ) @@ -52,8 +54,10 @@ func InstructionIDToName(id ag_binary.TypeID) string { return "Initializemultireadwithparams" case Instruction_Initializetokenprices: return "Initializetokenprices" - case Instruction_InitializeLookupTable: - return "InitializeLookupTable" + case Instruction_Initializelookuptable: + return "Initializelookuptable" + case Instruction_Storeval: + return "Storeval" case Instruction_Store: return "Store" default: @@ -89,7 +93,10 @@ var InstructionImplDef = ag_binary.NewVariantDefinition( "initializetokenprices", (*Initializetokenprices)(nil), }, { - "initialize_lookup_table", (*InitializeLookupTable)(nil), + "initializelookuptable", (*Initializelookuptable)(nil), + }, + { + "storeval", (*Storeval)(nil), }, { "store", (*Store)(nil), diff --git a/contracts/programs/contract-reader-interface/src/lib.rs b/contracts/programs/contract-reader-interface/src/lib.rs index b832743ba..0acbd1d9f 100644 --- a/contracts/programs/contract-reader-interface/src/lib.rs +++ b/contracts/programs/contract-reader-interface/src/lib.rs @@ -66,7 +66,7 @@ pub mod contract_reader_interface { Ok(()) } - pub fn initialize_lookup_table( + pub fn initializelookuptable( ctx: Context, lookup_table: Pubkey, ) -> Result<()> { @@ -75,10 +75,19 @@ pub mod contract_reader_interface { account.administrator = ctx.accounts.admin.key(); account.pending_administrator = Pubkey::default(); account.lookup_table = lookup_table; + account.bump = ctx.bumps.write_data_account; Ok(()) } + pub fn storeval(ctx: Context, test_idx: u64, value: u64) -> Result<()> { + let data = &mut ctx.accounts.data; + data.bump = ctx.bumps.data; + data.idx = test_idx; + data.u64_value = value; + Ok(()) + } + pub fn store(ctx: Context, test_idx: u64, data: TestStructData) -> Result<()> { let test_struct_account = &mut ctx.accounts.test_struct.load_init()?; @@ -202,21 +211,22 @@ pub struct InitializeBillingTokenConfigWrapperOnce<'info> { } #[derive(Accounts)] +#[instruction(test_idx: u64)] pub struct InitializeLookupTableData<'info> { + /// Admin account that pays for PDA creation and signs the transaction + #[account(mut)] + pub admin: Signer<'info>, + /// PDA for LookupTableDataAccount, derived from seeds and created by the System Program #[account( init_if_needed, payer = admin, space = size_of::() + 8, - seeds = [b"data"], + seeds = [b"lookup".as_ref()], bump )] pub write_data_account: Account<'info, LookupTableDataAccount>, - /// Admin account that pays for PDA creation and signs the transaction - #[account(mut)] - pub admin: Signer<'info>, - /// System Program required for PDA creation pub system_program: Program<'info, System>, } @@ -239,12 +249,31 @@ pub struct StoreTestStruct<'info> { pub system_program: Program<'info, System>, } +#[derive(Accounts)] +#[instruction(test_idx: u64)] +pub struct StoreVal<'info> { + /// Admin account that pays for PDA creation and signs the transaction + #[account(mut)] + pub admin: Signer<'info>, + + // derived test PDA + #[account( + mut, + seeds=[b"data".as_ref(), test_idx.to_le_bytes().as_ref()], + bump)] + pub data: Account<'info, DataAccount>, + + /// System Program required for PDA creation + pub system_program: Program<'info, System>, +} + #[account] pub struct LookupTableDataAccount { pub version: u8, // Version of the data account pub administrator: Pubkey, // Administrator public key pub pending_administrator: Pubkey, // Pending administrator public key pub lookup_table: Pubkey, // Address of the lookup table + pub bump: u8, } #[account] diff --git a/integration-tests/relayinterface/chain_components_test.go b/integration-tests/relayinterface/chain_components_test.go index 2d4cd6981..4bfb7ffbf 100644 --- a/integration-tests/relayinterface/chain_components_test.go +++ b/integration-tests/relayinterface/chain_components_test.go @@ -7,6 +7,7 @@ import ( "context" "encoding/binary" "encoding/json" + "fmt" "math/big" "os" "path/filepath" @@ -18,6 +19,7 @@ import ( "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" "github.com/gagliardetto/solana-go/rpc/ws" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -31,6 +33,7 @@ import ( . "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" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-common/pkg/values" contractprimary "github.com/smartcontractkit/chainlink-solana/contracts/generated/contract_reader_interface" contractsecondary "github.com/smartcontractkit/chainlink-solana/contracts/generated/contract_reader_interface_secondary" @@ -184,7 +187,7 @@ func RunChainComponentsSolanaTests[T WrappedTestingT[T]](t T, it *SolanaChainCom RunTests(t, it, []Testcase[T]{testCases}) RunContractReaderTests(t, it) - // Add ChainWriter tests here + RunChainWriterTests(t, it) } func RunChainComponentsInLoopSolanaTests[T WrappedTestingT[T]](t T, it ChainComponentsInterfaceTester[T]) { @@ -196,6 +199,66 @@ func RunContractReaderTests[T WrappedTestingT[T]](t T, it *SolanaChainComponents RunContractReaderInterfaceTests(t, it, false, true) } +func RunChainWriterTests[T WrappedTestingT[T]](t T, it *SolanaChainComponentsInterfaceTester[T]) { + testCases := []Testcase[T]{ + { + Name: ChainWriterLookupTableTest, + Test: func(t T) { + cr := it.GetContractReader(t) + cw := it.GetContractWriter(t) + contracts := it.GetBindings(t) + + idx := it.getTestIdx(t.Name()) + ctx := tests.Context(t) + bound := BindingsByName(contracts, AnyContractName)[0] + require.NoError(t, cr.Bind(ctx, contracts)) + + testIdx := binary.LittleEndian.AppendUint64([]byte{}, idx) + dataPDAAccount, _, err := solana.FindProgramAddress([][]byte{[]byte("data"), testIdx}, solana.MustPublicKeyFromBase58(bound.Address)) + require.NoError(t, err) + fmt.Println("Data PDA Account", dataPDAAccount.Bytes()) + + // append random addresses to lookup table address list + lookupTableAddresses := make([]solana.PublicKey, 0, 10) + for i := 0; i < 9; i++ { + pk, pkErr := solana.NewRandomPrivateKey() + require.NoError(t, pkErr) + lookupTableAddresses = append(lookupTableAddresses, pk.PublicKey()) + } + + lookupTableAddresses = append(lookupTableAddresses, dataPDAAccount) + + lookupTableAddr := CreateTestLookupTable(ctx, t, it.Helper.SolanaClient(), *it.Helper.TXM(), it.Helper.Sender(), lookupTableAddresses) + fmt.Println("lookup table address", lookupTableAddr.String()) + initLookupTableArgs := LookupTableArgs{ + LookupTable: lookupTableAddr, + } + + SubmitTransactionToCW(t, it, cw, "initializeLookupTable", initLookupTableArgs, bound, types.Finalized) + + dataValue := uint64(1) + storeValArgs := DataAccountArgs{ + TestIdx: idx, + Value: dataValue, + } + SubmitTransactionToCW(t, it, cw, "storeVal", storeValArgs, bound, types.Finalized) + + var value values.Value + err = cr.GetLatestValue(ctx, bound.ReadIdentifier(MethodReturningUint64), primitives.Unconfirmed, nil, &value) + require.NoError(t, err) + + var prim uint64 + err = value.UnwrapTo(&prim) + require.NoError(t, err) + + assert.Equal(t, dataValue, prim) + }, + }, + } + + RunTests(t, it, testCases) +} + // GetLatestValue method const ( ContractReaderGetLatestValueUsingMultiReader = "Get latest value using multi reader" @@ -203,6 +266,7 @@ const ( ContractReaderGetLatestValueWithAddressHardcodedIntoResponse = "Get latest value with AddressHardcoded into response" ContractReaderGetLatestValueUsingMultiReaderWithParmsReuse = "Get latest value using multi reader with params reuse" ContractReaderGetLatestValueGetTokenPrices = "Get latest value handles get token prices edge case" + ChainWriterLookupTableTest = "Set contract value using a lookup table for addresses" ) type TimestampedUnixBig struct { @@ -390,6 +454,7 @@ type SolanaChainComponentsInterfaceTesterHelper[T WrappedTestingT[T]] interface CreateAccount(t T, it SolanaChainComponentsInterfaceTester[T], contractName string, value uint64, testStruct TestStruct) solana.PublicKey TXM() *txm.TxManager SolanaClient() *client.Client + Sender() solana.PrivateKey } type WrappedTestingT[T any] interface { @@ -510,6 +575,7 @@ type helper struct { secondaryIdlBts []byte txm txm.TxManager sc *client.Client + sender solana.PrivateKey } func (h *helper) Init(t *testing.T) { @@ -517,6 +583,7 @@ func (h *helper) Init(t *testing.T) { privateKey, err := solana.PrivateKeyFromBase58(solclient.DefaultPrivateKeysSolValidator[1]) require.NoError(t, err) + h.sender = privateKey h.rpcURL, h.wsURL = utils.SetupTestValidatorWithAnchorPrograms(t, privateKey.PublicKey().String(), []string{"contract-reader-interface", "contract-reader-interface-secondary"}) h.wsClient, err = ws.Connect(tests.Context(t), h.wsURL) @@ -641,11 +708,19 @@ func (h *helper) CreateAccount(t *testing.T, it SolanaChainComponentsInterfaceTe return programID } -type InitializeArgs struct { +func (h *helper) Sender() solana.PrivateKey { + return h.sender +} + +type DataAccountArgs struct { TestIdx uint64 Value uint64 } +type LookupTableArgs struct { + LookupTable solana.PublicKey +} + type StoreStructArgs struct { TestIdx uint64 Data TestStruct @@ -671,7 +746,7 @@ func (h *helper) runInitialize( return } - initArgs := InitializeArgs{ + initArgs := DataAccountArgs{ TestIdx: testIdx, Value: value, } @@ -1087,6 +1162,91 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T }, DebugIDLocation: "", }, + "initializeLookupTable": { + FromAddress: fromAddress, + InputModifications: nil, + ChainSpecificName: "initializelookuptable", + LookupTables: chainwriter.LookupTables{}, + Accounts: []chainwriter.Lookup{ + chainwriter.AccountConstant{ + Name: "Signer", + Address: fromAddress, + IsSigner: true, + IsWritable: true, + }, + chainwriter.PDALookups{ + Name: "Account", + PublicKey: chainwriter.AccountConstant{ + Address: primaryProgramPubKey, + }, + Seeds: []chainwriter.Seed{ + {Static: []byte("lookup")}, + }, + IsWritable: true, + IsSigner: false, + }, + chainwriter.AccountConstant{ + Name: "SystemProgram", + Address: solana.SystemProgramID.String(), + IsWritable: false, + IsSigner: false, + }, + }, + DebugIDLocation: "", + }, + "storeVal": { + FromAddress: fromAddress, + InputModifications: nil, + ChainSpecificName: "storeval", + LookupTables: chainwriter.LookupTables{ + DerivedLookupTables: []chainwriter.DerivedLookupTable{ + { + Name: "LookupTable", + Accounts: chainwriter.PDALookups{ + Name: "LookupTableAccount", + PublicKey: chainwriter.AccountConstant{ + Address: primaryProgramPubKey, + }, + Seeds: []chainwriter.Seed{ + {Static: []byte("lookup")}, + }, + InternalField: chainwriter.InternalField{ + TypeName: "LookupTableDataAccount", + Location: "LookupTable", + IDL: string(it.Helper.GetPrimaryIDL(t)), + }, + }, + }, + }, + }, + Accounts: []chainwriter.Lookup{ + chainwriter.AccountConstant{ + Name: "Signer", + Address: fromAddress, + IsSigner: true, + IsWritable: true, + }, + chainwriter.PDALookups{ + Name: "Account", + PublicKey: chainwriter.AccountConstant{ + Address: primaryProgramPubKey, + }, + Seeds: []chainwriter.Seed{ + {Static: []byte("data")}, + {Static: testIdx}, + }, + IsWritable: true, + IsSigner: false, + }, + chainwriter.AccountConstant{ + Name: "SystemProgram", + Address: solana.SystemProgramID.String(), + IsWritable: false, + IsSigner: false, + }, + }, + DebugIDLocation: "", + }, MethodSettingStruct: { FromAddress: fromAddress, InputModifications: []commoncodec.ModifierConfig{ @@ -1248,6 +1408,56 @@ func mustUnmarshalIDL[T WrappedTestingT[T]](t T, rawIDL string) codec.IDL { return idl } +func CreateTestLookupTable[T WrappedTestingT[T]](ctx context.Context, t T, c *client.Client, txm txm.TxManager, sender solana.PrivateKey, addresses []solana.PublicKey) solana.PublicKey { + // Create lookup tables + slot, serr := c.SlotHeightWithCommitment(ctx, rpc.CommitmentFinalized) + require.NoError(t, serr) + table, createTableInstruction, ierr := utils.NewCreateLookupTableInstruction( + sender.PublicKey(), + sender.PublicKey(), + slot, + ) + require.NoError(t, ierr) + res, err := c.LatestBlockhash(ctx) + require.NoError(t, err) + + tx1, err1 := solana.NewTransaction([]solana.Instruction{createTableInstruction}, res.Value.Blockhash) + require.NoError(t, err1) + txID1 := uuid.NewString() + err = txm.Enqueue(ctx, "", tx1, &txID1, res.Value.LastValidBlockHeight) + require.NoError(t, err) + pollTxStatusTillCommitment(ctx, t, txm, txID1, types.Finalized) + + res, err = c.LatestBlockhash(ctx) + require.NoError(t, err) + + addEntriesInstruction := utils.NewExtendLookupTableInstruction(table, sender.PublicKey(), sender.PublicKey(), addresses) + tx2, err2 := solana.NewTransaction([]solana.Instruction{addEntriesInstruction}, res.Value.Blockhash) + require.NoError(t, err2) + txID2 := uuid.NewString() + err = txm.Enqueue(ctx, "", tx2, &txID2, res.Value.LastValidBlockHeight) + require.NoError(t, err) + pollTxStatusTillCommitment(ctx, t, txm, txID2, types.Finalized) + + return table +} + +func pollTxStatusTillCommitment[T WrappedTestingT[T]](ctx context.Context, t T, txm txm.TxManager, txID string, targetStatus types.TransactionStatus) { + var txStatus types.TransactionStatus + count := 0 + for txStatus != targetStatus && txStatus != types.Finalized { + count++ + status, err := txm.GetTransactionStatus(ctx, txID) + if err == nil { + txStatus = status + } + time.Sleep(100 * time.Millisecond) + if count > 500 { + require.NoError(t, fmt.Errorf("unable to find transaction within timeout")) + } + } +} + const ( primaryProgramPubKey = "6AfuXF6HapDUhQfE4nQG9C1SGtA1YjP3icaJyRfU4RyE" secondaryProgramPubKey = "9SFyk8NmGYh5D612mJwUYhguCRY9cFgaS2vksrigepjf" diff --git a/integration-tests/relayinterface/lookups_test.go b/integration-tests/relayinterface/lookups_test.go index c027af488..72958962b 100644 --- a/integration-tests/relayinterface/lookups_test.go +++ b/integration-tests/relayinterface/lookups_test.go @@ -601,7 +601,7 @@ func TestLookupTables(t *testing.T) { chainwriter.InitializeDataAccount(ctx, t, rpcClient, programID, sender, lookupTable) args := map[string]interface{}{ - "seed1": []byte("data"), + "seed1": []byte("lookup"), } lookupConfig := chainwriter.LookupTables{ diff --git a/pkg/solana/chainwriter/chain_writer.go b/pkg/solana/chainwriter/chain_writer.go index 2d6c8d8de..91a0cdc44 100644 --- a/pkg/solana/chainwriter/chain_writer.go +++ b/pkg/solana/chainwriter/chain_writer.go @@ -187,33 +187,36 @@ func (s *SolanaChainWriterService) FilterLookupTableAddresses( continue } - // Collect public keys that are actually used - var usedAddresses solana.PublicKeySlice + tableAddresses := make(solana.PublicKeySlice, 0, len(metas)) + foundUsedAddress := false + // Parse metas into public keys for filtered lookup table map for _, meta := range metas { + tableAddresses = append(tableAddresses, meta.PublicKey) if _, exists := usedAccounts[meta.PublicKey.String()]; exists { - usedAddresses = append(usedAddresses, meta.PublicKey) + foundUsedAddress = true } } - // Add to the filtered map if there are any used addresses - if len(usedAddresses) > 0 { - filteredLookupTables[tableKey] = usedAddresses + // Add lookup table to the filtered map if it contains an address used for the tx + if foundUsedAddress { + filteredLookupTables[tableKey] = tableAddresses } } } // Filter static lookup tables for tableKey, addresses := range staticTableMap { - var usedAddresses solana.PublicKeySlice + foundUsedAddress := false for _, staticAddress := range addresses { if _, exists := usedAccounts[staticAddress.String()]; exists { - usedAddresses = append(usedAddresses, staticAddress) + foundUsedAddress = true + break } } - // Add to the filtered map if there are any used addresses - if len(usedAddresses) > 0 { - filteredLookupTables[tableKey] = usedAddresses + // Add lookup table to the filtered map if it contains an address used for the tx + if foundUsedAddress { + filteredLookupTables[tableKey] = addresses } } diff --git a/pkg/solana/chainwriter/chain_writer_test.go b/pkg/solana/chainwriter/chain_writer_test.go index 8631c2fe2..8654f5260 100644 --- a/pkg/solana/chainwriter/chain_writer_test.go +++ b/pkg/solana/chainwriter/chain_writer_test.go @@ -406,7 +406,8 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { lookupTablePubkey := mockDataAccountLookupTable(t, rw, pda1) // mock fetch lookup table addresses call storedPubKey := chainwriter.GetRandomPubKey(t) - mockFetchLookupTableAddresses(t, rw, lookupTablePubkey, []solana.PublicKey{storedPubKey}) + unusedKeys := chainwriter.CreateTestPubKeys(t, 2) + mockFetchLookupTableAddresses(t, rw, lookupTablePubkey, append([]solana.PublicKey{storedPubKey}, unusedKeys...)) unusedProgramID := chainwriter.GetRandomPubKey(t) seed2 := []byte("seed2") @@ -414,8 +415,8 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { // mock data account response from program unusedLookupTable := mockDataAccountLookupTable(t, rw, unusedPda) // mock fetch lookup table addresses call - unusedKeys := chainwriter.GetRandomPubKey(t) - mockFetchLookupTableAddresses(t, rw, unusedLookupTable, []solana.PublicKey{unusedKeys}) + unusedKeys = chainwriter.CreateTestPubKeys(t, 2) + mockFetchLookupTableAddresses(t, rw, unusedLookupTable, unusedKeys) // mock static lookup table calls staticLookupTablePubkey1 := chainwriter.GetRandomPubKey(t) @@ -470,7 +471,7 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { Seed2: seed2, } - t.Run("returns filtered map with only relevant addresses required by account lookup config", func(t *testing.T) { + t.Run("returns filtered map with only relevant lookup tables required by account lookup config", func(t *testing.T) { accountLookupConfig := []chainwriter.Lookup{ chainwriter.AccountsFromLookupTable{ LookupTableName: "DerivedTable", @@ -493,7 +494,7 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { require.Len(t, filteredLookupTableMap, len(accounts)) entry, exists := filteredLookupTableMap[lookupTablePubkey] require.True(t, exists) - require.Len(t, entry, 1) + require.Len(t, entry, 3) require.Equal(t, storedPubKey, entry[0]) }) diff --git a/pkg/solana/chainwriter/helpers.go b/pkg/solana/chainwriter/helpers.go index c2fa5b832..2084249c1 100644 --- a/pkg/solana/chainwriter/helpers.go +++ b/pkg/solana/chainwriter/helpers.go @@ -172,18 +172,18 @@ func InitializeDataAccount( admin solana.PrivateKey, lookupTable solana.PublicKey, ) { - pda, _, err := solana.FindProgramAddress([][]byte{[]byte("data")}, programID) + pda, _, err := solana.FindProgramAddress([][]byte{[]byte("lookup")}, programID) require.NoError(t, err) - discriminator := GetDiscriminator("initialize_lookup_table") + discriminator := GetDiscriminator("initializelookuptable") instructionData := append(discriminator[:], lookupTable.Bytes()...) instruction := solana.NewInstruction( programID, solana.AccountMetaSlice{ - solana.Meta(pda).WRITE(), solana.Meta(admin.PublicKey()).SIGNER().WRITE(), + solana.Meta(pda).WRITE(), solana.Meta(solana.SystemProgramID), }, instructionData, From a95bf7c67c223cc426309b62badaf9c5d25f233d Mon Sep 17 00:00:00 2001 From: ilija42 <57732589+ilija42@users.noreply.github.com> Date: Thu, 13 Feb 2025 02:47:24 +0100 Subject: [PATCH 3/4] Fix get token prices read handling (#1075) --- .../relayinterface/chain_components_test.go | 46 +++++---- pkg/solana/chainreader/chain_reader.go | 93 +++++++++++-------- pkg/solana/codec/anchoridl.go | 8 +- pkg/solana/codec/solana.go | 11 +++ 4 files changed, 96 insertions(+), 62 deletions(-) diff --git a/integration-tests/relayinterface/chain_components_test.go b/integration-tests/relayinterface/chain_components_test.go index 4bfb7ffbf..e2d76f90f 100644 --- a/integration-tests/relayinterface/chain_components_test.go +++ b/integration-tests/relayinterface/chain_components_test.go @@ -799,19 +799,19 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractReaderConfig(t T ResponseAddressHardCoder: &commoncodec.HardCodeModifierConfig{ // placeholder values, whatever is put as value gets replaced with a solana pub key anyway OffChainValues: map[string]any{ - "SharedAddress": solana.PublicKey{}, - "AddressToShare": solana.PublicKey{}, - }, - }, - OutputModifications: commoncodec.ModifiersConfig{ - &commoncodec.HardCodeModifierConfig{ - OffChainValues: map[string]any{"U": "", "V": false}, + "SharedAddress": "", + "AddressToShare": "", }, }, } multiReadDef := readWithAddressHardCodedIntoResponseDef multiReadDef.ResponseAddressHardCoder = nil + multiReadDef.OutputModifications = commoncodec.ModifiersConfig{ + &commoncodec.HardCodeModifierConfig{ + OffChainValues: map[string]any{"U": "", "V": false}, + }, + } multiReadDef.MultiReader = &config.MultiReader{ Reads: []config.ReadDefinition{{ ChainSpecificName: "MultiRead2", @@ -820,14 +820,31 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractReaderConfig(t T }}, } + idl := mustUnmarshalIDL(t, string(it.Helper.GetPrimaryIDL(t))) + idl.Accounts = append(idl.Accounts, codec.IdlTypeDef{ + Name: "USDPerToken", + Type: codec.IdlTypeDefTy{ + Kind: codec.IdlTypeDefTyKindStruct, + Fields: &codec.IdlTypeDefStruct{ + { + Name: "tokenPrices", + Type: codec.IdlType{ + AsIdlTypeVec: &codec.IdlTypeVec{Vec: codec.IdlType{AsIdlTypeDefined: &codec.IdlTypeDefined{Defined: "TimestampedPackedU224"}}}, + }, + }, + }, + }, + }) + return config.ContractReader{ Namespaces: map[string]config.ChainContractReader{ AnyContractName: { - IDL: mustUnmarshalIDL(t, string(it.Helper.GetPrimaryIDL(t))), + IDL: idl, Reads: map[string]config.ReadDefinition{ ReadWithAddressHardCodedIntoResponse: readWithAddressHardCodedIntoResponseDef, GetTokenPrices: { - ChainSpecificName: "BillingTokenConfigWrapper", + ChainSpecificName: "USDPerToken", + ReadType: config.Account, PDADefinition: codec.PDATypeDef{ Prefix: []byte("fee_billing_token_config"), Seeds: []codec.PDASeed{ @@ -842,17 +859,8 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractReaderConfig(t T }, }, OutputModifications: commoncodec.ModifiersConfig{ - &commoncodec.DropModifierConfig{ - Fields: []string{"Config"}, - }, - &commoncodec.HardCodeModifierConfig{ - OffChainValues: map[string]any{ - "Response": make([]TimestampedUnixBig, 1000), - }, - }, - &commoncodec.PropertyExtractorConfig{FieldName: "Response"}, + &commoncodec.PropertyExtractorConfig{FieldName: "TokenPrices"}, }, - ReadType: config.Account, }, MultiRead: multiReadDef, MultiReadWithParamsReuse: { diff --git a/pkg/solana/chainreader/chain_reader.go b/pkg/solana/chainreader/chain_reader.go index b9b0b0991..38d859206 100644 --- a/pkg/solana/chainreader/chain_reader.go +++ b/pkg/solana/chainreader/chain_reader.go @@ -187,7 +187,10 @@ func (s *ContractReaderService) GetLatestValue(ctx context.Context, readIdentifi // TODO this is a temporary edge case - NONEVM-1320 if values.reads[0].readName == GetTokenPrices { - return s.handleGetTokenPricesGetLatestValue(ctx, params, values, returnVal) + if err := s.handleGetTokenPricesGetLatestValue(ctx, params, values, returnVal); err != nil { + return fmt.Errorf("failed to read contract: %q, account: %q err: %w", values.contract, values.reads[0].readName, err) + } + return nil } batch := []call{ @@ -635,7 +638,14 @@ func (s *ContractReaderService) handleGetTokenPricesGetLatestValue( params any, values readValues, returnVal any, -) error { +) (err error) { + // shouldn't happen, but just to be sure + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic recovered: %v", r) + } + }() + pdaAddresses, err := s.getPDAsForGetTokenPrices(params, values) if err != nil { return err @@ -643,64 +653,69 @@ func (s *ContractReaderService) handleGetTokenPricesGetLatestValue( data, err := s.client.GetMultipleAccountData(ctx, pdaAddresses...) if err != nil { - return fmt.Errorf( - "for contract %q read %q: failed to get multiple account data: %w", - values.contract, values.reads[0].readName, err, - ) + return err } - // -------------- Fill out the returnVal slice with data -------------- - // can't typecast returnVal so we have to use reflection here - - // Ensure `returnVal` is a pointer to a slice we can populate. returnSliceVal := reflect.ValueOf(returnVal) - if returnSliceVal.Kind() == reflect.Ptr { + if returnSliceVal.Kind() != reflect.Ptr { + return fmt.Errorf("expected <**[]*struct { Value *big.Int; Timestamp *int64 } Value>, got %q", returnSliceVal.String()) + } + returnSliceVal = returnSliceVal.Elem() + + returnSliceValType := returnSliceVal.Type() + if returnSliceValType.Kind() != reflect.Ptr { + return fmt.Errorf("expected <*[]*struct { Value *big.Int; Timestamp *int64 } Value>, got %q", returnSliceValType.String()) + } + + sliceType := returnSliceValType.Elem() + if sliceType.Kind() != reflect.Slice { + return fmt.Errorf("expected []*struct { Value *big.Int; Timestamp *int64 }, got %q", sliceType.String()) + } + + if returnSliceVal.IsNil() { + // init a slice + sliceVal := reflect.MakeSlice(sliceType, 0, 0) + + // create a pointer to that slice to match what slicePtr + slicePtr := reflect.New(sliceType) + slicePtr.Elem().Set(sliceVal) + + returnSliceVal.Set(slicePtr) returnSliceVal = returnSliceVal.Elem() - if returnSliceVal.Kind() == reflect.Ptr { - returnSliceVal = returnSliceVal.Elem() - } } - if returnSliceVal.Kind() != reflect.Slice { - return fmt.Errorf( - "for contract %q read %q: expected `returnVal` to be a slice, got %s", - values.contract, values.reads[0].readName, returnSliceVal.Kind(), - ) + + pointerType := sliceType.Elem() + if pointerType.Kind() != reflect.Ptr { + return fmt.Errorf("expected *struct { Value *big.Int; Timestamp *int64 }, got %q", pointerType.String()) + } + + underlyingStruct := pointerType.Elem() + if underlyingStruct.Kind() != reflect.Struct { + return fmt.Errorf("expected struct { Value *big.Int; Timestamp *int64 }, got %q", underlyingStruct.String()) } - elemType := returnSliceVal.Type().Elem() for _, d := range data { var wrapper fee_quoter.BillingTokenConfigWrapper if err = wrapper.UnmarshalWithDecoder(bin.NewBorshDecoder(d)); err != nil { - return fmt.Errorf( - "for contract %q read %q: failed to unmarshal account data: %w", - values.contract, values.reads[0].readName, err, - ) + return err } - newElem := reflect.New(elemType).Elem() - + newElemPtr := reflect.New(underlyingStruct) + newElem := newElemPtr.Elem() valueField := newElem.FieldByName("Value") if !valueField.IsValid() { - return fmt.Errorf( - "for contract %q read %q: struct type missing `Value` field", - values.contract, values.reads[0].readName, - ) + return fmt.Errorf("field `Value` missing from %q", newElem.String()) } - valueField.Set(reflect.ValueOf(big.NewInt(0).SetBytes(wrapper.Config.UsdPerToken.Value[:]))) + valueField.Set(reflect.ValueOf(big.NewInt(0).SetBytes(wrapper.Config.UsdPerToken.Value[:]))) timestampField := newElem.FieldByName("Timestamp") if !timestampField.IsValid() { - return fmt.Errorf( - "for contract %q read %q: struct type missing `Timestamp` field", - values.contract, values.reads[0].readName, - ) + return fmt.Errorf("field `Timestamp` missing from %q", newElem.String()) } - // nolint:gosec // G115: integer overflow conversion int64 -> uint32 - timestampField.Set(reflect.ValueOf(uint32(wrapper.Config.UsdPerToken.Timestamp))) - - returnSliceVal.Set(reflect.Append(returnSliceVal, newElem)) + timestampField.Set(reflect.ValueOf(&wrapper.Config.UsdPerToken.Timestamp)) + returnSliceVal.Set(reflect.Append(returnSliceVal, newElemPtr)) } return nil diff --git a/pkg/solana/codec/anchoridl.go b/pkg/solana/codec/anchoridl.go index ceac3fb3b..5bdb38b2f 100644 --- a/pkg/solana/codec/anchoridl.go +++ b/pkg/solana/codec/anchoridl.go @@ -272,7 +272,7 @@ func (env *IdlType) UnmarshalJSON(data []byte) error { if err := utilz.TranscodeJSON(temp, &target); err != nil { return err } - env.asIdlTypeDefined = &target + env.AsIdlTypeDefined = &target } if got, ok := v["array"]; ok { if _, ok := got.([]interface{}); !ok { @@ -303,7 +303,7 @@ type IdlType struct { AsString IdlTypeAsString AsIdlTypeVec *IdlTypeVec asIdlTypeOption *IdlTypeOption - asIdlTypeDefined *IdlTypeDefined + AsIdlTypeDefined *IdlTypeDefined AsIdlTypeArray *IdlTypeArray } @@ -323,7 +323,7 @@ func (env *IdlType) IsIdlTypeOption() bool { return env.asIdlTypeOption != nil } func (env *IdlType) IsIdlTypeDefined() bool { - return env.asIdlTypeDefined != nil + return env.AsIdlTypeDefined != nil } func (env *IdlType) IsArray() bool { return env.AsIdlTypeArray != nil @@ -340,7 +340,7 @@ func (env *IdlType) GetIdlTypeOption() *IdlTypeOption { return env.asIdlTypeOption } func (env *IdlType) GetIdlTypeDefined() *IdlTypeDefined { - return env.asIdlTypeDefined + return env.AsIdlTypeDefined } func (env *IdlType) GetArray() *IdlTypeArray { return env.AsIdlTypeArray diff --git a/pkg/solana/codec/solana.go b/pkg/solana/codec/solana.go index 912847fd9..b16e66c44 100644 --- a/pkg/solana/codec/solana.go +++ b/pkg/solana/codec/solana.go @@ -350,6 +350,17 @@ func asDefined(parentTypeName string, definedName *IdlTypeDefined, refs *codecRe } func asArray(parentTypeName string, idlArray *IdlTypeArray, refs *codecRefs) (commonencodings.TypeCodec, error) { + if idlArray == nil { + return nil, fmt.Errorf("%w: field type cannot be nil", commontypes.ErrInvalidConfig) + } + + // better to implement bytes to big int codec modifiers, but this works fine + if idlArray.Num == 28 && idlArray.Thing.AsString == IdlTypeU8 { + // nolint:gosec + // G115: integer overflow conversion int -> uint + return binary.BigEndian().BigInt(uint(idlArray.Num), false) + } + codec, err := processFieldType(parentTypeName, idlArray.Thing, refs) if err != nil { return nil, err From e727e73f618151117f21ef1556fcc456ecf7bdea Mon Sep 17 00:00:00 2001 From: amit-momin <108959691+amit-momin@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:52:59 -0600 Subject: [PATCH 4/4] Add json tags and enable serialization for ChainWriter config (#1072) * Added JSON tags to ChainWriter config fields * Updated LookupOpts json tag * Updated Lookup to be a struct instead of an interface * added lookup validation * Fixed interface test configs * Pinned chainlink dependency to PR that fixes breaking changes * Tidied go mod --------- Co-authored-by: Silas Lenihan --- integration-tests/go.mod | 7 +- integration-tests/go.sum | 10 +- .../relayinterface/chain_components_test.go | 182 ++++++++-------- .../relayinterface/lookups_test.go | 141 +++++++------ pkg/solana/chainwriter/chain_writer.go | 22 +- pkg/solana/chainwriter/chain_writer_test.go | 199 +++++++++--------- pkg/solana/chainwriter/lookups.go | 124 ++++++----- pkg/solana/chainwriter/transform_registry.go | 14 +- 8 files changed, 375 insertions(+), 324 deletions(-) diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 5a7fb344f..97333b980 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -15,12 +15,12 @@ require ( github.com/pelletier/go-toml/v2 v2.2.3 github.com/rs/zerolog v1.33.0 github.com/smartcontractkit/chainlink-common v0.4.2-0.20250205141137-8f50d72601bb - github.com/smartcontractkit/chainlink-solana v1.1.2-0.20250211163416-98a5dc9e48d7 + github.com/smartcontractkit/chainlink-solana v1.1.2-0.20250213004451-65651ca8b78b github.com/smartcontractkit/chainlink-testing-framework/lib v1.51.0 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.10 github.com/smartcontractkit/chainlink/deployment v0.0.0-20250211163809-1d69f4b097db github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20250211163809-1d69f4b097db - github.com/smartcontractkit/chainlink/v2 v2.19.0-ccip1.5.16-alpha.0.0.20250211163809-1d69f4b097db + github.com/smartcontractkit/chainlink/v2 v2.19.0-ccip1.5.16-alpha.0.0.20250213004954-c684df5dc2eb github.com/smartcontractkit/libocr v0.0.0-20241223215956-e5b78d8e3919 github.com/stretchr/testify v1.10.0 github.com/testcontainers/testcontainers-go v0.35.0 @@ -326,7 +326,7 @@ require ( github.com/slack-go/slack v0.15.0 // indirect github.com/smartcontractkit/chain-selectors v1.0.40 // indirect github.com/smartcontractkit/chainlink-automation v0.8.1 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20250207152018-27a940edbb09 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20250212131315-e9b53b05b02a // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250206215114-fb6c3c35e8e3 // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20250128203428-08031923fbe5 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect @@ -334,6 +334,7 @@ require ( github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250211162441-3d6cea220efb // indirect github.com/smartcontractkit/chainlink-integrations/evm v0.0.0-20250211163724-89a32c016ce4 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 // indirect + github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6 // indirect github.com/smartcontractkit/chainlink-protos/svr v0.0.0-20250123084029-58cce9b32112 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20241009055228-33d0c0bf38de // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index cb16b4e4c..73a207962 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1100,8 +1100,8 @@ github.com/smartcontractkit/chain-selectors v1.0.40 h1:iLvvoZeehVq6/7F+zzolQLF0D github.com/smartcontractkit/chain-selectors v1.0.40/go.mod h1:xsKM0aN3YGcQKTPRPDDtPx2l4mlTN1Djmg0VVXV40b8= github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgBc2xpDKBco/Q4h4ydl6+UUU= github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250207152018-27a940edbb09 h1:eQLVvVPKru0szeApqNymCOpLHNWnndllxaDUZhapF/A= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250207152018-27a940edbb09/go.mod h1:UEnHaxkUsfreeA7rR45LMmua1Uen95tOFUR8/AI9BAo= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250212131315-e9b53b05b02a h1:iciffyGz76BiyILT8WSHePGr2C9v5PYK+PKwcW5ETLw= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250212131315-e9b53b05b02a/go.mod h1:Hht/OJq/PxC+gnBCIPyzHt4Otsw6mYwUVsmtOqIvlxo= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250206215114-fb6c3c35e8e3 h1:f4F/7OCuMybsPKKXXvLQz+Q1hGq07I1cfoWy5EA9iRg= github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250206215114-fb6c3c35e8e3/go.mod h1:Bmwq4lNb5tE47sydN0TKetcLEGbgl+VxHEWp4S0LI60= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250205141137-8f50d72601bb h1:1VC/hN1ojPiEWCsjxhvcw4p1Zveo90O38VQhktvo3Ag= @@ -1118,6 +1118,8 @@ github.com/smartcontractkit/chainlink-integrations/evm v0.0.0-20250211163724-89a github.com/smartcontractkit/chainlink-integrations/evm v0.0.0-20250211163724-89a32c016ce4/go.mod h1:UAWEMU4mLuxvr86sN8XFMtFRjkuYQnr+d6iGIbNC36s= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0 h1:ZBat8EBvE2LpSQR9U1gEbRV6PfAkiFdINmQ8nVnXIAQ= github.com/smartcontractkit/chainlink-protos/orchestrator v0.4.0/go.mod h1:m/A3lqD7ms/RsQ9BT5P2uceYY0QX5mIt4KQxT2G6qEo= +github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6 h1:L6KJ4kGv/yNNoCk8affk7Y1vAY0qglPMXC/hevV/IsA= +github.com/smartcontractkit/chainlink-protos/rmn/v1.6/go v0.0.0-20250131130834-15e0d4cde2a6/go.mod h1:FRwzI3hGj4CJclNS733gfcffmqQ62ONCkbGi49s658w= github.com/smartcontractkit/chainlink-protos/svr v0.0.0-20250123084029-58cce9b32112 h1:c77Gi/APraqwbBO8fbd/5JY2wW+MSIpYg8Uma9MEZFE= github.com/smartcontractkit/chainlink-protos/svr v0.0.0-20250123084029-58cce9b32112/go.mod h1:TcOliTQU6r59DwG4lo3U+mFM9WWyBHGuFkkxQpvSujo= github.com/smartcontractkit/chainlink-testing-framework/lib v1.51.0 h1:z7t2OhfE32KK4r5Nt3U0hOnbRwOwIbJs8i7kqKvjAA0= @@ -1128,8 +1130,8 @@ github.com/smartcontractkit/chainlink/deployment v0.0.0-20250211163809-1d69f4b09 github.com/smartcontractkit/chainlink/deployment v0.0.0-20250211163809-1d69f4b097db/go.mod h1:ROVLwMUH+YknJQ4TXkb/vYlQjjcAfMMwlKFXfeuanIQ= github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20250211163809-1d69f4b097db h1:5q1/+a9buf87/O5sC/nwbmHEWv2kDxIinhT/kSP0OzA= github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20250211163809-1d69f4b097db/go.mod h1:H6+nxMTBx5ZnmkyvpH0ySSZ5Ztsj4YYFmozN7Brhn6g= -github.com/smartcontractkit/chainlink/v2 v2.19.0-ccip1.5.16-alpha.0.0.20250211163809-1d69f4b097db h1:BrdA70ALIKBeFO9nHU3cQZ3yLA/XABBS9fIYllG5Hh4= -github.com/smartcontractkit/chainlink/v2 v2.19.0-ccip1.5.16-alpha.0.0.20250211163809-1d69f4b097db/go.mod h1:cfqvKjcLmE18v5Xx2OTv++f8AcOMG+7bft8bpGW+SFw= +github.com/smartcontractkit/chainlink/v2 v2.19.0-ccip1.5.16-alpha.0.0.20250213004954-c684df5dc2eb h1:hpI8fhgawsvuPZMywj1IWeMj91Br8tPqAYey3wmJtpM= +github.com/smartcontractkit/chainlink/v2 v2.19.0-ccip1.5.16-alpha.0.0.20250213004954-c684df5dc2eb/go.mod h1:PR07gbnqZP2cQaqu7d2n8At621hUTPa5gvIMdTi85Nw= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20241223215956-e5b78d8e3919 h1:IpGoPTXpvllN38kT2z2j13sifJMz4nbHglidvop7mfg= diff --git a/integration-tests/relayinterface/chain_components_test.go b/integration-tests/relayinterface/chain_components_test.go index e2d76f90f..3ba150eb6 100644 --- a/integration-tests/relayinterface/chain_components_test.go +++ b/integration-tests/relayinterface/chain_components_test.go @@ -1004,30 +1004,30 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T ChainSpecificName: "initialize", LookupTables: chainwriter.LookupTables{}, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Signer", Address: fromAddress, IsSigner: true, IsWritable: true, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "Account", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("data")}, {Static: testIdx}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "SystemProgram", Address: solana.SystemProgramID.String(), IsWritable: false, IsSigner: false, - }, + }}, }, DebugIDLocation: "", }, @@ -1037,42 +1037,42 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T ChainSpecificName: "initializemultiread", LookupTables: chainwriter.LookupTables{}, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Signer", Address: fromAddress, IsSigner: true, IsWritable: true, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "MultiRead1", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "ProgramID", Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("multi_read1")}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "MultiRead2", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "ProgramID", Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("multi_read2")}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "SystemProgram", Address: solana.SystemProgramID.String(), IsWritable: false, IsSigner: false, - }, + }}, }, DebugIDLocation: "", }, @@ -1082,44 +1082,44 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T ChainSpecificName: "initializemultireadwithparams", LookupTables: chainwriter.LookupTables{}, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Signer", Address: fromAddress, IsSigner: true, IsWritable: true, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "MultiRead3", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "ProgramID", Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("multi_read_with_params3")}, {Static: binary.LittleEndian.AppendUint64([]byte{}, 1)}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "MultiRead4", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "ProgramID", Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("multi_read_with_params4")}, {Static: binary.LittleEndian.AppendUint64([]byte{}, 1)}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "SystemProgram", Address: solana.SystemProgramID.String(), IsWritable: false, IsSigner: false, - }, + }}, }, DebugIDLocation: "", }, @@ -1129,44 +1129,44 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T ChainSpecificName: "initializetokenprices", LookupTables: chainwriter.LookupTables{}, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Signer", Address: fromAddress, IsSigner: true, IsWritable: true, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "BillingTokenConfigWrapper1", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "ProgramID", Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("fee_billing_token_config")}, {Static: pubKey1.Bytes()}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "BillingTokenConfigWrapper2", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "ProgramID", Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("fee_billing_token_config")}, {Static: pubKey2.Bytes()}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "SystemProgram", Address: solana.SystemProgramID.String(), IsWritable: false, IsSigner: false, - }, + }}, }, DebugIDLocation: "", }, @@ -1176,29 +1176,29 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T ChainSpecificName: "initializelookuptable", LookupTables: chainwriter.LookupTables{}, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Signer", Address: fromAddress, IsSigner: true, IsWritable: true, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "Account", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("lookup")}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "SystemProgram", Address: solana.SystemProgramID.String(), IsWritable: false, IsSigner: false, - }, + }}, }, DebugIDLocation: "", }, @@ -1210,11 +1210,11 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T DerivedLookupTables: []chainwriter.DerivedLookupTable{ { Name: "LookupTable", - Accounts: chainwriter.PDALookups{ + Accounts: chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "LookupTableAccount", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("lookup")}, }, @@ -1223,35 +1223,35 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T Location: "LookupTable", IDL: string(it.Helper.GetPrimaryIDL(t)), }, - }, + }}, }, }, }, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Signer", Address: fromAddress, IsSigner: true, IsWritable: true, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "Account", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("data")}, {Static: testIdx}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "SystemProgram", Address: solana.SystemProgramID.String(), IsWritable: false, IsSigner: false, - }, + }}, }, DebugIDLocation: "", }, @@ -1280,31 +1280,31 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T ChainSpecificName: "store", LookupTables: chainwriter.LookupTables{}, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Signer", Address: fromAddress, IsSigner: true, IsWritable: true, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "Account", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "ProgramID", Address: primaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("struct_data")}, {Static: testIdx}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "SystemProgram", Address: solana.SystemProgramID.String(), IsWritable: false, IsSigner: false, - }, + }}, }, DebugIDLocation: "", }, @@ -1319,31 +1319,33 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T ChainSpecificName: "initialize", LookupTables: chainwriter.LookupTables{}, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Signer", Address: fromAddress, IsSigner: true, IsWritable: true, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "Account", - PublicKey: chainwriter.AccountConstant{ - Name: "ProgramID", - Address: secondaryProgramPubKey, - }, + PublicKey: chainwriter.Lookup{ + AccountConstant: &chainwriter.AccountConstant{ + Name: "ProgramID", + Address: secondaryProgramPubKey, // line ~1338 + }, // line ~1339 closes AccountConstant + }, // line ~1340 closes chainwriter.Lookup Seeds: []chainwriter.Seed{ {Static: []byte("data")}, {Static: testIdx}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "SystemAccount", Address: solana.SystemProgramID.String(), IsWritable: false, IsSigner: false, - }, + }}, }, DebugIDLocation: "", }, @@ -1372,31 +1374,31 @@ func (it *SolanaChainComponentsInterfaceTester[T]) buildContractWriterConfig(t T ChainSpecificName: "store", LookupTables: chainwriter.LookupTables{}, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Signer", Address: fromAddress, IsSigner: true, IsWritable: true, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "Account", - PublicKey: chainwriter.AccountConstant{ + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "ProgramID", Address: secondaryProgramPubKey, - }, + }}, Seeds: []chainwriter.Seed{ {Static: []byte("struct_data")}, {Static: testIdx}, }, IsWritable: true, IsSigner: false, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "SystemProgram", Address: solana.SystemProgramID.String(), IsWritable: false, IsSigner: false, - }, + }}, }, DebugIDLocation: "", }, diff --git a/integration-tests/relayinterface/lookups_test.go b/integration-tests/relayinterface/lookups_test.go index 72958962b..080b60763 100644 --- a/integration-tests/relayinterface/lookups_test.go +++ b/integration-tests/relayinterface/lookups_test.go @@ -32,6 +32,23 @@ type TestAccountArgs struct { var testContractIDL = chainwriter.FetchTestContractIDL() +func TestLookup(t *testing.T) { + t.Run("Resolve fails on a lookup with multiple lookup types", func(t *testing.T) { + lookupConfig := chainwriter.Lookup{ + AccountConstant: &chainwriter.AccountConstant{ + Name: "TestAccount", + Address: "test", + }, + AccountLookup: &chainwriter.AccountLookup{ + Name: "TestAccount", + Location: "test", + }, + } + _, err := lookupConfig.Resolve(tests.Context(t), nil, nil, nil) + require.Contains(t, err.Error(), "exactly one of AccountConstant, AccountLookup, PDALookups, or AccountsFromLookupTable must be specified, got 2") + }) +} + func TestAccountContant(t *testing.T) { t.Run("AccountConstant resolves valid address", func(t *testing.T) { expectedAddr := chainwriter.GetRandomPubKey(t) @@ -42,19 +59,18 @@ func TestAccountContant(t *testing.T) { IsWritable: true, }, } - constantConfig := chainwriter.AccountConstant{ + constantConfig := chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "TestAccount", Address: expectedAddr.String(), IsSigner: true, IsWritable: true, - } - result, err := constantConfig.Resolve(tests.Context(t), nil, nil, nil) + }} + result, err := constantConfig.AccountConstant.Resolve() require.NoError(t, err) require.Equal(t, expectedMeta, result) }) } func TestAccountLookups(t *testing.T) { - ctx := tests.Context(t) t.Run("AccountLookup resolves valid address with just one address", func(t *testing.T) { expectedAddr := chainwriter.GetRandomPubKey(t) testArgs := chainwriter.TestArgs{ @@ -70,13 +86,13 @@ func TestAccountLookups(t *testing.T) { }, } - lookupConfig := chainwriter.AccountLookup{ + lookupConfig := chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{ Name: "TestAccount", Location: "Inner.Address", IsSigner: chainwriter.MetaBool{Value: true}, IsWritable: chainwriter.MetaBool{Value: true}, - } - result, err := lookupConfig.Resolve(ctx, testArgs, nil, nil) + }} + result, err := lookupConfig.AccountLookup.Resolve(testArgs) require.NoError(t, err) require.Equal(t, expectedMeta, result) }) @@ -104,13 +120,13 @@ func TestAccountLookups(t *testing.T) { }, } - lookupConfig := chainwriter.AccountLookup{ + lookupConfig := chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{ Name: "TestAccount", Location: "Inner.Address", IsSigner: chainwriter.MetaBool{Value: true}, IsWritable: chainwriter.MetaBool{Value: true}, - } - result, err := lookupConfig.Resolve(ctx, testArgs, nil, nil) + }} + result, err := lookupConfig.AccountLookup.Resolve(testArgs) require.NoError(t, err) for i, meta := range result { require.Equal(t, expectedMeta[i], meta) @@ -125,13 +141,13 @@ func TestAccountLookups(t *testing.T) { {Address: expectedAddr.Bytes()}, }, } - lookupConfig := chainwriter.AccountLookup{ + lookupConfig := chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{ Name: "InvalidAccount", Location: "Invalid.Directory", IsSigner: chainwriter.MetaBool{Value: true}, IsWritable: chainwriter.MetaBool{Value: true}, - } - _, err := lookupConfig.Resolve(ctx, testArgs, nil, nil) + }} + _, err := lookupConfig.AccountLookup.Resolve(testArgs) require.Error(t, err) }) @@ -146,12 +162,12 @@ func TestAccountLookups(t *testing.T) { } } - lookupConfig := chainwriter.AccountLookup{ + lookupConfig := chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{ Name: "InvalidAccount", Location: "Inner.Accounts.PublicKey", IsSigner: chainwriter.MetaBool{BitmapLocation: "Inner.Bitmap"}, IsWritable: chainwriter.MetaBool{BitmapLocation: "Inner.Bitmap"}, - } + }} args := TestAccountArgs{ Inner: InnerAccountArgs{ @@ -161,7 +177,7 @@ func TestAccountLookups(t *testing.T) { }, } - result, err := lookupConfig.Resolve(ctx, args, nil, nil) + result, err := lookupConfig.AccountLookup.Resolve(args) require.NoError(t, err) for i, meta := range result { @@ -185,12 +201,12 @@ func TestAccountLookups(t *testing.T) { } } - lookupConfig := chainwriter.AccountLookup{ + lookupConfig := chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{ Name: "InvalidAccount", Location: "Inner.Accounts.PublicKey", IsSigner: chainwriter.MetaBool{BitmapLocation: "Bitmaps"}, IsWritable: chainwriter.MetaBool{BitmapLocation: "Bitmaps"}, - } + }} args := TestAccountArgsExtended{ Inner: InnerAccountArgs{ @@ -199,7 +215,7 @@ func TestAccountLookups(t *testing.T) { Bitmaps: []uint64{5, 3}, } - _, err := lookupConfig.Resolve(ctx, args, nil, nil) + _, err := lookupConfig.AccountLookup.Resolve(args) require.Contains(t, err.Error(), "bitmap value is not a single value") }) @@ -213,12 +229,12 @@ func TestAccountLookups(t *testing.T) { } } - lookupConfig := chainwriter.AccountLookup{ + lookupConfig := chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{ Name: "InvalidAccount", Location: "Inner.Accounts.PublicKey", IsSigner: chainwriter.MetaBool{BitmapLocation: "Invalid.Bitmap"}, IsWritable: chainwriter.MetaBool{BitmapLocation: "Invalid.Bitmap"}, - } + }} args := TestAccountArgs{ Inner: InnerAccountArgs{ @@ -226,7 +242,7 @@ func TestAccountLookups(t *testing.T) { }, } - _, err := lookupConfig.Resolve(ctx, args, nil, nil) + _, err := lookupConfig.AccountLookup.Resolve(args) require.Contains(t, err.Error(), "error reading bitmap from location") }) @@ -240,12 +256,12 @@ func TestAccountLookups(t *testing.T) { } } - lookupConfig := chainwriter.AccountLookup{ + lookupConfig := chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{ Name: "InvalidAccount", Location: "Inner.Accounts.PublicKey", IsSigner: chainwriter.MetaBool{BitmapLocation: "Inner"}, IsWritable: chainwriter.MetaBool{BitmapLocation: "Inner"}, - } + }} args := TestAccountArgs{ Inner: InnerAccountArgs{ @@ -253,7 +269,7 @@ func TestAccountLookups(t *testing.T) { }, } - _, err := lookupConfig.Resolve(ctx, args, nil, nil) + _, err := lookupConfig.AccountLookup.Resolve(args) require.Contains(t, err.Error(), "invalid value format at path") }) } @@ -276,15 +292,15 @@ func TestPDALookups(t *testing.T) { }, } - pdaLookup := chainwriter.PDALookups{ + pdaLookup := chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "TestPDA", - PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}}, Seeds: []chainwriter.Seed{ - {Dynamic: chainwriter.AccountConstant{Name: "seed", Address: seed.String()}}, + {Dynamic: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "seed", Address: seed.String()}}}, }, IsSigner: false, IsWritable: true, - } + }} result, err := pdaLookup.Resolve(ctx, nil, nil, nil) require.NoError(t, err) @@ -305,16 +321,16 @@ func TestPDALookups(t *testing.T) { }, } - pdaLookup := chainwriter.PDALookups{ + pdaLookup := chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "TestPDA", - PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}}, Seeds: []chainwriter.Seed{ - {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}}, - {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}}}, }, IsSigner: false, IsWritable: true, - } + }} args := map[string]interface{}{ "test_seed": seed1, @@ -327,15 +343,15 @@ func TestPDALookups(t *testing.T) { }) t.Run("PDALookup fails with missing seeds", func(t *testing.T) { - pdaLookup := chainwriter.PDALookups{ + pdaLookup := chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "TestPDA", - PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}}, Seeds: []chainwriter.Seed{ - {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "MissingSeed"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "seed1", Location: "MissingSeed"}}}, }, IsSigner: false, IsWritable: true, - } + }} args := map[string]interface{}{ "test_seed": []byte("data"), @@ -361,16 +377,16 @@ func TestPDALookups(t *testing.T) { }, } - pdaLookup := chainwriter.PDALookups{ + pdaLookup := chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "TestPDA", - PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}}, Seeds: []chainwriter.Seed{ - {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}}, - {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "seed1", Location: "test_seed"}}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "seed2", Location: "another_seed"}}}, }, IsSigner: false, IsWritable: true, - } + }} args := map[string]interface{}{ "test_seed": seed1, @@ -399,16 +415,16 @@ func TestPDALookups(t *testing.T) { expectedMeta = append(expectedMeta, meta) } - pdaLookup := chainwriter.PDALookups{ + pdaLookup := chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "TestPDA", - PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}}, Seeds: []chainwriter.Seed{ - {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "single_seed"}}, - {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "array_seed"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "seed1", Location: "single_seed"}}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "seed2", Location: "array_seed"}}}, }, IsSigner: false, IsWritable: false, - } + }} args := map[string]interface{}{ "single_seed": singleSeed, @@ -439,16 +455,16 @@ func TestPDALookups(t *testing.T) { } } - pdaLookup := chainwriter.PDALookups{ + pdaLookup := chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "TestPDA", - PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "ProgramID", Address: programID.String()}}, Seeds: []chainwriter.Seed{ - {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, - {Dynamic: chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "seed2", Location: "seed2"}}}, }, IsSigner: false, IsWritable: false, - } + }} args := map[string]interface{}{ "seed1": arraySeed1, @@ -502,12 +518,12 @@ func TestLookupTables(t *testing.T) { DerivedLookupTables: []chainwriter.DerivedLookupTable{ { Name: "DerivedTable", - Accounts: chainwriter.AccountConstant{ + Accounts: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "TestLookupTable", Address: table.String(), IsSigner: true, IsWritable: true, - }, + }}, }, }, StaticLookupTables: nil, @@ -529,12 +545,13 @@ func TestLookupTables(t *testing.T) { DerivedLookupTables: []chainwriter.DerivedLookupTable{ { Name: "DerivedTable", - Accounts: chainwriter.AccountConstant{ + Accounts: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{ Name: "InvalidTable", Address: invalidTable.String(), IsSigner: true, IsWritable: true, }, + }, }, }, StaticLookupTables: nil, @@ -565,11 +582,11 @@ func TestLookupTables(t *testing.T) { DerivedLookupTables: []chainwriter.DerivedLookupTable{ { Name: "DerivedTable", - Accounts: chainwriter.AccountLookup{ + Accounts: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{ Name: "TestLookupTable", Location: "Inner.Address", IsSigner: chainwriter.MetaBool{Value: true}, - }, + }}, }, }, StaticLookupTables: nil, @@ -608,11 +625,11 @@ func TestLookupTables(t *testing.T) { DerivedLookupTables: []chainwriter.DerivedLookupTable{ { Name: "DerivedTable", - Accounts: chainwriter.PDALookups{ + Accounts: chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "DataAccountPDA", - PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}}, Seeds: []chainwriter.Seed{ - {Dynamic: chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "seed1", Location: "seed1"}}}, }, IsSigner: false, IsWritable: false, @@ -620,7 +637,7 @@ func TestLookupTables(t *testing.T) { TypeName: "LookupTableDataAccount", Location: "LookupTable", IDL: testContractIDL, - }, + }}, }, }, }, diff --git a/pkg/solana/chainwriter/chain_writer.go b/pkg/solana/chainwriter/chain_writer.go index 91a0cdc44..df80042e7 100644 --- a/pkg/solana/chainwriter/chain_writer.go +++ b/pkg/solana/chainwriter/chain_writer.go @@ -44,23 +44,23 @@ var ( // nolint // ignoring naming suggestion type ChainWriterConfig struct { - Programs map[string]ProgramConfig + Programs map[string]ProgramConfig `json:"programs"` } type ProgramConfig struct { - Methods map[string]MethodConfig - IDL string + Methods map[string]MethodConfig `json:"methods"` + IDL string `json:"idl"` } type MethodConfig struct { - FromAddress string - InputModifications commoncodec.ModifiersConfig - ChainSpecificName string - LookupTables LookupTables - Accounts []Lookup + FromAddress string `json:"fromAddress"` + InputModifications commoncodec.ModifiersConfig `json:"inputModifications,omitempty"` + ChainSpecificName string `json:"chainSpecificName"` + LookupTables LookupTables `json:"lookupTables,omitempty"` + Accounts []Lookup `json:"accounts"` // Location in the args where the debug ID is stored - DebugIDLocation string - ArgsTransform string + DebugIDLocation string `json:"debugIDLocation,omitempty"` + ArgsTransform string `json:"argsTransform,omitempty"` } func NewSolanaChainWriterService(logger logger.Logger, reader client.Reader, txm txm.TxManager, ge fees.Estimator, config ChainWriterConfig) (*SolanaChainWriterService, error) { @@ -150,7 +150,7 @@ func GetAddresses(ctx context.Context, args any, accounts []Lookup, derivedTable var addresses []*solana.AccountMeta for _, accountConfig := range accounts { meta, err := accountConfig.Resolve(ctx, args, derivedTableMap, reader) - if accountConfig.IsOptional() && err != nil { + if accountConfig.Optional && err != nil { // skip optional accounts if they are not found continue } diff --git a/pkg/solana/chainwriter/chain_writer_test.go b/pkg/solana/chainwriter/chain_writer_test.go index 8654f5260..f423110de 100644 --- a/pkg/solana/chainwriter/chain_writer_test.go +++ b/pkg/solana/chainwriter/chain_writer_test.go @@ -98,12 +98,12 @@ func TestChainWriter_GetAddresses(t *testing.T) { DerivedLookupTables: []chainwriter.DerivedLookupTable{ { Name: "DerivedTable", - Accounts: chainwriter.PDALookups{ + Accounts: chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "DataAccountPDA", - PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}}, Seeds: []chainwriter.Seed{ // extract seed2 for PDA lookup - {Dynamic: chainwriter.AccountLookup{Name: "Seed2", Location: "Seed2"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "Seed2", Location: "Seed2"}}}, }, IsSigner: derivedTablePdaLookupMeta.IsSigner, IsWritable: derivedTablePdaLookupMeta.IsWritable, @@ -112,7 +112,7 @@ func TestChainWriter_GetAddresses(t *testing.T) { Location: "LookupTable", IDL: testContractIDL, }, - }, + }}, }, }, StaticLookupTables: nil, @@ -131,34 +131,34 @@ func TestChainWriter_GetAddresses(t *testing.T) { } accountLookupConfig := []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Constant", Address: constantAccountMeta.PublicKey.String(), IsSigner: constantAccountMeta.IsSigner, IsWritable: constantAccountMeta.IsWritable, - }, - chainwriter.AccountLookup{ + }}, + {AccountLookup: &chainwriter.AccountLookup{ Name: "LookupTable", Location: "LookupTable", IsSigner: chainwriter.MetaBool{Value: accountLookupMeta.IsSigner}, IsWritable: chainwriter.MetaBool{Value: accountLookupMeta.IsWritable}, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "DataAccountPDA", - PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: solana.SystemProgramID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "WriteTest", Address: solana.SystemProgramID.String()}}, Seeds: []chainwriter.Seed{ // extract seed1 for PDA lookup - {Dynamic: chainwriter.AccountLookup{Name: "Seed1", Location: "Seed1"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "Seed1", Location: "Seed1"}}}, }, IsSigner: pdaLookupMeta.IsSigner, IsWritable: pdaLookupMeta.IsWritable, // Just get the address of the account, nothing internal. InternalField: chainwriter.InternalField{}, - }, - chainwriter.AccountsFromLookupTable{ + }}, + {AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{ LookupTableName: "DerivedTable", IncludeIndexes: []int{0}, - }, + }}, } // Fetch derived table map @@ -199,10 +199,10 @@ func TestChainWriter_GetAddresses(t *testing.T) { } accountLookupConfig := []chainwriter.Lookup{ - chainwriter.AccountsFromLookupTable{ + {AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{ LookupTableName: "DerivedTable", IncludeIndexes: []int{0, 2}, - }, + }}, } // Fetch derived table map @@ -224,9 +224,9 @@ func TestChainWriter_GetAddresses(t *testing.T) { } accountLookupConfig := []chainwriter.Lookup{ - chainwriter.AccountsFromLookupTable{ + {AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{ LookupTableName: "DerivedTable", - }, + }}, } // Fetch derived table map @@ -248,12 +248,14 @@ func TestChainWriter_GetAddresses(t *testing.T) { t.Run("AccountLookup error is skipped when Lookup is optional", func(t *testing.T) { accountLookupConfig := []chainwriter.Lookup{ - chainwriter.AccountLookup{ - Name: "OptionalAccountLookup", - Location: invalidLocation, - IsSigner: chainwriter.MetaBool{Value: false}, - IsWritable: chainwriter.MetaBool{Value: false}, - LookupOpts: chainwriter.LookupOpts{Optional: true}, + { + AccountLookup: &chainwriter.AccountLookup{ + Name: "OptionalAccountLookup", + Location: invalidLocation, + IsSigner: chainwriter.MetaBool{Value: false}, + IsWritable: chainwriter.MetaBool{Value: false}, + }, + Optional: true, }, } @@ -266,13 +268,12 @@ func TestChainWriter_GetAddresses(t *testing.T) { t.Run("AccountLookup error is returned when Lookup is required", func(t *testing.T) { accountLookupConfig := []chainwriter.Lookup{ - chainwriter.AccountLookup{ + {AccountLookup: &chainwriter.AccountLookup{ Name: "NonOptionalAccountLookup", Location: invalidLocation, IsSigner: chainwriter.MetaBool{Value: false}, IsWritable: chainwriter.MetaBool{Value: false}, - LookupOpts: chainwriter.LookupOpts{Optional: false}, - }, + }}, } args := Arguments{} @@ -283,13 +284,15 @@ func TestChainWriter_GetAddresses(t *testing.T) { t.Run("PDALookups error is skipped when Lookup is optional", func(t *testing.T) { accountLookupConfig := []chainwriter.Lookup{ - chainwriter.PDALookups{ - Name: "OptionalPDA", - PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: solana.SystemProgramID.String()}, - Seeds: []chainwriter.Seed{ - {Dynamic: chainwriter.AccountLookup{Location: invalidLocation}}, + { + PDALookups: &chainwriter.PDALookups{ + Name: "OptionalPDA", + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "ProgramID", Address: solana.SystemProgramID.String()}}, + Seeds: []chainwriter.Seed{ + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Location: invalidLocation}}}, + }, }, - LookupOpts: chainwriter.LookupOpts{Optional: true}, + Optional: true, }, } @@ -301,14 +304,13 @@ func TestChainWriter_GetAddresses(t *testing.T) { t.Run("PDALookups error is returned when Lookup is required", func(t *testing.T) { accountLookupConfig := []chainwriter.Lookup{ - chainwriter.PDALookups{ + {PDALookups: &chainwriter.PDALookups{ Name: "NonOptionalPDA", - PublicKey: chainwriter.AccountConstant{Name: "ProgramID", Address: solana.SystemProgramID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "ProgramID", Address: solana.SystemProgramID.String()}}, Seeds: []chainwriter.Seed{ - {Dynamic: chainwriter.AccountLookup{Location: invalidLocation}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Location: invalidLocation}}}, }, - LookupOpts: chainwriter.LookupOpts{Optional: false}, - }, + }}, } args := Arguments{} @@ -323,9 +325,9 @@ func TestChainWriter_GetAddresses(t *testing.T) { { Name: "OptionalDerivedTable", Optional: true, - Accounts: chainwriter.AccountLookup{ + Accounts: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{ Location: invalidLocation, - }, + }}, }, }, } @@ -342,9 +344,9 @@ func TestChainWriter_GetAddresses(t *testing.T) { DerivedLookupTables: []chainwriter.DerivedLookupTable{ { Name: "NonOptionalDerivedTable", - Accounts: chainwriter.AccountLookup{ + Accounts: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{ Location: invalidLocation, - }, + }}, Optional: false, }, }, @@ -357,9 +359,11 @@ func TestChainWriter_GetAddresses(t *testing.T) { t.Run("AccountsFromLookupTable error is skipped when Lookup is optional", func(t *testing.T) { accountLookupConfig := []chainwriter.Lookup{ - chainwriter.AccountsFromLookupTable{ - LookupTableName: "NonExistent", - LookupOpts: chainwriter.LookupOpts{Optional: true}, + { + AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{ + LookupTableName: "NonExistent", + }, + Optional: true, }, } @@ -372,10 +376,9 @@ func TestChainWriter_GetAddresses(t *testing.T) { t.Run("AccountsFromLookupTable error is returned when Lookup is required", func(t *testing.T) { accountLookupConfig := []chainwriter.Lookup{ - chainwriter.AccountsFromLookupTable{ + {AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{ LookupTableName: "NonExistent", - LookupOpts: chainwriter.LookupOpts{Optional: false}, - }, + }}, } args := Arguments{} @@ -428,12 +431,12 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { DerivedLookupTables: []chainwriter.DerivedLookupTable{ { Name: "DerivedTable", - Accounts: chainwriter.PDALookups{ + Accounts: chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "DataAccountPDA", - PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}}, Seeds: []chainwriter.Seed{ // extract seed1 for PDA lookup - {Dynamic: chainwriter.AccountLookup{Name: "Seed1", Location: "Seed1"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "Seed1", Location: "Seed1"}}}, }, IsSigner: true, IsWritable: true, @@ -442,16 +445,16 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { Location: "LookupTable", IDL: testContractIDL, }, - }, + }}, }, { Name: "MiscDerivedTable", - Accounts: chainwriter.PDALookups{ + Accounts: chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "MiscPDA", - PublicKey: chainwriter.AccountConstant{Name: "UnusedAccount", Address: unusedProgramID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "UnusedAccount", Address: unusedProgramID.String()}}, Seeds: []chainwriter.Seed{ // extract seed2 for PDA lookup - {Dynamic: chainwriter.AccountLookup{Name: "Seed2", Location: "Seed2"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "Seed2", Location: "Seed2"}}}, }, IsSigner: true, IsWritable: true, @@ -460,7 +463,7 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { Location: "LookupTable", IDL: testContractIDL, }, - }, + }}, }, }, StaticLookupTables: []solana.PublicKey{staticLookupTablePubkey1, staticLookupTablePubkey2}, @@ -473,10 +476,10 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { t.Run("returns filtered map with only relevant lookup tables required by account lookup config", func(t *testing.T) { accountLookupConfig := []chainwriter.Lookup{ - chainwriter.AccountsFromLookupTable{ + {AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{ LookupTableName: "DerivedTable", IncludeIndexes: []int{0}, - }, + }}, } // Fetch derived table map @@ -516,12 +519,12 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { t.Run("returns empty map if only constant account lookup required", func(t *testing.T) { accountLookupConfig := []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "Constant", Address: chainwriter.GetRandomPubKey(t).String(), IsSigner: false, IsWritable: false, - }, + }}, } // Fetch derived table map @@ -586,12 +589,12 @@ func TestChainWriter_SubmitTransaction(t *testing.T) { DerivedLookupTables: []chainwriter.DerivedLookupTable{ { Name: "DerivedTable", - Accounts: chainwriter.PDALookups{ + Accounts: chainwriter.Lookup{PDALookups: &chainwriter.PDALookups{ Name: "DataAccountPDA", - PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "WriteTest", Address: programID.String()}}, Seeds: []chainwriter.Seed{ // extract seed2 for PDA lookup - {Dynamic: chainwriter.AccountLookup{Name: "Seed2", Location: "Seed2"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "Seed2", Location: "Seed2"}}}, }, IsSigner: false, IsWritable: false, @@ -600,52 +603,52 @@ func TestChainWriter_SubmitTransaction(t *testing.T) { Location: "LookupTable", IDL: testContractIDL, }, - }, + }}, }, }, StaticLookupTables: []solana.PublicKey{staticLookupTablePubkey}, }, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "feepayer", Address: admin.String(), IsSigner: false, IsWritable: false, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "Constant", Address: account1.String(), IsSigner: false, IsWritable: false, - }, - chainwriter.AccountLookup{ + }}, + {AccountLookup: &chainwriter.AccountLookup{ Name: "LookupTable", Location: "LookupTable", IsSigner: chainwriter.MetaBool{Value: false}, IsWritable: chainwriter.MetaBool{Value: false}, - }, - chainwriter.PDALookups{ + }}, + {PDALookups: &chainwriter.PDALookups{ Name: "DataAccountPDA", - PublicKey: chainwriter.AccountConstant{Name: "WriteTest", Address: solana.SystemProgramID.String()}, + PublicKey: chainwriter.Lookup{AccountConstant: &chainwriter.AccountConstant{Name: "WriteTest", Address: solana.SystemProgramID.String()}}, Seeds: []chainwriter.Seed{ // extract seed1 for PDA lookup - {Dynamic: chainwriter.AccountLookup{Name: "Seed1", Location: "Seed1"}}, + {Dynamic: chainwriter.Lookup{AccountLookup: &chainwriter.AccountLookup{Name: "Seed1", Location: "Seed1"}}}, }, IsSigner: false, IsWritable: false, // Just get the address of the account, nothing internal. InternalField: chainwriter.InternalField{}, - }, - chainwriter.AccountsFromLookupTable{ + }}, + {AccountsFromLookupTable: &chainwriter.AccountsFromLookupTable{ LookupTableName: "DerivedTable", IncludeIndexes: []int{0}, - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "systemprogram", Address: solana.SystemProgramID.String(), IsSigner: false, IsWritable: false, - }, + }}, }, ArgsTransform: "", }, @@ -767,34 +770,34 @@ func TestChainWriter_CCIPOfframp(t *testing.T) { ArgsTransform: "CCIP", LookupTables: chainwriter.LookupTables{}, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "testAcc1", Address: chainwriter.GetRandomPubKey(t).String(), - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "testAcc2", Address: chainwriter.GetRandomPubKey(t).String(), - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "testAcc3", Address: chainwriter.GetRandomPubKey(t).String(), - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "poolAddr1", Address: poolKeys[0].String(), - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "poolAddr2", Address: poolKeys[1].String(), - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "poolAddr3", Address: poolKeys[2].String(), - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "poolAddr4", Address: poolKeys[3].String(), - }, + }}, }, }, ccipconsts.MethodCommit: { @@ -811,18 +814,18 @@ func TestChainWriter_CCIPOfframp(t *testing.T) { ArgsTransform: "", LookupTables: chainwriter.LookupTables{}, Accounts: []chainwriter.Lookup{ - chainwriter.AccountConstant{ + {AccountConstant: &chainwriter.AccountConstant{ Name: "testAcc1", Address: chainwriter.GetRandomPubKey(t).String(), - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "testAcc2", Address: chainwriter.GetRandomPubKey(t).String(), - }, - chainwriter.AccountConstant{ + }}, + {AccountConstant: &chainwriter.AccountConstant{ Name: "testAcc3", Address: chainwriter.GetRandomPubKey(t).String(), - }, + }}, }, }, }, diff --git a/pkg/solana/chainwriter/lookups.go b/pkg/solana/chainwriter/lookups.go index dc3abd359..b98c4d111 100644 --- a/pkg/solana/chainwriter/lookups.go +++ b/pkg/solana/chainwriter/lookups.go @@ -13,92 +13,113 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" ) -// Lookup is an interface that defines a method to resolve an address (or multiple addresses) from a given definition. -type Lookup interface { - Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader) ([]*solana.AccountMeta, error) - IsOptional() bool -} - -type LookupOpts struct { - Optional bool -} - -func (cl LookupOpts) IsOptional() bool { - return cl.Optional +type Lookup struct { + Optional bool + AccountConstant *AccountConstant `json:"accountConstant,omitempty"` + AccountLookup *AccountLookup `json:"accountLookup,omitempty"` + PDALookups *PDALookups `json:"pdas,omitempty"` + AccountsFromLookupTable *AccountsFromLookupTable `json:"accountsFromLookupTable,omitempty"` } // AccountConstant represents a fixed address, provided in Base58 format, converted into a `solana.PublicKey`. type AccountConstant struct { - Name string - Address string - IsSigner bool - IsWritable bool - LookupOpts + Name string `json:"name,omitempty"` + Address string `json:"address"` + IsSigner bool `json:"isSigner,omitempty"` + IsWritable bool `json:"isWritable,omitempty"` } // AccountLookup dynamically derives an account address from args using a specified location path. type AccountLookup struct { - Name string - Location string + Name string `json:"name,omitempty"` + Location string `json:"location"` // IsSigner and IsWritable can either be a constant bool or a location to a bitmap which decides the bools - IsSigner MetaBool - IsWritable MetaBool - LookupOpts + IsSigner MetaBool `json:"isSigner,omitempty"` + IsWritable MetaBool `json:"isWritable,omitempty"` } type MetaBool struct { - Value bool - BitmapLocation string + Value bool `json:"value,omitempty"` + BitmapLocation string `json:"bitmapLocation,omitempty"` } type Seed struct { - Static []byte // Static seed value - Dynamic Lookup // Dynamic lookup for seed + Static []byte `json:"static,omitempty"` // Static seed value + Dynamic Lookup `json:"dynamic,omitempty"` // 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 + Name string `json:"name,omitempty"` // The public key of the PDA to be combined with seeds. If there are multiple PublicKeys // there will be multiple PDAs generated by combining each PublicKey with the seeds. - PublicKey Lookup + PublicKey Lookup `json:"publicKey"` // Seeds to be derived from an additional lookup - Seeds []Seed - IsSigner bool - IsWritable bool + Seeds []Seed `json:"seeds"` + IsSigner bool `json:"isSigner,omitempty"` + IsWritable bool `json:"isWritable,omitempty"` // OPTIONAL: On-chain location and type of desired data from PDA (e.g. a sub-account of the data account) - InternalField InternalField - LookupOpts + InternalField InternalField `json:"internalField,omitempty"` } type InternalField struct { // must map directly to IDL type - TypeName string - Location string - IDL string + TypeName string `json:"typeName"` + Location string `json:"location"` + IDL string `json:"idl"` } // LookupTables represents a list of lookup tables that are used to derive addresses for a program. type LookupTables struct { - DerivedLookupTables []DerivedLookupTable - StaticLookupTables []solana.PublicKey + DerivedLookupTables []DerivedLookupTable `json:"derivedLookupTables,omitempty"` + StaticLookupTables []solana.PublicKey `json:"staticLookupTables,omitempty"` } // DerivedLookupTable represents a lookup table that is used to derive addresses for a program. type DerivedLookupTable struct { - Name string - Accounts Lookup - Optional bool + Name string `json:"name,omitempty"` + Accounts Lookup `json:"accounts"` + Optional bool `json:"optional"` } // AccountsFromLookupTable extracts accounts from a lookup table that was previously read and stored in memory. type AccountsFromLookupTable struct { - LookupTableName string - IncludeIndexes []int - LookupOpts + LookupTableName string `json:"lookupTableName"` + IncludeIndexes []int `json:"includeIndexes"` +} + +func (l Lookup) validate() error { + count := 0 + for _, v := range []bool{l.AccountConstant != nil, l.AccountLookup != nil, l.PDALookups != nil, l.AccountsFromLookupTable != nil} { + if v { + count++ + } + } + if count != 1 { + return fmt.Errorf("exactly one of AccountConstant, AccountLookup, PDALookups, or AccountsFromLookupTable must be specified, got %d", count) + } + return nil } -func (ac AccountConstant) Resolve(_ context.Context, _ any, _ map[string]map[string][]*solana.AccountMeta, _ client.Reader) ([]*solana.AccountMeta, error) { +func (l Lookup) Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader) ([]*solana.AccountMeta, error) { + // could update this in the future to validate the entire config at initialization time recursively. + err := l.validate() + if err != nil { + return nil, err + } + if l.AccountConstant != nil { + return l.AccountConstant.Resolve() + } else if l.AccountLookup != nil { + return l.AccountLookup.Resolve(args) + } else if l.PDALookups != nil { + return l.PDALookups.Resolve(ctx, args, derivedTableMap, reader) + } else if l.AccountsFromLookupTable != nil { + return l.AccountsFromLookupTable.Resolve(derivedTableMap) + } + return nil, fmt.Errorf("no lookup type specified") +} + +func (ac AccountConstant) Resolve() ([]*solana.AccountMeta, error) { address, err := solana.PublicKeyFromBase58(ac.Address) if err != nil { return nil, fmt.Errorf("error getting account from constant: %w", err) @@ -112,7 +133,7 @@ func (ac AccountConstant) Resolve(_ context.Context, _ any, _ map[string]map[str }, nil } -func (al AccountLookup) Resolve(_ context.Context, args any, _ map[string]map[string][]*solana.AccountMeta, _ client.Reader) ([]*solana.AccountMeta, error) { +func (al AccountLookup) Resolve(args any) ([]*solana.AccountMeta, error) { derivedValues, err := GetValuesAtLocation(args, al.Location) if err != nil { return nil, fmt.Errorf("error getting account from lookup: %w", err) @@ -171,7 +192,7 @@ func resolveBitMap(mb MetaBool, args any, length int) ([]bool, error) { return result, nil } -func (alt AccountsFromLookupTable) Resolve(_ context.Context, _ any, derivedTableMap map[string]map[string][]*solana.AccountMeta, _ client.Reader) ([]*solana.AccountMeta, error) { +func (alt AccountsFromLookupTable) Resolve(derivedTableMap map[string]map[string][]*solana.AccountMeta) ([]*solana.AccountMeta, error) { // Fetch the inner map for the specified lookup table name innerMap, ok := derivedTableMap[alt.LookupTableName] if !ok { @@ -294,9 +315,10 @@ func getSeedBytesCombinations( if seed.Static != nil { expansions = append(expansions, seed.Static) // Static and Dynamic seeds are mutually exclusive - } else if seed.Dynamic != nil { + } else if !seed.Dynamic.IsNil() { dynamicSeed := seed.Dynamic - if lookupSeed, ok := dynamicSeed.(AccountLookup); ok { + if dynamicSeed.AccountLookup != nil { + lookupSeed := dynamicSeed.AccountLookup // 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 { @@ -345,6 +367,10 @@ func getSeedBytesCombinations( return allCombinations, nil } +func (l Lookup) IsNil() bool { + return l.AccountConstant == nil && l.AccountLookup == nil && l.PDALookups == nil && l.AccountsFromLookupTable == nil +} + // generatePDAs generates program-derived addresses (PDAs) from public keys and seeds. // it will result in a list of PDAs whose length is the product of the number of public keys // and the number of seed combinations. diff --git a/pkg/solana/chainwriter/transform_registry.go b/pkg/solana/chainwriter/transform_registry.go index aff7ebac3..1fbd46b08 100644 --- a/pkg/solana/chainwriter/transform_registry.go +++ b/pkg/solana/chainwriter/transform_registry.go @@ -46,9 +46,9 @@ func CCIPArgsTransform(ctx context.Context, cw *SolanaChainWriterService, args a // PDA lookup to fetch router address routerAddrLookup := PDALookups{ Name: "ReferenceAddresses", - PublicKey: AccountConstant{ + PublicKey: Lookup{AccountConstant: &AccountConstant{ Address: toAddress, - }, + }}, Seeds: []Seed{ {Static: []byte("reference_addresses")}, }, @@ -78,14 +78,14 @@ func CCIPArgsTransform(ctx context.Context, cw *SolanaChainWriterService, args a DerivedLookupTables: []DerivedLookupTable{ { Name: "PoolLookupTable", - Accounts: PDALookups{ + Accounts: Lookup{PDALookups: &PDALookups{ Name: "TokenAdminRegistry", - PublicKey: AccountConstant{ + PublicKey: Lookup{AccountConstant: &AccountConstant{ Address: routerAddress.String(), - }, + }}, Seeds: []Seed{ {Static: []byte("token_admin_registry")}, - {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.TokenAmounts.DestTokenAddress"}}, + {Dynamic: Lookup{AccountLookup: &AccountLookup{Location: "Info.AbstractReports.Messages.TokenAmounts.DestTokenAddress"}}}, }, IsSigner: false, IsWritable: false, @@ -94,7 +94,7 @@ func CCIPArgsTransform(ctx context.Context, cw *SolanaChainWriterService, args a Location: "LookupTable", IDL: routerProgramConfig.IDL, }, - }, + }}, }, }, }