diff --git a/go.mod b/go.mod index 6c1ab5c8c..1b214af7e 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/pelletier/go-toml/v2 v2.2.3 github.com/prometheus/client_golang v1.20.5 github.com/smartcontractkit/chainlink-ccip v0.0.0-20250203132120-f0d42463e405 - github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250128162345-af4c8fd4481a + github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250206215114-fb6c3c35e8e3 github.com/smartcontractkit/chainlink-common v0.4.2-0.20250205141137-8f50d72601bb github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250205165125-271e20f6de0a github.com/smartcontractkit/libocr v0.0.0-20241223215956-e5b78d8e3919 diff --git a/go.sum b/go.sum index 5a4518a15..858e1ef7a 100644 --- a/go.sum +++ b/go.sum @@ -578,8 +578,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartcontractkit/chainlink-ccip v0.0.0-20250203132120-f0d42463e405 h1:5QyaPGLmt+rlnvQL7drAE23Wq9rX5hO35kTZirAb97A= github.com/smartcontractkit/chainlink-ccip v0.0.0-20250203132120-f0d42463e405/go.mod h1:UEnHaxkUsfreeA7rR45LMmua1Uen95tOFUR8/AI9BAo= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250128162345-af4c8fd4481a h1:1MrD2OiP/CRfyBSwTQE66R1+gLWBgWcU/SYl/+DmZ/Y= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250128162345-af4c8fd4481a/go.mod h1:Bmwq4lNb5tE47sydN0TKetcLEGbgl+VxHEWp4S0LI60= +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= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250205141137-8f50d72601bb/go.mod h1:Z2e1ynSJ4pg83b4Qldbmryc5lmnrI3ojOdg1FUloa68= github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250205165125-271e20f6de0a h1:ZG8v7aQxyp9cOYXpW6oodL+OWgwDku544qyzXPPgs7M= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 06821b5b9..6635f35ba 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -343,7 +343,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.37 // indirect github.com/smartcontractkit/chainlink-automation v0.8.1 // indirect github.com/smartcontractkit/chainlink-ccip v0.0.0-20250203132120-f0d42463e405 // indirect - github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250128162345-af4c8fd4481a // indirect + github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250206215114-fb6c3c35e8e3 // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20250130125138-3df261e09ddc // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20250128203428-08031923fbe5 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index f5b25f7f6..fd1b3e723 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1229,8 +1229,8 @@ github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgB github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= github.com/smartcontractkit/chainlink-ccip v0.0.0-20250203132120-f0d42463e405 h1:5QyaPGLmt+rlnvQL7drAE23Wq9rX5hO35kTZirAb97A= github.com/smartcontractkit/chainlink-ccip v0.0.0-20250203132120-f0d42463e405/go.mod h1:UEnHaxkUsfreeA7rR45LMmua1Uen95tOFUR8/AI9BAo= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250128162345-af4c8fd4481a h1:1MrD2OiP/CRfyBSwTQE66R1+gLWBgWcU/SYl/+DmZ/Y= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250128162345-af4c8fd4481a/go.mod h1:Bmwq4lNb5tE47sydN0TKetcLEGbgl+VxHEWp4S0LI60= +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= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250205141137-8f50d72601bb/go.mod h1:Z2e1ynSJ4pg83b4Qldbmryc5lmnrI3ojOdg1FUloa68= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20250130125138-3df261e09ddc h1:WZERXv2hTYRA0NpWg79ci/ZZSxucmvkty39iUOV8d7I= diff --git a/pkg/solana/chainreader/account_read_binding.go b/pkg/solana/chainreader/account_read_binding.go index 38d968abc..b6435d0d8 100644 --- a/pkg/solana/chainreader/account_read_binding.go +++ b/pkg/solana/chainreader/account_read_binding.go @@ -18,10 +18,10 @@ type accountReadBinding struct { codec types.RemoteCodec key solana.PublicKey isPda bool // flag to signify whether or not the account read is for a PDA - prefix string // only used for PDA public key calculation + prefix []byte // only used for PDA public key calculation } -func newAccountReadBinding(namespace, genericName, prefix string, isPda bool) *accountReadBinding { +func newAccountReadBinding(namespace, genericName string, prefix []byte, isPda bool) *accountReadBinding { return &accountReadBinding{ namespace: namespace, genericName: genericName, @@ -71,7 +71,7 @@ func (b *accountReadBinding) Decode(ctx context.Context, bts []byte, outVal any) func (b *accountReadBinding) buildSeedsSlice(ctx context.Context, params any) ([][]byte, error) { flattenedSeeds := make([]byte, 0, solana.MaxSeeds*solana.MaxSeedLength) // Append the static prefix string first - flattenedSeeds = append(flattenedSeeds, []byte(b.prefix)...) + flattenedSeeds = append(flattenedSeeds, b.prefix...) // Encode the seeds provided in the params encodedParamSeeds, err := b.codec.Encode(ctx, params, codec.WrapItemType(true, b.namespace, b.genericName)) if err != nil { diff --git a/pkg/solana/chainreader/chain_reader.go b/pkg/solana/chainreader/chain_reader.go index f8c180697..1da277e2a 100644 --- a/pkg/solana/chainreader/chain_reader.go +++ b/pkg/solana/chainreader/chain_reader.go @@ -21,6 +21,7 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" "github.com/smartcontractkit/chainlink-solana/pkg/solana/logpoller" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/utils" ) type EventsReader interface { @@ -316,7 +317,7 @@ func (s *ContractReaderService) addCodecDef(forEncoding bool, namespace, generic func (s *ContractReaderService) init(namespaces map[string]config.ChainContractReader) error { for namespace, nameSpaceDef := range namespaces { for genericName, read := range nameSpaceDef.Reads { - injectAddressModifier(read.InputModifications, read.OutputModifications) + utils.InjectAddressModifier(read.InputModifications, read.OutputModifications) switch read.ReadType { case config.Account: @@ -374,12 +375,12 @@ func (s *ContractReaderService) addAccountRead(namespace string, genericName str ) // Create PDA read binding if PDA prefix or seeds configs are populated - if len(readDefinition.PDADefiniton.Prefix) > 0 || len(readDefinition.PDADefiniton.Seeds) > 0 { + if readDefinition.PDADefiniton.Prefix != nil || len(readDefinition.PDADefiniton.Seeds) > 0 { inputAccountIDLDef = readDefinition.PDADefiniton reader = newAccountReadBinding(namespace, genericName, readDefinition.PDADefiniton.Prefix, true) } else { inputAccountIDLDef = codec.NilIdlTypeDefTy - reader = newAccountReadBinding(namespace, genericName, "", false) + reader = newAccountReadBinding(namespace, genericName, nil, false) } if err := s.addCodecDef(true, namespace, genericName, idl, inputAccountIDLDef, readDefinition.InputModifications); err != nil { return err @@ -420,24 +421,6 @@ func (s *ContractReaderService) addEventRead( return nil } -// injectAddressModifier injects AddressModifier into OutputModifications. -// This is necessary because AddressModifier cannot be serialized and must be applied at runtime. -func injectAddressModifier(inputModifications, outputModifications commoncodec.ModifiersConfig) { - for i, modConfig := range inputModifications { - if addrModifierConfig, ok := modConfig.(*commoncodec.AddressBytesToStringModifierConfig); ok { - addrModifierConfig.Modifier = codec.SolanaAddressModifier{} - outputModifications[i] = addrModifierConfig - } - } - - for i, modConfig := range outputModifications { - if addrModifierConfig, ok := modConfig.(*commoncodec.AddressBytesToStringModifierConfig); ok { - addrModifierConfig.Modifier = codec.SolanaAddressModifier{} - outputModifications[i] = addrModifierConfig - } - } -} - type accountDataReader struct { client *rpc.Client } diff --git a/pkg/solana/chainreader/chain_reader_test.go b/pkg/solana/chainreader/chain_reader_test.go index 77b72a200..987b3d256 100644 --- a/pkg/solana/chainreader/chain_reader_test.go +++ b/pkg/solana/chainreader/chain_reader_test.go @@ -274,7 +274,7 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { programID := solana.NewWallet().PublicKey() pubKey := solana.NewWallet().PublicKey() uint64Seed := uint64(5) - prefixString := "Prefix" + prefixBytes := []byte("Prefix") readDef := config.ReadDefinition{ ChainSpecificName: testutils.TestStructWithNestedStruct, @@ -294,7 +294,7 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { { name: "happy path", pdaDefinition: codec.PDATypeDef{ - Prefix: prefixString, + Prefix: prefixBytes, Seeds: []codec.PDASeed{ { Name: "PubKey", @@ -306,7 +306,7 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { }, }, }, - expected: mustFindProgramAddress(t, programID, [][]byte{[]byte(prefixString), pubKey.Bytes(), go_binary.LittleEndian.AppendUint64([]byte{}, uint64Seed)}), + expected: mustFindProgramAddress(t, programID, [][]byte{prefixBytes, pubKey.Bytes(), go_binary.LittleEndian.AppendUint64([]byte{}, uint64Seed)}), params: map[string]any{ "PubKey": pubKey, "Uint64Seed": uint64Seed, @@ -315,7 +315,7 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { { name: "with modifier and random field", pdaDefinition: codec.PDATypeDef{ - Prefix: prefixString, + Prefix: prefixBytes, Seeds: []codec.PDASeed{ { Name: "PubKey", @@ -330,7 +330,7 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { inputModifier: codeccommon.ModifiersConfig{ &codeccommon.RenameModifierConfig{Fields: map[string]string{"PubKey": "PublicKey"}}, }, - expected: mustFindProgramAddress(t, programID, [][]byte{[]byte(prefixString), pubKey.Bytes(), go_binary.LittleEndian.AppendUint64([]byte{}, uint64Seed)}), + expected: mustFindProgramAddress(t, programID, [][]byte{prefixBytes, pubKey.Bytes(), go_binary.LittleEndian.AppendUint64([]byte{}, uint64Seed)}), params: map[string]any{ "PublicKey": pubKey, "randomField": "randomValue", // unused field should be ignored by the codec @@ -340,15 +340,15 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { { name: "only prefix", pdaDefinition: codec.PDATypeDef{ - Prefix: prefixString, + Prefix: prefixBytes, }, - expected: mustFindProgramAddress(t, programID, [][]byte{[]byte(prefixString)}), + expected: mustFindProgramAddress(t, programID, [][]byte{prefixBytes}), params: nil, }, { name: "no prefix", pdaDefinition: codec.PDATypeDef{ - Prefix: "", + Prefix: nil, Seeds: []codec.PDASeed{ { Name: "PubKey", @@ -369,7 +369,7 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { { name: "public key seed provided as bytes", pdaDefinition: codec.PDATypeDef{ - Prefix: prefixString, + Prefix: prefixBytes, Seeds: []codec.PDASeed{ { Name: "PubKey", @@ -377,7 +377,7 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { }, }, }, - expected: mustFindProgramAddress(t, programID, [][]byte{[]byte(prefixString), pubKey.Bytes()}), + expected: mustFindProgramAddress(t, programID, [][]byte{prefixBytes, pubKey.Bytes()}), params: map[string]any{ "PubKey": pubKey.Bytes(), }, @@ -424,12 +424,12 @@ func TestSolanaChainReaderService_GetLatestValue(t *testing.T) { }) t.Run("PDA account read errors if missing param", func(t *testing.T) { - prefixString := "Prefix" + prefixBytes := []byte("Prefix") readDef := config.ReadDefinition{ ChainSpecificName: testutils.TestStructWithNestedStruct, ReadType: config.Account, PDADefiniton: codec.PDATypeDef{ - Prefix: prefixString, + Prefix: prefixBytes, Seeds: []codec.PDASeed{ { Name: "PubKey", diff --git a/pkg/solana/chainwriter/chain_writer.go b/pkg/solana/chainwriter/chain_writer.go index a914d6aa3..8109491a8 100644 --- a/pkg/solana/chainwriter/chain_writer.go +++ b/pkg/solana/chainwriter/chain_writer.go @@ -19,6 +19,7 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees" "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/utils" ) const ServiceName = "SolanaChainWriter" @@ -91,6 +92,7 @@ func (s *SolanaChainWriterService) parsePrograms(config ChainWriterConfig) error return fmt.Errorf("failed to unmarshal IDL for program: %s, error: %w", program, err) } for method, methodConfig := range programConfig.Methods { + utils.InjectAddressModifier(methodConfig.InputModifications, nil) idlDef, err := codec.FindDefinitionFromIDL(codec.ChainConfigTypeInstructionDef, methodConfig.ChainSpecificName, idl) if err != nil { return err diff --git a/pkg/solana/chainwriter/chain_writer_test.go b/pkg/solana/chainwriter/chain_writer_test.go index 81a330c74..2141b3001 100644 --- a/pkg/solana/chainwriter/chain_writer_test.go +++ b/pkg/solana/chainwriter/chain_writer_test.go @@ -21,6 +21,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" idl "github.com/smartcontractkit/chainlink-ccip/chains/solana" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp" "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" @@ -37,6 +38,7 @@ type Arguments struct { Seed2 []byte } +var ccipOfframpIDL = idl.FetchCCIPOfframpIDL() var ccipRouterIDL = idl.FetchCCIPRouterIDL() var testContractIDL = chainwriter.FetchTestContractIDL() @@ -584,7 +586,7 @@ func TestChainWriter_SubmitTransaction(t *testing.T) { }) } -func TestChainWriter_CCIPRouter(t *testing.T) { +func TestChainWriter_CCIPOfframp(t *testing.T) { t.Parallel() // setup admin key @@ -592,6 +594,7 @@ func TestChainWriter_CCIPRouter(t *testing.T) { require.NoError(t, err) admin := adminPk.PublicKey() + offrampAddr := chainwriter.GetRandomPubKey(t) routerAddr := chainwriter.GetRandomPubKey(t) destTokenAddr := chainwriter.GetRandomPubKey(t) @@ -601,7 +604,7 @@ func TestChainWriter_CCIPRouter(t *testing.T) { // simplified CCIP Config - does not contain full account list ccipCWConfig := chainwriter.ChainWriterConfig{ Programs: map[string]chainwriter.ProgramConfig{ - "ccip_router": { + "ccip-offramp": { Methods: map[string]chainwriter.MethodConfig{ "execute": { FromAddress: admin.String(), @@ -676,6 +679,10 @@ func TestChainWriter_CCIPRouter(t *testing.T) { }, }, }, + IDL: ccipOfframpIDL, + }, + // Requires only the IDL for the CCIPArgsTransform to fetch the TokenAdminRegistry + "ccip-router": { IDL: ccipRouterIDL, }, }, @@ -702,13 +709,14 @@ func TestChainWriter_CCIPRouter(t *testing.T) { lookupTable := mockTokenAdminRegistryLookupTable(t, rw, pda) + mockFetchRouterAddress(t, rw, routerAddr, offrampAddr) mockFetchLookupTableAddresses(t, rw, lookupTable, poolKeys) txID := uuid.NewString() txm.On("Enqueue", mock.Anything, admin.String(), mock.MatchedBy(func(tx *solana.Transaction) bool { txData := tx.Message.Instructions[0].Data payload := txData[8:] - var decoded ccip_router.Execute + var decoded ccip_offramp.Execute dec := ag_binary.NewBorshDecoder(payload) err = dec.Decode(&decoded) require.NoError(t, err) @@ -746,7 +754,7 @@ func TestChainWriter_CCIPRouter(t *testing.T) { }, } - submitErr := cw.SubmitTransaction(ctx, "ccip_router", "execute", args, txID, routerAddr.String(), nil, nil) + submitErr := cw.SubmitTransaction(ctx, "ccip-offramp", "execute", args, txID, offrampAddr.String(), nil, nil) require.NoError(t, submitErr) }) @@ -787,14 +795,14 @@ func TestChainWriter_CCIPRouter(t *testing.T) { txm.On("Enqueue", mock.Anything, admin.String(), mock.MatchedBy(func(tx *solana.Transaction) bool { txData := tx.Message.Instructions[0].Data payload := txData[8:] - var decoded ccip_router.Commit + var decoded ccip_offramp.Commit dec := ag_binary.NewBorshDecoder(payload) err := dec.Decode(&decoded) require.NoError(t, err) return true }), &txID, mock.Anything).Return(nil).Once() - submitErr := cw.SubmitTransaction(ctx, "ccip_router", "commit", args, txID, routerAddr.String(), nil, nil) + submitErr := cw.SubmitTransaction(ctx, "ccip-offramp", "commit", args, txID, offrampAddr.String(), nil, nil) require.NoError(t, submitErr) }) } @@ -951,3 +959,19 @@ func mockFetchLookupTableAddresses(t *testing.T, rw *clientmocks.ReaderWriter, l Value: &rpc.Account{Data: rpc.DataBytesOrJSONFromBytes(lookupTableStateBytes)}, }, nil) } + +func mockFetchRouterAddress(t *testing.T, rw *clientmocks.ReaderWriter, routerAddr, offrampAddr solana.PublicKey) { + pda, _, err := solana.FindProgramAddress([][]byte{[]byte("reference_addresses")}, offrampAddr) + require.NoError(t, err) + referenceAddresses := ccip_offramp.ReferenceAddresses{ + Version: 1, + Router: routerAddr, + FeeQuoter: solana.PublicKey{}, + OfframpLookupTable: solana.PublicKey{}, + } + referenceAddressesBytes := mustBorshEncodeStruct(t, referenceAddresses) + rw.On("GetAccountInfoWithOpts", mock.Anything, pda, mock.Anything).Return(&rpc.GetAccountInfoResult{ + RPCContext: rpc.RPCContext{}, + Value: &rpc.Account{Data: rpc.DataBytesOrJSONFromBytes(referenceAddressesBytes)}, + }, nil) +} diff --git a/pkg/solana/chainwriter/helpers.go b/pkg/solana/chainwriter/helpers.go index 4f73a2a33..c2fa5b832 100644 --- a/pkg/solana/chainwriter/helpers.go +++ b/pkg/solana/chainwriter/helpers.go @@ -38,7 +38,7 @@ type DataAccount struct { //go:embed testContractIDL.json var testContractIDL string -// FetchCCIPRouterIDL returns the IDL for chain components test contract +// FetchTestContractIDL returns the IDL for chain components test contract func FetchTestContractIDL() string { return testContractIDL } diff --git a/pkg/solana/chainwriter/lookups.go b/pkg/solana/chainwriter/lookups.go index 36719538a..8d98845b6 100644 --- a/pkg/solana/chainwriter/lookups.go +++ b/pkg/solana/chainwriter/lookups.go @@ -282,7 +282,7 @@ func getSeedBytesCombinations( } else if seed.Dynamic != nil { dynamicSeed := seed.Dynamic if lookupSeed, ok := dynamicSeed.(AccountLookup); ok { - // Get value from a location (This doens't have to be an address, it can be any value) + // Get value from a location (This doesn't have to be an address, it can be any value) bytes, err := GetValuesAtLocation(args, lookupSeed.Location) if err != nil { return nil, fmt.Errorf("error getting address seed for location %q: %w", lookupSeed.Location, err) diff --git a/pkg/solana/chainwriter/transform_registry.go b/pkg/solana/chainwriter/transform_registry.go index 8d7661807..dd4e9b04c 100644 --- a/pkg/solana/chainwriter/transform_registry.go +++ b/pkg/solana/chainwriter/transform_registry.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/gagliardetto/solana-go" - "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router" + "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" ) @@ -14,14 +14,14 @@ type ReportPreTransform struct { ReportContext [2][32]byte Report []byte Info ccipocr3.ExecuteReportInfo - AbstractReport ccip_router.ExecutionReportSingleChain + AbstractReport ccip_offramp.ExecutionReportSingleChain } type ReportPostTransform struct { ReportContext [2][32]byte Report []byte Info ccipocr3.ExecuteReportInfo - AbstractReport ccip_router.ExecutionReportSingleChain + AbstractReport ccip_offramp.ExecutionReportSingleChain TokenIndexes []byte } @@ -37,6 +37,35 @@ func FindTransform(id string) (func(context.Context, *SolanaChainWriterService, // This Transform function looks up the token pool addresses in the accounts slice and augments the args // with the indexes of the token pool addresses in the accounts slice. func CCIPArgsTransform(ctx context.Context, cw *SolanaChainWriterService, args any, accounts solana.AccountMetaSlice, toAddress string) (any, error) { + // Fetch offramp config to use to fetch the router address + offrampProgramConfig, ok := cw.config.Programs["ccip-offramp"] + if !ok { + return nil, fmt.Errorf("ccip-offramp program not found in config") + } + // PDA lookup to fetch router address + routerAddrLookup := PDALookups{ + Name: "ReferenceAddresses", + PublicKey: AccountConstant{ + Address: toAddress, + }, + Seeds: []Seed{ + {Static: []byte("reference_addresses")}, + }, + // Reads the router address from the reference addresses PDA + InternalField: InternalField{ + TypeName: "ReferenceAddresses", + Location: "Router", + }, + } + accountMetas, err := routerAddrLookup.Resolve(ctx, nil, nil, cw.reader, offrampProgramConfig.IDL) + if err != nil { + return nil, fmt.Errorf("failed to fetch the router program address from the reference addresses account: %w", err) + } + if len(accountMetas) != 1 { + return nil, fmt.Errorf("expect 1 address to be returned for router address, received %d: %w", len(accountMetas), err) + } + + routerAddress := accountMetas[0].PublicKey TokenPoolLookupTable := LookupTables{ DerivedLookupTables: []DerivedLookupTable{ { @@ -44,7 +73,7 @@ func CCIPArgsTransform(ctx context.Context, cw *SolanaChainWriterService, args a Accounts: PDALookups{ Name: "TokenAdminRegistry", PublicKey: AccountConstant{ - Address: toAddress, + Address: routerAddress.String(), }, Seeds: []Seed{ {Static: []byte("token_admin_registry")}, @@ -61,9 +90,10 @@ func CCIPArgsTransform(ctx context.Context, cw *SolanaChainWriterService, args a }, } - routerProgramConfig, ok := cw.config.Programs["ccip_router"] + // Fetch router config to use to fetch TokenAdminRegistry + routerProgramConfig, ok := cw.config.Programs["ccip-router"] if !ok { - return nil, fmt.Errorf("ccip_router program not found in config") + return nil, fmt.Errorf("ccip-router program not found in config") } tableMap, _, err := cw.ResolveLookupTables(ctx, args, TokenPoolLookupTable, routerProgramConfig.IDL) diff --git a/pkg/solana/codec/anchoridl.go b/pkg/solana/codec/anchoridl.go index 7de955d8e..beffef731 100644 --- a/pkg/solana/codec/anchoridl.go +++ b/pkg/solana/codec/anchoridl.go @@ -160,7 +160,7 @@ type IdlField struct { // PDA is a struct that does not correlate to an official IDL type // It is needed to encode seeds to calculate the address for PDA account reads type PDATypeDef struct { - Prefix string `json:"prefix,omitempty"` + Prefix []byte `json:"prefix,omitempty"` Seeds []PDASeed `json:"seeds,omitempty"` } diff --git a/pkg/solana/codec/anchoridl_test.go b/pkg/solana/codec/anchoridl_test.go index a5cd4f378..7a9d21845 100644 --- a/pkg/solana/codec/anchoridl_test.go +++ b/pkg/solana/codec/anchoridl_test.go @@ -25,6 +25,6 @@ func TestIDLTypes_JSONMarshalUnmarshal(t *testing.T) { ensureUnmarshal[IdlField](t, idl) }) t.Run("CCIP IDL", func(t *testing.T) { - ensureUnmarshal[IDL](t, solana.FetchCCIPRouterIDL()) + ensureUnmarshal[IDL](t, solana.FetchCCIPOfframpIDL()) }) } diff --git a/pkg/solana/utils/utils.go b/pkg/solana/utils/utils.go index 24075b6f0..d7aa1c1a1 100644 --- a/pkg/solana/utils/utils.go +++ b/pkg/solana/utils/utils.go @@ -12,6 +12,9 @@ import ( "github.com/gagliardetto/solana-go/rpc" "github.com/stretchr/testify/require" + commoncodec "github.com/smartcontractkit/chainlink-common/pkg/codec" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana/codec" "github.com/smartcontractkit/chainlink-solana/pkg/solana/internal" ) @@ -89,3 +92,21 @@ func sendTransaction(ctx context.Context, rpcClient *rpc.Client, t *testing.T, i require.NoError(t, err) return txres } + +// InjectAddressModifier injects AddressModifier into InputModifications and OutputModifications. +// This is necessary because AddressModifier cannot be serialized and must be applied at runtime. +func InjectAddressModifier(inputModifications, outputModifications commoncodec.ModifiersConfig) { + for i, modConfig := range inputModifications { + if addrModifierConfig, ok := modConfig.(*commoncodec.AddressBytesToStringModifierConfig); ok { + addrModifierConfig.Modifier = codec.SolanaAddressModifier{} + inputModifications[i] = addrModifierConfig + } + } + + for i, modConfig := range outputModifications { + if addrModifierConfig, ok := modConfig.(*commoncodec.AddressBytesToStringModifierConfig); ok { + addrModifierConfig.Modifier = codec.SolanaAddressModifier{} + outputModifications[i] = addrModifierConfig + } + } +}