From da75f6d7734685f31e757b191725a77cf0094120 Mon Sep 17 00:00:00 2001 From: Silas Lenihan Date: Mon, 27 Jan 2025 11:16:58 -0500 Subject: [PATCH] Updated PDALookup internal field to use codec --- go.mod | 4 +- go.sum | 8 +- integration-tests/go.mod | 4 +- integration-tests/go.sum | 8 +- .../relayinterface/lookups_test.go | 45 ++-- pkg/solana/chainwriter/ccip_example_config.go | 70 ++++--- pkg/solana/chainwriter/chain_writer.go | 16 +- pkg/solana/chainwriter/chain_writer_test.go | 194 +++++++++++------- pkg/solana/chainwriter/helpers.go | 18 +- pkg/solana/chainwriter/lookups.go | 61 +++--- pkg/solana/chainwriter/transform_registry.go | 17 +- 11 files changed, 259 insertions(+), 186 deletions(-) diff --git a/go.mod b/go.mod index 3acff1048..f919f9207 100644 --- a/go.mod +++ b/go.mod @@ -15,8 +15,8 @@ require ( github.com/jackc/pgx/v4 v4.18.3 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-20250122132616-0b8ae69756db - github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250122150024-c1b7ba41ee90 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20250127195852-3ea463336e7f + github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250127195852-3ea463336e7f github.com/smartcontractkit/chainlink-common v0.4.2-0.20250121163309-3e179a73cb92 github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250121205514-f73e2f86c23b github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 diff --git a/go.sum b/go.sum index a3e584291..400e7c341 100644 --- a/go.sum +++ b/go.sum @@ -549,10 +549,10 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 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-20250122132616-0b8ae69756db h1:W5r1QLevaqmbGimB9F3JMJyiw/qTXRATFi5DbmzEVNc= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250122132616-0b8ae69756db/go.mod h1:UEnHaxkUsfreeA7rR45LMmua1Uen95tOFUR8/AI9BAo= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250122150024-c1b7ba41ee90 h1:zLARR/tDnpp471QWvDCnUFB5WK9XzP7dZTqIBQjUMNM= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250122150024-c1b7ba41ee90/go.mod h1:Bmwq4lNb5tE47sydN0TKetcLEGbgl+VxHEWp4S0LI60= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250127195852-3ea463336e7f h1:u09xYqvXIikqspsnTba8EDyif1eoWuFbZG+iCFCj118= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250127195852-3ea463336e7f/go.mod h1:UEnHaxkUsfreeA7rR45LMmua1Uen95tOFUR8/AI9BAo= +github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250127195852-3ea463336e7f h1:6YomtETcLQzuAWOYwsWkApmEV6Wj1q0fTHJe/WRAJgE= +github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250127195852-3ea463336e7f/go.mod h1:Bmwq4lNb5tE47sydN0TKetcLEGbgl+VxHEWp4S0LI60= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250121163309-3e179a73cb92 h1:9zmJi4TctSNvmVdmRh2UpbNRDnrWKYn4o+PZDAIhqqc= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250121163309-3e179a73cb92/go.mod h1:V3BHfvLnQNBUoZ4bGjD29ZPhyzPE++DkYkhvPb9tcRs= github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20250121205514-f73e2f86c23b h1:TO1pwFeQKDOmv3loFiLJvYhtymuTgQUw9WgtwK1rueg= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 79e3193a7..dfa7f9239 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -342,8 +342,8 @@ require ( github.com/slack-go/slack v0.15.0 // indirect github.com/smartcontractkit/chain-selectors v1.0.36 // indirect github.com/smartcontractkit/chainlink-automation v0.8.1 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20250122132616-0b8ae69756db // indirect - github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250122150024-c1b7ba41ee90 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20250127195852-3ea463336e7f // indirect + github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250127195852-3ea463336e7f // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20250115135646-ac859d85e7e3 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index d3388b444..10f7e99ee 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1227,10 +1227,10 @@ github.com/smartcontractkit/chain-selectors v1.0.36 h1:KSpO8I+JOiuyN4FuXsV471sPo github.com/smartcontractkit/chain-selectors v1.0.36/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-20250122132616-0b8ae69756db h1:W5r1QLevaqmbGimB9F3JMJyiw/qTXRATFi5DbmzEVNc= -github.com/smartcontractkit/chainlink-ccip v0.0.0-20250122132616-0b8ae69756db/go.mod h1:UEnHaxkUsfreeA7rR45LMmua1Uen95tOFUR8/AI9BAo= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250122150024-c1b7ba41ee90 h1:zLARR/tDnpp471QWvDCnUFB5WK9XzP7dZTqIBQjUMNM= -github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250122150024-c1b7ba41ee90/go.mod h1:Bmwq4lNb5tE47sydN0TKetcLEGbgl+VxHEWp4S0LI60= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250127195852-3ea463336e7f h1:u09xYqvXIikqspsnTba8EDyif1eoWuFbZG+iCFCj118= +github.com/smartcontractkit/chainlink-ccip v0.0.0-20250127195852-3ea463336e7f/go.mod h1:UEnHaxkUsfreeA7rR45LMmua1Uen95tOFUR8/AI9BAo= +github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250127195852-3ea463336e7f h1:6YomtETcLQzuAWOYwsWkApmEV6Wj1q0fTHJe/WRAJgE= +github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20250127195852-3ea463336e7f/go.mod h1:Bmwq4lNb5tE47sydN0TKetcLEGbgl+VxHEWp4S0LI60= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250121163309-3e179a73cb92 h1:9zmJi4TctSNvmVdmRh2UpbNRDnrWKYn4o+PZDAIhqqc= github.com/smartcontractkit/chainlink-common v0.4.2-0.20250121163309-3e179a73cb92/go.mod h1:V3BHfvLnQNBUoZ4bGjD29ZPhyzPE++DkYkhvPb9tcRs= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e h1:PRoeby6ZlTuTkv2f+7tVU4+zboTfRzI+beECynF4JQ0= diff --git a/integration-tests/relayinterface/lookups_test.go b/integration-tests/relayinterface/lookups_test.go index 4b0f672dc..221b08652 100644 --- a/integration-tests/relayinterface/lookups_test.go +++ b/integration-tests/relayinterface/lookups_test.go @@ -1,7 +1,6 @@ package relayinterface import ( - "reflect" "testing" "time" @@ -30,6 +29,8 @@ type TestAccountArgs struct { Inner InnerAccountArgs } +var testContractIDL = chainwriter.FetchTestContractIDL() + func TestAccountContant(t *testing.T) { t.Run("AccountConstant resolves valid address", func(t *testing.T) { expectedAddr := chainwriter.GetRandomPubKey(t) @@ -46,7 +47,7 @@ func TestAccountContant(t *testing.T) { IsSigner: true, IsWritable: true, } - result, err := constantConfig.Resolve(tests.Context(t), nil, nil, nil) + result, err := constantConfig.Resolve(tests.Context(t), nil, nil, nil, testContractIDL) require.NoError(t, err) require.Equal(t, expectedMeta, result) }) @@ -74,7 +75,7 @@ func TestAccountLookups(t *testing.T) { IsSigner: chainwriter.MetaBool{Value: true}, IsWritable: chainwriter.MetaBool{Value: true}, } - result, err := lookupConfig.Resolve(ctx, testArgs, nil, nil) + result, err := lookupConfig.Resolve(ctx, testArgs, nil, nil, testContractIDL) require.NoError(t, err) require.Equal(t, expectedMeta, result) }) @@ -108,7 +109,7 @@ func TestAccountLookups(t *testing.T) { IsSigner: chainwriter.MetaBool{Value: true}, IsWritable: chainwriter.MetaBool{Value: true}, } - result, err := lookupConfig.Resolve(ctx, testArgs, nil, nil) + result, err := lookupConfig.Resolve(ctx, testArgs, nil, nil, testContractIDL) require.NoError(t, err) for i, meta := range result { require.Equal(t, expectedMeta[i], meta) @@ -129,7 +130,7 @@ func TestAccountLookups(t *testing.T) { IsSigner: chainwriter.MetaBool{Value: true}, IsWritable: chainwriter.MetaBool{Value: true}, } - _, err := lookupConfig.Resolve(ctx, testArgs, nil, nil) + _, err := lookupConfig.Resolve(ctx, testArgs, nil, nil, testContractIDL) require.Error(t, err) }) @@ -159,7 +160,7 @@ func TestAccountLookups(t *testing.T) { }, } - result, err := lookupConfig.Resolve(ctx, args, nil, nil) + result, err := lookupConfig.Resolve(ctx, args, nil, nil, testContractIDL) require.NoError(t, err) for i, meta := range result { @@ -197,7 +198,7 @@ func TestAccountLookups(t *testing.T) { Bitmaps: []uint64{5, 3}, } - _, err := lookupConfig.Resolve(ctx, args, nil, nil) + _, err := lookupConfig.Resolve(ctx, args, nil, nil, testContractIDL) require.Contains(t, err.Error(), "bitmap value is not a single value") }) @@ -224,7 +225,7 @@ func TestAccountLookups(t *testing.T) { }, } - _, err := lookupConfig.Resolve(ctx, args, nil, nil) + _, err := lookupConfig.Resolve(ctx, args, nil, nil, testContractIDL) require.Contains(t, err.Error(), "error reading bitmap from location") }) @@ -251,7 +252,7 @@ func TestAccountLookups(t *testing.T) { }, } - _, err := lookupConfig.Resolve(ctx, args, nil, nil) + _, err := lookupConfig.Resolve(ctx, args, nil, nil, testContractIDL) require.Contains(t, err.Error(), "invalid value format at path") }) } @@ -284,7 +285,7 @@ func TestPDALookups(t *testing.T) { IsWritable: true, } - result, err := pdaLookup.Resolve(ctx, nil, nil, nil) + result, err := pdaLookup.Resolve(ctx, nil, nil, nil, testContractIDL) require.NoError(t, err) require.Equal(t, expectedMeta, result) }) @@ -319,7 +320,7 @@ func TestPDALookups(t *testing.T) { "another_seed": seed2, } - result, err := pdaLookup.Resolve(ctx, args, nil, nil) + result, err := pdaLookup.Resolve(ctx, args, nil, nil, testContractIDL) require.NoError(t, err) require.Equal(t, expectedMeta, result) }) @@ -339,7 +340,7 @@ func TestPDALookups(t *testing.T) { "test_seed": []byte("data"), } - _, err := pdaLookup.Resolve(ctx, args, nil, nil) + _, err := pdaLookup.Resolve(ctx, args, nil, nil, testContractIDL) require.Error(t, err) require.Contains(t, err.Error(), "key not found") }) @@ -375,7 +376,7 @@ func TestPDALookups(t *testing.T) { "another_seed": seed2, } - result, err := pdaLookup.Resolve(ctx, args, nil, nil) + result, err := pdaLookup.Resolve(ctx, args, nil, nil, testContractIDL) require.NoError(t, err) require.Equal(t, expectedMeta, result) }) @@ -413,7 +414,7 @@ func TestPDALookups(t *testing.T) { "array_seed": arraySeed, } - result, err := pdaLookup.Resolve(ctx, args, nil, nil) + result, err := pdaLookup.Resolve(ctx, args, nil, nil, testContractIDL) require.NoError(t, err) require.Equal(t, expectedMeta, result) }) @@ -453,7 +454,7 @@ func TestPDALookups(t *testing.T) { "seed2": arraySeed2, } - result, err := pdaLookup.Resolve(ctx, args, nil, nil) + result, err := pdaLookup.Resolve(ctx, args, nil, nil, testContractIDL) require.NoError(t, err) require.Equal(t, expectedMeta, result) }) @@ -489,7 +490,7 @@ func TestLookupTables(t *testing.T) { DerivedLookupTables: nil, StaticLookupTables: []solana.PublicKey{table}, } - _, staticTableMap, resolveErr := cw.ResolveLookupTables(ctx, nil, lookupConfig) + _, staticTableMap, resolveErr := cw.ResolveLookupTables(ctx, nil, lookupConfig, testContractIDL) require.NoError(t, resolveErr) require.Equal(t, pubKeys, staticTableMap[table]) }) @@ -510,7 +511,7 @@ func TestLookupTables(t *testing.T) { }, StaticLookupTables: nil, } - derivedTableMap, _, resolveErr := cw.ResolveLookupTables(ctx, nil, lookupConfig) + derivedTableMap, _, resolveErr := cw.ResolveLookupTables(ctx, nil, lookupConfig, testContractIDL) require.NoError(t, resolveErr) addresses, ok := derivedTableMap["DerivedTable"][table.String()] @@ -538,7 +539,7 @@ func TestLookupTables(t *testing.T) { StaticLookupTables: nil, } - _, _, err = cw.ResolveLookupTables(ctx, nil, lookupConfig) + _, _, err = cw.ResolveLookupTables(ctx, nil, lookupConfig, testContractIDL) require.Error(t, err) require.Contains(t, err.Error(), "error fetching account info for table") // Example error message }) @@ -551,7 +552,7 @@ func TestLookupTables(t *testing.T) { StaticLookupTables: []solana.PublicKey{invalidTable}, } - _, _, err = cw.ResolveLookupTables(ctx, nil, lookupConfig) + _, _, err = cw.ResolveLookupTables(ctx, nil, lookupConfig, testContractIDL) require.Error(t, err) require.Contains(t, err.Error(), "error fetching account info for table") // Example error message }) @@ -579,7 +580,7 @@ func TestLookupTables(t *testing.T) { }, } - derivedTableMap, _, err := cw.ResolveLookupTables(ctx, testArgs, lookupConfig) + derivedTableMap, _, err := cw.ResolveLookupTables(ctx, testArgs, lookupConfig, testContractIDL) require.NoError(t, err) addresses, ok := derivedTableMap["DerivedTable"][table.String()] @@ -615,7 +616,7 @@ func TestLookupTables(t *testing.T) { IsSigner: false, IsWritable: false, InternalField: chainwriter.InternalField{ - Type: reflect.TypeOf(chainwriter.DataAccount{}), + TypeName: "LookupTableDataAccount", Location: "LookupTable", }, }, @@ -624,7 +625,7 @@ func TestLookupTables(t *testing.T) { StaticLookupTables: nil, } - derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupConfig) + derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupConfig, testContractIDL) require.NoError(t, err) addresses, ok := derivedTableMap["DerivedTable"][lookupTable.String()] diff --git a/pkg/solana/chainwriter/ccip_example_config.go b/pkg/solana/chainwriter/ccip_example_config.go index 8b95e1efe..4be8467df 100644 --- a/pkg/solana/chainwriter/ccip_example_config.go +++ b/pkg/solana/chainwriter/ccip_example_config.go @@ -1,10 +1,8 @@ package chainwriter import ( - "reflect" - "github.com/gagliardetto/solana-go" - "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router" + "github.com/smartcontractkit/chainlink-common/pkg/codec" ) func TestConfig() { @@ -20,10 +18,17 @@ func TestConfig() { executionReportSingleChainIDL := `{"name":"ExecutionReportSingleChain","type":{"kind":"struct","fields":[{"name":"source_chain_selector","type":"u64"},{"name":"message","type":{"defined":"Any2SolanaRampMessage"}},{"name":"root","type":{"array":["u8",32]}},{"name":"proofs","type":{"vec":{"array":["u8",32]}}}]}},{"name":"Any2SolanaRampMessage","type":{"kind":"struct","fields":[{"name":"header","type":{"defined":"RampMessageHeader"}},{"name":"sender","type":{"vec":"u8"}},{"name":"data","type":{"vec":"u8"}},{"name":"receiver","type":{"array":["u8",32]}},{"name":"extra_args","type":{"defined":"SolanaExtraArgs"}}]}},{"name":"RampMessageHeader","type":{"kind":"struct","fields":[{"name":"message_id","type":{"array":["u8",32]}},{"name":"source_chain_selector","type":"u64"},{"name":"dest_chain_selector","type":"u64"},{"name":"sequence_number","type":"u64"},{"name":"nonce","type":"u64"}]}},{"name":"SolanaExtraArgs","type":{"kind":"struct","fields":[{"name":"compute_units","type":"u32"},{"name":"allow_out_of_order_execution","type":"bool"}]}}` executeConfig := MethodConfig{ - FromAddress: fromAddress, - InputModifications: nil, - ChainSpecificName: "execute", - ArgsTransform: "CCIP", + FromAddress: fromAddress, + InputModifications: []codec.ModifierConfig{ + &codec.RenameModifierConfig{ + Fields: map[string]string{"ReportContextByteWords": "ReportContext"}, + }, + &codec.RenameModifierConfig{ + Fields: map[string]string{"RawExecutionReport": "Report"}, + }, + }, + ChainSpecificName: "execute", + ArgsTransform: "CCIP", // LookupTables are on-chain stores of accounts. They can be used in two ways: // 1. As a way to store a list of accounts that are all associated together (i.e. Token State registry) // 2. To compress the transactions in a TX and reduce the size of the TX. (The traditional way) @@ -49,12 +54,12 @@ func TestConfig() { // Seeds would be used if the user needed to look up addresses to use as seeds, which isn't the case here. Seeds: []Seed{ {Static: []byte("token_admin_registry")}, - {Dynamic: AccountLookup{Location: "AbstractReport.Message.TokenAmounts.DestTokenAddress"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.TokenAmounts.DestTokenAddress"}}, }, IsSigner: false, IsWritable: false, InternalField: InternalField{ - Type: reflect.TypeOf(ccip_router.TokenAdminRegistry{}), + TypeName: "TokenAdminRegistry", Location: "LookupTable", }, }, @@ -102,7 +107,7 @@ func TestConfig() { // Similar to the TokenAdminRegistry above, the user is looking up PDA accounts based on the dest tokens. Seeds: []Seed{ {Static: []byte("source_chain_state")}, - {Dynamic: AccountLookup{Location: "AbstractReport.Message.Header.DestChainSelector"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.Header.DestChainSelector"}}, }, IsSigner: false, IsWritable: false, @@ -116,10 +121,10 @@ func TestConfig() { }, Seeds: []Seed{ {Static: []byte("commit_report")}, - {Dynamic: AccountLookup{Location: "AbstractReport.Message.Header.DestChainSelector"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.Header.DestChainSelector"}}, {Dynamic: AccountLookup{ // The seed is the merkle root of the report, as passed into the input params. - Location: "Info.MerkleRootChain.MerkleRoot", + Location: "Info.MerkleRoots.MerkleRoot", }}, }, IsSigner: false, @@ -173,8 +178,8 @@ func TestConfig() { // User specified accounts - formatted as AccountMeta AccountLookup{ Name: "UserAccounts", - Location: "AbstractReport.Message.ExtraArgs.Accounts", - IsWritable: MetaBool{BitmapLocation: "AbstractReport.Message.ExtraArgs.AccountsBitmap"}, + Location: "Info.AbstractReports.Message.ExtraArgsDecoded.Accounts", + IsWritable: MetaBool{BitmapLocation: "Info.AbstractReports.Message.ExtraArgsDecoded.IsWritableBitmap"}, IsSigner: MetaBool{Value: false}, }, // PDA Account Lookup - Based on an account lookup and an address lookup @@ -185,14 +190,14 @@ func TestConfig() { }, Seeds: []Seed{ // receiver address - {Dynamic: AccountLookup{Location: "AbstractReport.Message.Receiver"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.Receiver"}}, // token programs {Dynamic: AccountsFromLookupTable{ LookupTableName: "PoolLookupTable", IncludeIndexes: []int{6}, }}, // mint - {Dynamic: AccountLookup{Location: "AbstractReport.Message.TokenAmounts.DestTokenAddress"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.TokenAmounts.DestTokenAddress"}}, }, IsSigner: false, IsWritable: false, @@ -212,7 +217,7 @@ func TestConfig() { IncludeIndexes: []int{6}, }}, // mint - {Dynamic: AccountLookup{Location: "AbstractReport.Message.TokenAmounts.DestTokenAddress"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.TokenAmounts.DestTokenAddress"}}, }, IsSigner: false, IsWritable: false, @@ -226,8 +231,8 @@ func TestConfig() { // Similar to the TokenAdminRegistry above, the user is looking up PDA accounts based on the dest tokens. Seeds: []Seed{ {Static: []byte("ccip_tokenpool_billing")}, - {Dynamic: AccountLookup{Location: "AbstractReport.Message.Header.DestChainSelector"}}, - {Dynamic: AccountLookup{Location: "AbstractReport.Message.TokenAmounts.DestTokenAddress"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.Header.DestChainSelector"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.TokenAmounts.DestTokenAddress"}}, }, IsSigner: false, IsWritable: false, @@ -242,8 +247,8 @@ func TestConfig() { }, Seeds: []Seed{ {Static: []byte("ccip_tokenpool_chainconfig")}, - {Dynamic: AccountLookup{Location: "AbstractReport.Message.Header.DestChainSelector"}}, - {Dynamic: AccountLookup{Location: "AbstractReport.Message.TokenAmounts.DestTokenAddress"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.Header.DestChainSelector"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.TokenAmounts.DestTokenAddress"}}, }, IsSigner: false, IsWritable: false, @@ -260,9 +265,16 @@ func TestConfig() { } commitConfig := MethodConfig{ - FromAddress: fromAddress, - InputModifications: nil, - ChainSpecificName: "commit", + FromAddress: fromAddress, + InputModifications: []codec.ModifierConfig{ + &codec.RenameModifierConfig{ + Fields: map[string]string{"ReportContextByteWords": "ReportContext"}, + }, + &codec.RenameModifierConfig{ + Fields: map[string]string{"RawReport": "Report"}, + }, + }, + ChainSpecificName: "commit", LookupTables: LookupTables{ StaticLookupTables: []solana.PublicKey{ commonAddressesLookupTable, @@ -290,7 +302,7 @@ func TestConfig() { // Similar to the TokenAdminRegistry above, the user is looking up PDA accounts based on the dest tokens. Seeds: []Seed{ {Static: []byte("source_chain_state")}, - {Dynamic: AccountLookup{Location: "MerkleRoot.DestChainSelector"}}, + {Dynamic: AccountLookup{Location: "Info.MerkleRoots.ChainSel"}}, }, IsSigner: false, IsWritable: true, @@ -306,10 +318,10 @@ func TestConfig() { }, Seeds: []Seed{ {Static: []byte("commit_report")}, - {Dynamic: AccountLookup{Location: "AbstractReport.MerkleRoots.ChainSel"}}, + {Dynamic: AccountLookup{Location: "Info.MerkleRoots.ChainSel"}}, {Dynamic: AccountLookup{ // The seed is the merkle root of the report, as passed into the input params. - Location: "AbstractReport.MerkleRoots.MerkleRoot", + Location: "Info.MerkleRoots.MerkleRoot", }}, }, IsSigner: false, @@ -356,7 +368,7 @@ func TestConfig() { }, Seeds: []Seed{ {Static: []byte("fee_billing_token_config")}, - {Dynamic: AccountLookup{Location: "AbstractReport.PriceUpdatesTokenPriceUpdates.TokenPrice.TokenID"}}, + {Dynamic: AccountLookup{Location: "Info.TokenPrices.TokenID"}}, }, IsSigner: false, IsWritable: false, @@ -369,7 +381,7 @@ func TestConfig() { }, Seeds: []Seed{ {Static: []byte("dest_chain_state")}, - {Dynamic: AccountLookup{Location: "AbstractReport.MerkleRoots.ChainSel"}}, + {Dynamic: AccountLookup{Location: "Info.MerkleRoots.ChainSel"}}, }, IsSigner: false, IsWritable: false, diff --git a/pkg/solana/chainwriter/chain_writer.go b/pkg/solana/chainwriter/chain_writer.go index 4195d5125..1bf8a3a8b 100644 --- a/pkg/solana/chainwriter/chain_writer.go +++ b/pkg/solana/chainwriter/chain_writer.go @@ -144,10 +144,10 @@ for Solana transactions. It handles constant addresses, dynamic lookups, program ### Error Handling: - Errors are wrapped with the `debugID` for easier tracing. */ -func GetAddresses(ctx context.Context, args any, accounts []Lookup, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader) ([]*solana.AccountMeta, error) { +func GetAddresses(ctx context.Context, args any, accounts []Lookup, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader, idl string) ([]*solana.AccountMeta, error) { var addresses []*solana.AccountMeta for _, accountConfig := range accounts { - meta, err := accountConfig.Resolve(ctx, args, derivedTableMap, reader) + meta, err := accountConfig.Resolve(ctx, args, derivedTableMap, reader, idl) if err != nil { return nil, err } @@ -258,13 +258,13 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra } // Fetch derived and static table maps - derivedTableMap, staticTableMap, err := s.ResolveLookupTables(ctx, args, methodConfig.LookupTables) + derivedTableMap, staticTableMap, err := s.ResolveLookupTables(ctx, args, methodConfig.LookupTables, programConfig.IDL) if err != nil { return errorWithDebugID(fmt.Errorf("error getting lookup tables: %w", err), debugID) } // Resolve account metas - accounts, err := GetAddresses(ctx, args, methodConfig.Accounts, derivedTableMap, s.reader) + accounts, err := GetAddresses(ctx, args, methodConfig.Accounts, derivedTableMap, s.reader, programConfig.IDL) if err != nil { return errorWithDebugID(fmt.Errorf("error resolving account addresses: %w", err), debugID) } @@ -348,7 +348,7 @@ func (s *SolanaChainWriterService) GetFeeComponents(ctx context.Context) (*types }, nil } -func (s *SolanaChainWriterService) ResolveLookupTables(ctx context.Context, args any, lookupTables LookupTables) (map[string]map[string][]*solana.AccountMeta, map[solana.PublicKey]solana.PublicKeySlice, error) { +func (s *SolanaChainWriterService) ResolveLookupTables(ctx context.Context, args any, lookupTables LookupTables, idl string) (map[string]map[string][]*solana.AccountMeta, map[solana.PublicKey]solana.PublicKeySlice, error) { derivedTableMap := make(map[string]map[string][]*solana.AccountMeta) staticTableMap := make(map[solana.PublicKey]solana.PublicKeySlice) @@ -356,7 +356,7 @@ func (s *SolanaChainWriterService) ResolveLookupTables(ctx context.Context, args for _, derivedLookup := range lookupTables.DerivedLookupTables { // Load the lookup table - note: This could be multiple tables if the lookup is a PDALookups that resolves to more // than one address - lookupTableMap, err := s.loadTable(ctx, args, derivedLookup) + lookupTableMap, err := s.loadTable(ctx, args, derivedLookup, idl) if err != nil { return nil, nil, fmt.Errorf("error loading derived lookup table: %w", err) } @@ -384,9 +384,9 @@ func (s *SolanaChainWriterService) ResolveLookupTables(ctx context.Context, args return derivedTableMap, staticTableMap, nil } -func (s *SolanaChainWriterService) loadTable(ctx context.Context, args any, rlt DerivedLookupTable) (map[string]map[string][]*solana.AccountMeta, error) { +func (s *SolanaChainWriterService) loadTable(ctx context.Context, args any, rlt DerivedLookupTable, idl string) (map[string]map[string][]*solana.AccountMeta, error) { // Resolve all addresses specified by the identifier - lookupTableAddresses, err := GetAddresses(ctx, args, []Lookup{rlt.Accounts}, nil, s.reader) + lookupTableAddresses, err := GetAddresses(ctx, args, []Lookup{rlt.Accounts}, nil, s.reader, idl) if err != nil { return nil, fmt.Errorf("error resolving addresses for lookup table: %w", err) } diff --git a/pkg/solana/chainwriter/chain_writer_test.go b/pkg/solana/chainwriter/chain_writer_test.go index c017fd2b1..01ec41881 100644 --- a/pkg/solana/chainwriter/chain_writer_test.go +++ b/pkg/solana/chainwriter/chain_writer_test.go @@ -2,11 +2,11 @@ package chainwriter_test import ( "bytes" + _ "embed" "encoding/json" "errors" + "fmt" "math/big" - "os" - "reflect" "testing" ag_binary "github.com/gagliardetto/binary" @@ -39,6 +39,7 @@ type Arguments struct { } var ccipRouterIDL = idl.FetchCCIPRouterIDL() +var testContractIDL = chainwriter.FetchTestContractIDL() func TestChainWriter_GetAddresses(t *testing.T) { ctx := tests.Context(t) @@ -105,7 +106,7 @@ func TestChainWriter_GetAddresses(t *testing.T) { IsSigner: derivedTablePdaLookupMeta.IsSigner, IsWritable: derivedTablePdaLookupMeta.IsWritable, InternalField: chainwriter.InternalField{ - Type: reflect.TypeOf(chainwriter.DataAccount{}), + TypeName: "LookupTableDataAccount", Location: "LookupTable", }, }, @@ -158,11 +159,11 @@ func TestChainWriter_GetAddresses(t *testing.T) { } // Fetch derived table map - derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig, testContractIDL) require.NoError(t, err) // Resolve account metas - accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw, testContractIDL) require.NoError(t, err) // account metas should be returned in the same order as the provided account lookup configs @@ -202,11 +203,11 @@ func TestChainWriter_GetAddresses(t *testing.T) { } // Fetch derived table map - derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig, testContractIDL) require.NoError(t, err) // Resolve account metas - accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw, testContractIDL) require.NoError(t, err) require.Len(t, accounts, 2) @@ -226,11 +227,11 @@ func TestChainWriter_GetAddresses(t *testing.T) { } // Fetch derived table map - derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + derivedTableMap, _, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig, testContractIDL) require.NoError(t, err) // Resolve account metas - accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw, testContractIDL) require.NoError(t, err) require.Len(t, accounts, 3) @@ -292,7 +293,7 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { IsSigner: true, IsWritable: true, InternalField: chainwriter.InternalField{ - Type: reflect.TypeOf(chainwriter.DataAccount{}), + TypeName: "LookupTableDataAccount", Location: "LookupTable", }, }, @@ -309,7 +310,7 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { IsSigner: true, IsWritable: true, InternalField: chainwriter.InternalField{ - Type: reflect.TypeOf(chainwriter.DataAccount{}), + TypeName: "LookupTableDataAccount", Location: "LookupTable", }, }, @@ -332,11 +333,11 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { } // Fetch derived table map - derivedTableMap, staticTableMap, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + derivedTableMap, staticTableMap, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig, testContractIDL) require.NoError(t, err) // Resolve account metas - accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw, testContractIDL) require.NoError(t, err) // Filter the lookup table addresses based on which accounts are actually used @@ -354,11 +355,11 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { accountLookupConfig := []chainwriter.Lookup{} // Fetch derived table map - derivedTableMap, staticTableMap, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + derivedTableMap, staticTableMap, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig, testContractIDL) require.NoError(t, err) // Resolve account metas - accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw, testContractIDL) require.NoError(t, err) // Filter the lookup table addresses based on which accounts are actually used @@ -377,11 +378,11 @@ func TestChainWriter_FilterLookupTableAddresses(t *testing.T) { } // Fetch derived table map - derivedTableMap, staticTableMap, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig) + derivedTableMap, staticTableMap, err := cw.ResolveLookupTables(ctx, args, lookupTableConfig, testContractIDL) require.NoError(t, err) // Resolve account metas - accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw) + accounts, err := chainwriter.GetAddresses(ctx, args, accountLookupConfig, derivedTableMap, rw, testContractIDL) require.NoError(t, err) // Filter the lookup table addresses based on which accounts are actually used @@ -427,11 +428,6 @@ func TestChainWriter_SubmitTransaction(t *testing.T) { staticLookupKeys := chainwriter.CreateTestPubKeys(t, 2) mockFetchLookupTableAddresses(t, rw, staticLookupTablePubkey, staticLookupKeys) - data, err := os.ReadFile("testContractIDL.json") - require.NoError(t, err) - - testContractIDLJson := string(data) - cwConfig := chainwriter.ChainWriterConfig{ Programs: map[string]chainwriter.ProgramConfig{ "contract_reader_interface": { @@ -453,7 +449,7 @@ func TestChainWriter_SubmitTransaction(t *testing.T) { IsSigner: false, IsWritable: false, InternalField: chainwriter.InternalField{ - Type: reflect.TypeOf(chainwriter.DataAccount{}), + TypeName: "LookupTableDataAccount", Location: "LookupTable", }, }, @@ -506,7 +502,7 @@ func TestChainWriter_SubmitTransaction(t *testing.T) { ArgsTransform: "", }, }, - IDL: testContractIDLJson, + IDL: testContractIDL, }, }, } @@ -608,16 +604,9 @@ func TestChainWriter_CCIPRouter(t *testing.T) { routerAddr := chainwriter.GetRandomPubKey(t) destTokenAddr := chainwriter.GetRandomPubKey(t) - pda, _, err := solana.FindProgramAddress([][]byte{[]byte("token_admin_registry"), destTokenAddr.Bytes()}, routerAddr) - require.NoError(t, err) - - lookupTable := mockTokenAdminRegistryLookupTable(t, rw, pda) - poolKeys := []solana.PublicKey{destTokenAddr} poolKeys = append(poolKeys, chainwriter.CreateTestPubKeys(t, 3)...) - mockFetchLookupTableAddresses(t, rw, lookupTable, poolKeys) - // simplified CCIP Config - does not contain full account list ccipCWConfig := chainwriter.ChainWriterConfig{ Programs: map[string]chainwriter.ProgramConfig{ @@ -630,7 +619,7 @@ func TestChainWriter_CCIPRouter(t *testing.T) { Fields: map[string]string{"ReportContextByteWords": "ReportContext"}, }, &codec.RenameModifierConfig{ - Fields: map[string]string{"ExecutionReport": "AbstractReport"}, + Fields: map[string]string{"RawExecutionReport": "Report"}, }, }, ChainSpecificName: "execute", @@ -667,6 +656,34 @@ func TestChainWriter_CCIPRouter(t *testing.T) { }, }, }, + "commit": { + FromAddress: admin.String(), + InputModifications: []codec.ModifierConfig{ + &codec.RenameModifierConfig{ + Fields: map[string]string{"ReportContextByteWords": "ReportContext"}, + }, + &codec.RenameModifierConfig{ + Fields: map[string]string{"RawReport": "Report"}, + }, + }, + ChainSpecificName: "commit", + ArgsTransform: "", + LookupTables: chainwriter.LookupTables{}, + Accounts: []chainwriter.Lookup{ + chainwriter.AccountConstant{ + Name: "testAcc1", + Address: chainwriter.GetRandomPubKey(t).String(), + }, + chainwriter.AccountConstant{ + Name: "testAcc2", + Address: chainwriter.GetRandomPubKey(t).String(), + }, + chainwriter.AccountConstant{ + Name: "testAcc3", + Address: chainwriter.GetRandomPubKey(t).String(), + }, + }, + }, }, IDL: ccipRouterIDL, }, @@ -677,11 +694,18 @@ func TestChainWriter_CCIPRouter(t *testing.T) { cw, err := chainwriter.NewSolanaChainWriterService(testutils.NewNullLogger(), rw, txm, ge, ccipCWConfig) require.NoError(t, err) - t.Run("ArgsTransform works", func(t *testing.T) { - txID := uuid.NewString() + t.Run("CCIP execute is encoded successfully and ArgsTransform is applied correctly.", func(t *testing.T) { recentBlockHash := solana.Hash{} - rw.On("LatestBlockhash", mock.Anything).Return(&rpc.GetLatestBlockhashResult{Value: &rpc.LatestBlockhashResult{Blockhash: recentBlockHash, LastValidBlockHeight: uint64(100)}}, nil).Once() + + pda, _, err := solana.FindProgramAddress([][]byte{[]byte("token_admin_registry"), destTokenAddr.Bytes()}, routerAddr) + require.NoError(t, err) + + lookupTable := mockTokenAdminRegistryLookupTable(t, rw, pda) + + 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:] @@ -697,63 +721,78 @@ func TestChainWriter_CCIPRouter(t *testing.T) { return true }), &txID, mock.Anything).Return(nil).Once() - abstractReport := ccip_router.ExecutionReportSingleChain{ - SourceChainSelector: 1, - Message: ccip_router.Any2SVMRampMessage{ - Header: ccip_router.RampMessageHeader{ - MessageId: ccipocr3.Bytes32{0x1}, - SourceChainSelector: 1, - DestChainSelector: 2, - SequenceNumber: 1, - Nonce: 0, - }, - Sender: admin.Bytes(), - Data: ccipocr3.Bytes{0x1}, - LogicReceiver: chainwriter.GetRandomPubKey(t), - TokenReceiver: admin, - TokenAmounts: []ccip_router.Any2SVMTokenTransfer{ - { - SourcePoolAddress: chainwriter.GetRandomPubKey(t).Bytes(), - DestTokenAddress: destTokenAddr, - ExtraData: ccipocr3.Bytes{0x1}, - Amount: ccip_router.CrossChainAmount{LeBytes: [32]uint8{0x1}}, - DestGasAmount: 2, - }, - }, - ExtraArgs: ccip_router.SVMExtraArgs{ - ComputeUnits: 1, - IsWritableBitmap: 6, - Accounts: []solana.PublicKey{ - chainwriter.GetRandomPubKey(t), + // stripped back report just for purposes of example + abstractReport := ccipocr3.ExecutePluginReportSingleChain{ + Messages: []ccipocr3.Message{ + { + TokenAmounts: []ccipocr3.RampTokenAmount{ + { + DestTokenAddress: destTokenAddr.Bytes(), + }, }, }, - OnRampAddress: []byte{0x1}, }, - OffchainTokenData: [][]byte{{0x1}}, - Proofs: [][32]byte{{0x1}}, } // Marshal the abstract report to json just for testing purposes. - encodededReport, err := json.Marshal(abstractReport) + encodedReport, err := json.Marshal(abstractReport) require.NoError(t, err) args := chainwriter.ReportPreTransform{ - ReportContext: [3][32]uint8{{0x01, 0x02, 0x03}}, - Report: encodededReport, + ReportContext: [2][32]byte{{0x01}, {0x02}}, + Report: encodedReport, Info: ccipocr3.ExecuteReportInfo{ - { - ChainSel: 1, - OnRampAddress: chainwriter.GetRandomPubKey(t).Bytes(), - SeqNumsRange: ccipocr3.NewSeqNumRange(1, 2), - MerkleRoot: [32]byte{0x01, 0x02, 0x03}, - }, + MerkleRoots: []ccipocr3.MerkleRootChain{}, + AbstractReports: []ccipocr3.ExecutePluginReportSingleChain{abstractReport}, }, - AbstractReport: abstractReport, } submitErr := cw.SubmitTransaction(ctx, "ccip_router", "execute", args, txID, routerAddr.String(), nil, nil) require.NoError(t, submitErr) }) + + t.Run("CCIP commit is encoded successfully", func(t *testing.T) { + recentBlockHash := solana.Hash{} + rw.On("LatestBlockhash", mock.Anything).Return(&rpc.GetLatestBlockhashResult{Value: &rpc.LatestBlockhashResult{Blockhash: recentBlockHash, LastValidBlockHeight: uint64(100)}}, nil).Once() + + type CommitArgs struct { + ReportContext [2][32]byte + Report []byte + Rs [][32]byte + Ss [][32]byte + RawVs [32]byte + Info ccipocr3.CommitReportInfo + } + + txID := uuid.NewString() + + // TODO: Replace with actual type from ccipocr3 + args := CommitArgs{ + ReportContext: [2][32]byte{{0x01}, {0x02}}, + Report: []byte{0x01, 0x02}, + Rs: [][32]byte{{0x01, 0x02}}, + Ss: [][32]byte{{0x01, 0x02}}, + RawVs: [32]byte{0x01, 0x02}, + Info: ccipocr3.CommitReportInfo{ + RemoteF: 1, + MerkleRoots: []ccipocr3.MerkleRootChain{}, + }, + } + + 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 + dec := ag_binary.NewBorshDecoder(payload) + err := dec.Decode(&decoded) + require.NoError(t, err) + fmt.Println(decoded.ReportContextByteWords) + return true + }), &txID, mock.Anything).Return(nil).Once() + + submitErr := cw.SubmitTransaction(ctx, "ccip_router", "commit", args, txID, routerAddr.String(), nil, nil) + require.NoError(t, submitErr) + }) } func TestChainWriter_GetTransactionStatus(t *testing.T) { @@ -864,13 +903,14 @@ func mustFindPdaProgramAddress(t *testing.T, seeds [][]byte, programID solana.Pu func mockDataAccountLookupTable(t *testing.T, rw *clientmocks.ReaderWriter, pda solana.PublicKey) solana.PublicKey { lookupTablePubkey := chainwriter.GetRandomPubKey(t) dataAccount := chainwriter.DataAccount{ - Discriminator: [8]byte{}, Version: 1, Administrator: chainwriter.GetRandomPubKey(t), PendingAdministrator: chainwriter.GetRandomPubKey(t), LookupTable: lookupTablePubkey, } dataAccountBytes := mustBorshEncodeStruct(t, dataAccount) + // codec will expect discriminator + dataAccountBytes = append([]byte{220, 119, 44, 40, 237, 41, 223, 7}, dataAccountBytes...) rw.On("GetAccountInfoWithOpts", mock.Anything, pda, mock.Anything).Return(&rpc.GetAccountInfoResult{ RPCContext: rpc.RPCContext{}, Value: &rpc.Account{Data: rpc.DataBytesOrJSONFromBytes(dataAccountBytes)}, diff --git a/pkg/solana/chainwriter/helpers.go b/pkg/solana/chainwriter/helpers.go index 37eaf45bf..4277c3a99 100644 --- a/pkg/solana/chainwriter/helpers.go +++ b/pkg/solana/chainwriter/helpers.go @@ -3,6 +3,7 @@ package chainwriter import ( "context" "crypto/sha256" + _ "embed" "encoding/binary" "errors" "fmt" @@ -28,13 +29,20 @@ type InnerArgs struct { } type DataAccount struct { - Discriminator [8]byte Version uint8 Administrator solana.PublicKey PendingAdministrator solana.PublicKey LookupTable solana.PublicKey } +//go:embed testContractIDL.json +var testContractIDL string + +// FetchCCIPRouterIDL returns +func FetchTestContractIDL() string { + return testContractIDL +} + // GetValuesAtLocation parses through nested types and arrays to find all locations of values func GetValuesAtLocation(args any, location string) ([][]byte, error) { var vals [][]byte @@ -45,6 +53,12 @@ func GetValuesAtLocation(args any, location string) ([][]byte, error) { return nil, err } for _, value := range addressList { + // Dereference if it's a pointer + rv := reflect.ValueOf(value) + if rv.Kind() == reflect.Ptr && !rv.IsNil() { + value = rv.Elem().Interface() + } + if byteArray, ok := value.([]byte); ok { vals = append(vals, byteArray) } else if address, ok := value.(solana.PublicKey); ok { @@ -55,6 +69,8 @@ func GetValuesAtLocation(args any, location string) ([][]byte, error) { vals = append(vals, buf) } else if addr, ok := value.(ccipocr3.UnknownAddress); ok { vals = append(vals, addr) + } else if arr, ok := value.([32]uint8); ok { + vals = append(vals, arr[:]) } else { return nil, fmt.Errorf("invalid value format at path: %s, type: %s", location, reflect.TypeOf(value).String()) } diff --git a/pkg/solana/chainwriter/lookups.go b/pkg/solana/chainwriter/lookups.go index cd939dc8e..36719538a 100644 --- a/pkg/solana/chainwriter/lookups.go +++ b/pkg/solana/chainwriter/lookups.go @@ -3,19 +3,19 @@ package chainwriter import ( "context" "encoding/binary" + "encoding/json" "fmt" - "reflect" - ag_binary "github.com/gagliardetto/binary" "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" + "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) + Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader, idl string) ([]*solana.AccountMeta, error) } // AccountConstant represents a fixed address, provided in Base58 format, converted into a `solana.PublicKey`. @@ -60,7 +60,8 @@ type PDALookups struct { } type InternalField struct { - Type reflect.Type + // must map directly to IDL type + TypeName string Location string } @@ -82,7 +83,7 @@ type AccountsFromLookupTable struct { IncludeIndexes []int } -func (ac AccountConstant) Resolve(_ context.Context, _ any, _ map[string]map[string][]*solana.AccountMeta, _ client.Reader) ([]*solana.AccountMeta, error) { +func (ac AccountConstant) Resolve(_ context.Context, _ any, _ map[string]map[string][]*solana.AccountMeta, _ client.Reader, _ string) ([]*solana.AccountMeta, error) { address, err := solana.PublicKeyFromBase58(ac.Address) if err != nil { return nil, fmt.Errorf("error getting account from constant: %w", err) @@ -96,7 +97,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(_ context.Context, args any, _ map[string]map[string][]*solana.AccountMeta, _ client.Reader, _ string) ([]*solana.AccountMeta, error) { derivedValues, err := GetValuesAtLocation(args, al.Location) if err != nil { return nil, fmt.Errorf("error getting account from lookup: %w", err) @@ -155,7 +156,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(_ context.Context, _ any, derivedTableMap map[string]map[string][]*solana.AccountMeta, _ client.Reader, _ string) ([]*solana.AccountMeta, error) { // Fetch the inner map for the specified lookup table name innerMap, ok := derivedTableMap[alt.LookupTableName] if !ok { @@ -185,8 +186,8 @@ func (alt AccountsFromLookupTable) Resolve(_ context.Context, _ any, derivedTabl return result, nil } -func (pda PDALookups) Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader) ([]*solana.AccountMeta, error) { - publicKeys, err := GetAddresses(ctx, args, []Lookup{pda.PublicKey}, derivedTableMap, reader) +func (pda PDALookups) Resolve(ctx context.Context, args any, derivedTableMap map[string]map[string][]*solana.AccountMeta, reader client.Reader, idl string) ([]*solana.AccountMeta, error) { + publicKeys, err := GetAddresses(ctx, args, []Lookup{pda.PublicKey}, derivedTableMap, reader, idl) if err != nil { return nil, fmt.Errorf("error getting public key for PDALookups: %w", err) } @@ -217,9 +218,26 @@ func (pda PDALookups) Resolve(ctx context.Context, args any, derivedTableMap map return nil, fmt.Errorf("error fetching account info for PDA account: %s, error: %w", accountMeta.PublicKey.String(), err) } - decoded, err := decodeBorshIntoType(accountInfo.GetBinary(), pda.InternalField.Type) + var idlCodec codec.IDL + if err = json.Unmarshal([]byte(idl), &idlCodec); err != nil { + return nil, fmt.Errorf("failed to unmarshal IDL for PDA: %s, error: %w", pda.Name, err) + } + + internalType := pda.InternalField.TypeName + + idlDef, err := codec.FindDefinitionFromIDL(codec.ChainConfigTypeAccountDef, internalType, idlCodec) + if err != nil { + return nil, fmt.Errorf("error finding definition for type %s: %w", internalType, err) + } + + input, err := codec.CreateCodecEntry(idlDef, internalType, idlCodec, nil) + if err != nil { + return nil, fmt.Errorf("failed to create codec entry for method %s, error: %w", internalType, err) + } + + decoded, _, err := input.Decode(accountInfo.Value.Data.GetBinary()) if err != nil { - return nil, fmt.Errorf("error decoding Borsh data dynamically: %w", err) + return nil, fmt.Errorf("error decoding account data: %w", err) } value, err := GetValuesAtLocation(decoded, pda.InternalField.Location) @@ -239,25 +257,6 @@ func (pda PDALookups) Resolve(ctx context.Context, args any, derivedTableMap map return result, nil } -func decodeBorshIntoType(data []byte, typ reflect.Type) (interface{}, error) { - // Ensure the type is a struct - if typ.Kind() != reflect.Struct { - return nil, fmt.Errorf("provided type is not a struct: %s", typ.Kind()) - } - - // Create a new instance of the type - instance := reflect.New(typ).Interface() - - // Decode using Borsh - err := ag_binary.NewBorshDecoder(data).Decode(instance) - if err != nil { - return nil, fmt.Errorf("error decoding Borsh data: %w", err) - } - - // Return the underlying value (not a pointer) - return reflect.ValueOf(instance).Elem().Interface(), nil -} - // getSeedBytesCombinations extracts the seeds for the PDALookups. // The return type is [][][]byte, where each element of the outer slice is // one combination of seeds. This handles the case where one seed can resolve @@ -298,7 +297,7 @@ func getSeedBytesCombinations( } } else { // Get address seeds from the lookup - seedAddresses, err := GetAddresses(ctx, args, []Lookup{dynamicSeed}, derivedTableMap, reader) + seedAddresses, err := GetAddresses(ctx, args, []Lookup{dynamicSeed}, derivedTableMap, reader, "") if err != nil { return nil, fmt.Errorf("error getting address seed: %w", err) } diff --git a/pkg/solana/chainwriter/transform_registry.go b/pkg/solana/chainwriter/transform_registry.go index 32c1e153c..f81af04bc 100644 --- a/pkg/solana/chainwriter/transform_registry.go +++ b/pkg/solana/chainwriter/transform_registry.go @@ -3,7 +3,6 @@ package chainwriter import ( "context" "fmt" - "reflect" "github.com/gagliardetto/solana-go" "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_router" @@ -13,14 +12,14 @@ import ( // TODO: make this type in the ccipocr3 package // TODO: consolidate Info and AbstractReport type ReportPreTransform struct { - ReportContext [3][32]byte + ReportContext [2][32]byte Report []byte Info ccipocr3.ExecuteReportInfo AbstractReport ccip_router.ExecutionReportSingleChain } type ReportPostTransform struct { - ReportContext [3][32]byte + ReportContext [2][32]byte Report []byte Info ccipocr3.ExecuteReportInfo AbstractReport ccip_router.ExecutionReportSingleChain @@ -51,19 +50,25 @@ func CCIPArgsTransform(ctx context.Context, cw *SolanaChainWriterService, args a }, Seeds: []Seed{ {Static: []byte("token_admin_registry")}, - {Dynamic: AccountLookup{Location: "AbstractReport.Message.TokenAmounts.DestTokenAddress"}}, + {Dynamic: AccountLookup{Location: "Info.AbstractReports.Messages.TokenAmounts.DestTokenAddress"}}, }, IsSigner: false, IsWritable: false, InternalField: InternalField{ - Type: reflect.TypeOf(ccip_router.TokenAdminRegistry{}), + TypeName: "TokenAdminRegistry", Location: "LookupTable", }, }, }, }, } - tableMap, _, err := cw.ResolveLookupTables(ctx, args, TokenPoolLookupTable) + + routerProgramConfig, ok := cw.config.Programs["ccip_router"] + if !ok { + return nil, fmt.Errorf("ccip_router program not found in config") + } + + tableMap, _, err := cw.ResolveLookupTables(ctx, args, TokenPoolLookupTable, routerProgramConfig.IDL) if err != nil { return nil, err }