diff --git a/integration-tests/relayinterface/chain_components_test.go b/integration-tests/relayinterface/chain_components_test.go index c00371db4..f63c38ef6 100644 --- a/integration-tests/relayinterface/chain_components_test.go +++ b/integration-tests/relayinterface/chain_components_test.go @@ -250,7 +250,6 @@ func RunChainWriterTests[T WrappedTestingT[T]](t T, it *SolanaChainComponentsInt 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) diff --git a/pkg/solana/chainwriter/chain_writer.go b/pkg/solana/chainwriter/chain_writer.go index a17c5d923..c423fad34 100644 --- a/pkg/solana/chainwriter/chain_writer.go +++ b/pkg/solana/chainwriter/chain_writer.go @@ -22,6 +22,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" + txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" "github.com/smartcontractkit/chainlink-solana/pkg/solana/utils" ) @@ -383,6 +384,7 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra // Filter the lookup table addresses based on which accounts are actually used filteredLookupTableMap := s.FilterLookupTableAddresses(accounts, derivedTableMap, staticTableMap) + computeLimit := uint32(0) // Transform args if necessary if methodConfig.ArgsTransform != "" { transformFunc, tfErr := FindTransform(methodConfig.ArgsTransform) @@ -390,7 +392,7 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra return errorWithDebugID(fmt.Errorf("error finding transform function: %w", tfErr), debugID) } s.lggr.Debugw("Applying args transformation", "contract", contractName, "method", method) - args, accounts, err = transformFunc(ctx, args, accounts, derivedTableMap) + args, accounts, computeLimit, err = transformFunc(ctx, args, accounts, derivedTableMap) if err != nil { return errorWithDebugID(fmt.Errorf("error transforming args: %w", err), debugID) } @@ -436,8 +438,13 @@ func (s *SolanaChainWriterService) SubmitTransaction(ctx context.Context, contra } s.lggr.Debugw("Sending main transaction", "contract", contractName, "method", method) + + options := []txmutils.SetTxConfig{} + if computeLimit != 0 { + options = append(options, txmutils.SetComputeUnitLimit(computeLimit)) + } // Enqueue transaction - if err = s.txm.Enqueue(ctx, methodConfig.FromAddress, tx, &transactionID, blockhash.Value.LastValidBlockHeight); err != nil { + if err = s.txm.Enqueue(ctx, methodConfig.FromAddress, tx, &transactionID, blockhash.Value.LastValidBlockHeight, options...); err != nil { return errorWithDebugID(fmt.Errorf("error enqueuing transaction: %w", err), debugID) } diff --git a/pkg/solana/chainwriter/chain_writer_test.go b/pkg/solana/chainwriter/chain_writer_test.go index 2aab7625b..84d9b17c8 100644 --- a/pkg/solana/chainwriter/chain_writer_test.go +++ b/pkg/solana/chainwriter/chain_writer_test.go @@ -33,6 +33,7 @@ import ( clientmocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/client/mocks" feemocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees/mocks" txmMocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/mocks" + txmutils "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/utils" ) type Arguments struct { @@ -927,7 +928,14 @@ func TestChainWriter_CCIPOfframp(t *testing.T) { require.Len(t, tokenIndexes, 1) require.Equal(t, uint8(3), tokenIndexes[0]) return true - }), &txID, mock.Anything).Return(nil).Once() + }), &txID, mock.Anything, mock.AnythingOfType("utils.SetTxConfig")).Return(nil).Run(func(args mock.Arguments) { + setComputeLimit, ok := args[5].(txmutils.SetTxConfig) + require.True(t, ok) + + txConfig := &txmutils.TxConfig{} + setComputeLimit(txConfig) + require.Equal(t, uint32(500), txConfig.ComputeUnitLimit) + }).Once() // stripped back report just for purposes of example abstractReport := ccipocr3.ExecutePluginReportSingleChain{ @@ -938,6 +946,9 @@ func TestChainWriter_CCIPOfframp(t *testing.T) { DestTokenAddress: destTokenAddr.Bytes(), }, }, + ExtraArgsDecoded: map[string]any{ + "ComputeUnits": uint32(500), + }, }, }, } diff --git a/pkg/solana/chainwriter/transform_registry.go b/pkg/solana/chainwriter/transform_registry.go index 99781f5b3..98071ad69 100644 --- a/pkg/solana/chainwriter/transform_registry.go +++ b/pkg/solana/chainwriter/transform_registry.go @@ -7,19 +7,17 @@ import ( "github.com/gagliardetto/solana-go" "github.com/mitchellh/mapstructure" - "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" ) type ReportPostTransform struct { - ReportContext [2][32]byte - Report []byte - Info ccipocr3.ExecuteReportInfo - AbstractReport ccip_offramp.ExecutionReportSingleChain - TokenIndexes []byte + ReportContext [2][32]byte + Report []byte + Info ccipocr3.ExecuteReportInfo + TokenIndexes []byte } -func FindTransform(id string) (func(context.Context, any, solana.AccountMetaSlice, map[string]map[string][]*solana.AccountMeta) (any, solana.AccountMetaSlice, error), error) { +func FindTransform(id string) (func(context.Context, any, solana.AccountMetaSlice, map[string]map[string][]*solana.AccountMeta) (any, solana.AccountMetaSlice, uint32, error), error) { switch id { case "CCIPExecute": return CCIPExecuteArgsTransform, nil @@ -32,11 +30,20 @@ func FindTransform(id string) (func(context.Context, any, solana.AccountMetaSlic // 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 CCIPExecuteArgsTransform(ctx context.Context, args any, accounts solana.AccountMetaSlice, tableMap map[string]map[string][]*solana.AccountMeta) (any, solana.AccountMetaSlice, error) { +func CCIPExecuteArgsTransform(ctx context.Context, args any, accounts solana.AccountMetaSlice, tableMap map[string]map[string][]*solana.AccountMeta) (any, solana.AccountMetaSlice, uint32, error) { var argsTransformed ReportPostTransform err := mapstructure.Decode(args, &argsTransformed) if err != nil { - return nil, nil, err + return nil, nil, 0, err + } + + if len(argsTransformed.Info.AbstractReports) != 1 || len(argsTransformed.Info.AbstractReports[0].Messages) != 1 { + return nil, nil, 0, fmt.Errorf("Expected 1 report with 1 message") + } + + computeUnits := argsTransformed.Info.AbstractReports[0].Messages[0].ExtraArgsDecoded["ComputeUnits"] + if computeUnits == nil { + return nil, nil, 0, errors.New("missing ComputeUnits in ExtraArgs") } registryTables, exists := tableMap["PoolLookupTable"] @@ -44,7 +51,7 @@ func CCIPExecuteArgsTransform(ctx context.Context, args any, accounts solana.Acc // Return with empty TokenIndexes if !exists { argsTransformed.TokenIndexes = []byte{} - return argsTransformed, accounts, nil + return argsTransformed, accounts, computeUnits.(uint32), nil } tokenPoolAddresses := []solana.PublicKey{} @@ -57,7 +64,7 @@ func CCIPExecuteArgsTransform(ctx context.Context, args any, accounts solana.Acc for _, address := range tokenPoolAddresses { if account.PublicKey == address { if i > 255 { - return nil, nil, fmt.Errorf("index %d out of range for uint8", i) + return nil, nil, 0, fmt.Errorf("index %d out of range for uint8", i) } tokenIndexes = append(tokenIndexes, uint8(i)) //nolint:gosec } @@ -65,28 +72,28 @@ func CCIPExecuteArgsTransform(ctx context.Context, args any, accounts solana.Acc } if len(tokenIndexes) != len(tokenPoolAddresses) { - return nil, nil, fmt.Errorf("missing token pools in accounts") + return nil, nil, 0, fmt.Errorf("missing token pools in accounts") } argsTransformed.TokenIndexes = tokenIndexes - return argsTransformed, accounts, nil + return argsTransformed, accounts, computeUnits.(uint32), nil } // This Transform function trims off the GlobalState account from commit transactions if there are no token or gas price updates -func CCIPCommitAccountTransform(ctx context.Context, args any, accounts solana.AccountMetaSlice, _ map[string]map[string][]*solana.AccountMeta) (any, solana.AccountMetaSlice, error) { +func CCIPCommitAccountTransform(ctx context.Context, args any, accounts solana.AccountMetaSlice, _ map[string]map[string][]*solana.AccountMeta) (any, solana.AccountMetaSlice, uint32, error) { var tokenPriceVals, gasPriceVals [][]byte var err error tokenPriceVals, err = GetValuesAtLocation(args, "Info.TokenPrices.TokenID") if err != nil && !errors.Is(err, errFieldNotFound) { - return nil, nil, fmt.Errorf("error getting values at location: %w", err) + return nil, nil, 0, fmt.Errorf("error getting values at location: %w", err) } gasPriceVals, err = GetValuesAtLocation(args, "Info.GasPrices.ChainSel") if err != nil && !errors.Is(err, errFieldNotFound) { - return nil, nil, fmt.Errorf("error getting values at location: %w", err) + return nil, nil, 0, fmt.Errorf("error getting values at location: %w", err) } transformedAccounts := accounts if len(tokenPriceVals) == 0 && len(gasPriceVals) == 0 { transformedAccounts = accounts[:len(accounts)-1] } - return args, transformedAccounts, nil + return args, transformedAccounts, 0, nil } diff --git a/pkg/solana/chainwriter/transform_registry_test.go b/pkg/solana/chainwriter/transform_registry_test.go index 872939558..d19b88b09 100644 --- a/pkg/solana/chainwriter/transform_registry_test.go +++ b/pkg/solana/chainwriter/transform_registry_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/gagliardetto/solana-go" - "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/ccip_offramp" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/stretchr/testify/require" @@ -13,10 +12,9 @@ import ( ) type ReportPreTransform struct { - ReportContext [2][32]byte - Report []byte - Info ccipocr3.ExecuteReportInfo - AbstractReport ccip_offramp.ExecutionReportSingleChain + ReportContext [2][32]byte + Report []byte + Info ccipocr3.ExecuteReportInfo } func Test_CCIPExecuteArgsTransform(t *testing.T) { @@ -33,6 +31,9 @@ func Test_CCIPExecuteArgsTransform(t *testing.T) { TokenAmounts: []ccipocr3.RampTokenAmount{{ DestTokenAddress: ccipocr3.UnknownAddress(destTokenAddr.Bytes()), }}, + ExtraArgsDecoded: map[string]any{ + "ComputeUnits": uint32(500), + }, }}, }}, }, @@ -40,7 +41,7 @@ func Test_CCIPExecuteArgsTransform(t *testing.T) { accounts := []*solana.AccountMeta{{PublicKey: poolKeys[0]}, {PublicKey: poolKeys[1]}} - t.Run("CCIPExecute ArgsTransform includes token indexes", func(t *testing.T) { + t.Run("CCIPExecute ArgsTransform includes token indexes and computeUnits", func(t *testing.T) { tableMap := make(map[string]map[string][]*solana.AccountMeta) tableMap["PoolLookupTable"] = make(map[string][]*solana.AccountMeta) lookupTablePubkey := chainwriter.GetRandomPubKey(t) @@ -51,8 +52,10 @@ func Test_CCIPExecuteArgsTransform(t *testing.T) { } tableMap["PoolLookupTable"][lookupTablePubkey.String()] = poolKeysMeta - transformedArgs, newAccounts, err := chainwriter.CCIPExecuteArgsTransform(ctx, args, accounts, tableMap) + transformedArgs, newAccounts, computeUnits, err := chainwriter.CCIPExecuteArgsTransform(ctx, args, accounts, tableMap) require.NoError(t, err) + + require.Equal(t, computeUnits, uint32(500)) // Accounts should be unchanged require.Len(t, newAccounts, 2) typedArgs, ok := transformedArgs.(chainwriter.ReportPostTransform) @@ -62,8 +65,10 @@ func Test_CCIPExecuteArgsTransform(t *testing.T) { }) t.Run("CCIPExecute ArgsTransform includes empty token indexes if lookup table not found", func(t *testing.T) { - transformedArgs, newAccounts, err := chainwriter.CCIPExecuteArgsTransform(ctx, args, accounts, nil) + transformedArgs, newAccounts, computeUnits, err := chainwriter.CCIPExecuteArgsTransform(ctx, args, accounts, nil) require.NoError(t, err) + require.Equal(t, computeUnits, uint32(500)) + // Accounts should be unchanged require.Len(t, newAccounts, 2) typedArgs, ok := transformedArgs.(chainwriter.ReportPostTransform) @@ -76,16 +81,58 @@ func Test_CCIPExecuteArgsTransform(t *testing.T) { args := struct { ReportContext [2][32]uint8 Report []uint8 + Info ccipocr3.ExecuteReportInfo }{ ReportContext: [2][32]uint8{}, Report: []uint8{}, + Info: ccipocr3.ExecuteReportInfo{ + AbstractReports: []ccipocr3.ExecutePluginReportSingleChain{{ + Messages: []ccipocr3.Message{{ + ExtraArgsDecoded: map[string]any{ + "ComputeUnits": uint32(500), + }, + }}, + }}, + }, } - transformedArgs, newAccounts, err := chainwriter.CCIPExecuteArgsTransform(ctx, args, accounts, nil) + transformedArgs, newAccounts, computeUnits, err := chainwriter.CCIPExecuteArgsTransform(ctx, args, accounts, nil) require.NoError(t, err) + + require.Equal(t, computeUnits, uint32(500)) _, ok := transformedArgs.(chainwriter.ReportPostTransform) require.True(t, ok) require.Len(t, newAccounts, 2) }) + + t.Run("CCIPExecute ArgsTransform fails with empty Info", func(t *testing.T) { + args := struct { + ReportContext [2][32]uint8 + Report []uint8 + Info ccipocr3.ExecuteReportInfo + }{ + ReportContext: [2][32]uint8{}, + Report: []uint8{}, + Info: ccipocr3.ExecuteReportInfo{}, + } + _, _, _, err := chainwriter.CCIPExecuteArgsTransform(ctx, args, accounts, nil) + require.Contains(t, err.Error(), "Expected 1 report with 1 message") + }) + + t.Run("CCIPExecute ArgsTransform fails with multiple reports", func(t *testing.T) { + args := struct { + ReportContext [2][32]uint8 + Report []uint8 + Info ccipocr3.ExecuteReportInfo + }{ + ReportContext: [2][32]uint8{}, + Report: []uint8{}, + Info: ccipocr3.ExecuteReportInfo{ + AbstractReports: []ccipocr3.ExecutePluginReportSingleChain{{}, {}}, + }, + } + _, _, _, err := chainwriter.CCIPExecuteArgsTransform(ctx, args, accounts, nil) + require.Contains(t, err.Error(), "Expected 1 report with 1 message") + }) } func Test_CCIPCommitAccountTransform(t *testing.T) { @@ -101,7 +148,7 @@ func Test_CCIPCommitAccountTransform(t *testing.T) { }, } accounts := []*solana.AccountMeta{{PublicKey: key1}, {PublicKey: key2}} - _, newAccounts, err := chainwriter.CCIPCommitAccountTransform(ctx, args, accounts, nil) + _, newAccounts, _, err := chainwriter.CCIPCommitAccountTransform(ctx, args, accounts, nil) require.NoError(t, err) require.Len(t, newAccounts, 2) }) @@ -112,7 +159,7 @@ func Test_CCIPCommitAccountTransform(t *testing.T) { Info: ccipocr3.CommitReportInfo{}, } accounts := []*solana.AccountMeta{{PublicKey: key1}, {PublicKey: key2}} - _, newAccounts, err := chainwriter.CCIPCommitAccountTransform(ctx, args, accounts, nil) + _, newAccounts, _, err := chainwriter.CCIPCommitAccountTransform(ctx, args, accounts, nil) require.NoError(t, err) require.Len(t, newAccounts, 1) })